Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | 3x 3x 25x 25x 25x 4x 21x 21x 21x 21x 21x 4x 4x 2x 2x 4x 17x 17x 17x 17x 2x 2x 14x | import { EntraClientAuthCredential } from './credential';
/**
* Token response from the Microsoft Entra ID token endpoint.
*/
interface TokenResponse {
access_token: string;
token_type: string;
expires_in: number;
ext_expires_in?: number;
}
/**
* Error response from the Microsoft Entra ID token endpoint.
*/
interface TokenErrorResponse {
error: string;
error_description: string;
error_codes?: number[];
timestamp?: string;
trace_id?: string;
correlation_id?: string;
}
/**
* A buffer time (in milliseconds) before the token's actual expiration to trigger a refresh.
* Tokens are refreshed 5 minutes before they expire.
*/
const TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000;
/**
* Service for acquiring OAuth 2.0 access tokens using the Client Credentials Flow
* against Microsoft Entra ID (Azure AD).
*
* This flow is used for service-to-service authentication where no user interaction
* is required. The application authenticates using its own identity (client ID + secret)
* rather than on behalf of a user.
*
* Token endpoint: `https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token`
*/
export class EntraClientCredentialService {
/**
* In-memory token cache keyed by `{tenantId}:{clientId}`.
*/
private static _tokenCache = new Map<string, { accessToken: string; expiresAt: number }>();
/**
* Acquires an access token using the Client Credentials Grant.
* Returns a cached token if it is still valid; otherwise, requests a new one.
*
* @param credential The Entra Client Credential configuration.
* @returns A promise that resolves to an access token string.
* @throws An error if the token request fails.
*/
async acquireToken(credential: EntraClientAuthCredential): Promise<string> {
const cacheKey = `${credential.tenantId}:${credential.clientId}`;
const cached = EntraClientCredentialService._tokenCache.get(cacheKey);
if (cached && cached.expiresAt > Date.now() + TOKEN_EXPIRY_BUFFER_MS) {
return cached.accessToken;
}
const tokenEndpoint = `https://login.microsoftonline.com/${credential.tenantId}/oauth2/v2.0/token`;
const scope = credential.scopes.join(' ');
const body = new URLSearchParams({
grant_type: 'client_credentials',
client_id: credential.clientId,
client_secret: credential.clientSecret,
scope: scope
});
const response = await fetch(tokenEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: body.toString()
});
if (!response.ok) {
let errorMessage: string;
try {
const errorBody = await response.json() as TokenErrorResponse;
errorMessage = errorBody.error_description || errorBody.error || response.statusText;
} catch {
errorMessage = await response.text() || response.statusText;
}
throw new Error(`Failed to acquire token from Entra ID: ${errorMessage}`);
}
const tokenResponse = await response.json() as TokenResponse;
const expiresAt = Date.now() + tokenResponse.expires_in * 1000;
EntraClientCredentialService._tokenCache.set(cacheKey, {
accessToken: tokenResponse.access_token,
expiresAt
});
return tokenResponse.access_token;
}
/**
* Clears the cached token for the given credential.
*
* @param credential The Entra Client Credential configuration.
*/
clearCache(credential: EntraClientAuthCredential): void {
const cacheKey = `${credential.tenantId}:${credential.clientId}`;
EntraClientCredentialService._tokenCache.delete(cacheKey);
}
/**
* Clears all cached tokens.
*/
clearAllCaches(): void {
EntraClientCredentialService._tokenCache.clear();
}
}
|