Browse Source

feat: Allow Azure OpenAI to authenticate using DefaultAzureCredential

Co-Authored-By: Selene Blok <20491756+selenecodes@users.noreply.github.com>
Timothy Jaeryang Baek 2 weeks ago
parent
commit
caf0a1fbb6
2 changed files with 49 additions and 11 deletions
  1. 37 4
      backend/open_webui/routers/openai.py
  2. 12 7
      src/lib/components/AddConnectionModal.svelte

+ 37 - 4
backend/open_webui/routers/openai.py

@@ -9,6 +9,8 @@ from aiocache import cached
 import requests
 from urllib.parse import quote
 
+from azure.identity import DefaultAzureCredential, get_bearer_token_provider
+
 from fastapi import Depends, HTTPException, Request, APIRouter
 from fastapi.responses import (
     FileResponse,
@@ -182,12 +184,30 @@ def get_headers_and_cookies(
         if oauth_token:
             token = f"{oauth_token.get('access_token', '')}"
 
+    elif auth_type in ("azure_ad", "azure_entra_id"):
+        token = get_azure_entra_id_access_token()
+
     if token:
         headers["Authorization"] = f"Bearer {token}"
 
     return headers, cookies
 
 
+def get_azure_entra_id_access_token():
+    """
+    Get Azure access token using DefaultAzureCredential for Azure OpenAI.
+    Returns the token string or None if authentication fails.
+    """
+    try:
+        token_provider = get_bearer_token_provider(
+            DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default"
+        )
+        return token_provider()
+    except Exception as e:
+        log.error(f"Error getting Azure access token: {e}")
+        return None
+
+
 ##########################################
 #
 # API routes
@@ -641,9 +661,12 @@ async def verify_connection(
             )
 
             if api_config.get("azure", False):
-                headers["api-key"] = key
-                api_version = api_config.get("api_version", "") or "2023-03-15-preview"
+                # Only set api-key header if not using Azure Entra ID authentication
+                auth_type = api_config.get("auth_type", "bearer")
+                if auth_type not in ("azure_ad", "azure_entra_id"):
+                    headers["api-key"] = key
 
+                api_version = api_config.get("api_version", "") or "2023-03-15-preview"
                 async with session.get(
                     url=f"{url}/openai/models?api-version={api_version}",
                     headers=headers,
@@ -885,7 +908,12 @@ async def generate_chat_completion(
     if api_config.get("azure", False):
         api_version = api_config.get("api_version", "2023-03-15-preview")
         request_url, payload = convert_to_azure_payload(url, payload, api_version)
-        headers["api-key"] = key
+
+        # Only set api-key header if not using Azure Entra ID authentication
+        auth_type = api_config.get("auth_type", "bearer")
+        if auth_type not in ("azure_ad", "azure_entra_id"):
+            headers["api-key"] = key
+
         headers["api-version"] = api_version
         request_url = f"{request_url}/chat/completions?api-version={api_version}"
     else:
@@ -1058,7 +1086,12 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
 
         if api_config.get("azure", False):
             api_version = api_config.get("api_version", "2023-03-15-preview")
-            headers["api-key"] = key
+
+            # Only set api-key header if not using Azure Entra ID authentication
+            auth_type = api_config.get("auth_type", "bearer")
+            if auth_type not in ("azure_ad", "azure_entra_id"):
+                headers["api-key"] = key
+
             headers["api-version"] = api_version
 
             payload = json.loads(body)

+ 12 - 7
src/lib/components/AddConnectionModal.svelte

@@ -122,7 +122,7 @@
 				return;
 			}
 
-			if (!key) {
+			if (!key && !['azure_ad', 'azure_entra_id'].includes(auth_type)) {
 				loading = false;
 
 				toast.error($i18n.t('Key is required'));
@@ -331,6 +331,9 @@
 												<option value="session">{$i18n.t('Session')}</option>
 												{#if !direct}
 													<option value="system_oauth">{$i18n.t('OAuth')}</option>
+													{#if azure}
+														<option value="azure_entra_id">{$i18n.t('Azure Entra ID')}</option>
+													{/if}
 												{/if}
 											{/if}
 										</select>
@@ -361,6 +364,12 @@
 											>
 												{$i18n.t('Forwards system user OAuth access token to authenticate')}
 											</div>
+										{:else if ['azure_ad', 'azure_entra_id'].includes(auth_type)}
+											<div
+												class={`text-xs self-center translate-y-[1px] ${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : 'text-gray-500'}`}
+											>
+												{$i18n.t('Uses DefaultAzureCredential to authenticate')}
+											</div>
 										{/if}
 									</div>
 								</div>
@@ -443,7 +452,7 @@
 							</div>
 						{/if}
 
-						<div class="flex flex-col w-full">
+						<div class="flex flex-col w-full mt-2">
 							<div class="mb-1 flex justify-between">
 								<div
 									class={`mb-0.5 text-xs text-gray-500
@@ -499,8 +508,6 @@
 							{/if}
 						</div>
 
-						<hr class=" border-gray-100 dark:border-gray-700/10 my-1.5 w-full" />
-
 						<div class="flex items-center">
 							<label class="sr-only" for="add-model-id-input">{$i18n.t('Add a model ID')}</label>
 							<input
@@ -528,9 +535,7 @@
 						</div>
 					</div>
 
-					<hr class=" border-gray-50 dark:border-gray-850 my-2.5 w-full" />
-
-					<div class="flex gap-2">
+					<div class="flex gap-2 mt-2">
 						<div class="flex flex-col w-full">
 							<div
 								class={`mb-0.5 text-xs text-gray-500