🔐 feat: Implement Entra ID authentication for Azure OpenAI integration

- Added support for Entra ID authentication in OpenAIClient and related services.
- Updated header management to conditionally use Entra ID access tokens or API keys based on environment configuration.
- Introduced utility functions for Entra ID token retrieval and credential management.
- Enhanced tests to verify Entra ID authentication flow and its integration with Azure configurations.
This commit is contained in:
victorbjorkgren 2025-09-12 17:29:43 +02:00 committed by victorbjor
parent a1471c2f37
commit 9288e84454
9 changed files with 212 additions and 18 deletions

View file

@ -1549,4 +1549,22 @@ describe('getOpenAIConfig', () => {
});
});
});
describe('Entra ID Authentication', () => {
it('should handle Entra ID authentication in Azure configuration', () => {
const azure = {
azureOpenAIApiInstanceName: 'test-instance',
azureOpenAIApiDeploymentName: 'test-deployment',
azureOpenAIApiVersion: '2023-05-15',
azureOpenAIApiKey: 'entra-id-placeholder',
};
const result = getOpenAIConfig(mockApiKey, { azure });
expect(result.llmConfig).toMatchObject({
...azure,
model: 'test-deployment',
});
});
});
});

View file

@ -6,7 +6,7 @@ import type {
UserKeyValues,
} from '~/types';
import { createHandleLLMNewToken } from '~/utils/generators';
import { getAzureCredentials } from '~/utils/azure';
import { getAzureCredentials, getEntraIdAccessToken, shouldUseEntraId } from '~/utils/azure';
import { isUserProvided } from '~/utils/common';
import { resolveHeaders } from '~/utils/env';
import { getOpenAIConfig } from './config';
@ -110,12 +110,30 @@ export const initializeOpenAI = async ({
if (!clientOptions.headers) {
clientOptions.headers = {};
}
clientOptions.headers['api-key'] = apiKey;
if (shouldUseEntraId()) {
clientOptions.headers['Authorization'] = `Bearer ${await getEntraIdAccessToken()}`;
} else {
clientOptions.headers['api-key'] = apiKey || '';
}
} else {
apiKey = azureOptions.azureOpenAIApiKey || '';
clientOptions.azure = azureOptions;
if (shouldUseEntraId()) {
apiKey = 'entra-id-placeholder';
clientOptions.headers['Authorization'] = `Bearer ${await getEntraIdAccessToken()}`;
}
}
} else if (isAzureOpenAI) {
clientOptions.azure =
userProvidesKey && userValues?.apiKey ? JSON.parse(userValues.apiKey) : getAzureCredentials();
apiKey = clientOptions.azure ? clientOptions.azure.azureOpenAIApiKey : undefined;
if (shouldUseEntraId()) {
clientOptions.headers = {
...clientOptions.headers,
Authorization: `Bearer ${await getEntraIdAccessToken()}`,
};
} else {
apiKey = clientOptions.azure ? clientOptions.azure.azureOpenAIApiKey : undefined;
}
}
if (userProvidesKey && !apiKey) {

View file

@ -1,5 +1,7 @@
import { isEnabled } from './common';
import type { AzureOptions, GenericClient } from '~/types';
import { DefaultAzureCredential } from '@azure/identity';
import { logger } from '@librechat/data-schemas';
/**
* Sanitizes the model name to be used in the URL by removing or replacing disallowed characters.
@ -118,3 +120,43 @@ export function constructAzureURL({
return finalURL;
}
/**
* Checks if Entra ID authentication should be used based on environment variables.
* @returns {boolean} True if Entra ID authentication should be used
*/
export const shouldUseEntraId = (): boolean => {
return process.env.AZURE_OPENAI_USE_ENTRA_ID === 'true';
};
/**
* Creates an Azure credential for Entra ID authentication.
* Uses DefaultAzureCredential which supports multiple authentication methods:
* - Managed Identity (when running in Azure)
* - Service Principal (when environment variables are set)
* - Azure CLI (for local development)
* - Visual Studio Code (for local development)
*
* @returns DefaultAzureCredential instance
*/
export const createEntraIdCredential = (): DefaultAzureCredential => {
return new DefaultAzureCredential();
};
/**
* Gets the access token for Entra ID authentication from azure/identity.
* @returns {Promise<AccessToken>} The access token
*/
export const getEntraIdAccessToken = async (): Promise<string> => {
try {
const credential = createEntraIdCredential();
const tokenResponse = await credential.getToken('https://cognitiveservices.azure.com/.default');
return tokenResponse.token;
} catch (error) {
logger.error('[ENTRA_ID_DEBUG] Failed to get Entra ID access token:', error);
throw error;
}
};