@azure/msal-browser
Version:
Microsoft Authentication Library for js
208 lines (205 loc) • 10.3 kB
JavaScript
/*! @azure/msal-browser v4.12.0 2025-05-06 */
'use strict';
import { AuthToken, Authority, AccountEntity, buildAccountToCache, CacheHelpers, ScopeSet, TimeUtils } from '@azure/msal-common/browser';
import { createBrowserAuthError } from '../error/BrowserAuthError.mjs';
import { base64Decode } from '../encode/Base64Decode.mjs';
import { createNewGuid } from '../crypto/BrowserCrypto.mjs';
import { nonBrowserEnvironment, unableToLoadToken } from '../error/BrowserAuthErrorCodes.mjs';
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
/**
* Token cache manager
*/
class TokenCache {
constructor(configuration, storage, logger, cryptoObj) {
this.isBrowserEnvironment = typeof window !== "undefined";
this.config = configuration;
this.storage = storage;
this.logger = logger;
this.cryptoObj = cryptoObj;
}
// Move getAllAccounts here and cache utility APIs
/**
* API to load tokens to msal-browser cache.
* @param request
* @param response
* @param options
* @returns `AuthenticationResult` for the response that was loaded.
*/
async loadExternalTokens(request, response, options) {
if (!this.isBrowserEnvironment) {
throw createBrowserAuthError(nonBrowserEnvironment);
}
const correlationId = request.correlationId || createNewGuid();
const idTokenClaims = response.id_token
? AuthToken.extractTokenClaims(response.id_token, base64Decode)
: undefined;
const authorityOptions = {
protocolMode: this.config.auth.protocolMode,
knownAuthorities: this.config.auth.knownAuthorities,
cloudDiscoveryMetadata: this.config.auth.cloudDiscoveryMetadata,
authorityMetadata: this.config.auth.authorityMetadata,
skipAuthorityMetadataCache: this.config.auth.skipAuthorityMetadataCache,
};
const authority = request.authority
? new Authority(Authority.generateAuthority(request.authority, request.azureCloudOptions), this.config.system.networkClient, this.storage, authorityOptions, this.logger, request.correlationId || createNewGuid())
: undefined;
const cacheRecordAccount = await this.loadAccount(request, options.clientInfo || response.client_info || "", correlationId, idTokenClaims, authority);
const idToken = await this.loadIdToken(response, cacheRecordAccount.homeAccountId, cacheRecordAccount.environment, cacheRecordAccount.realm, correlationId);
const accessToken = await this.loadAccessToken(request, response, cacheRecordAccount.homeAccountId, cacheRecordAccount.environment, cacheRecordAccount.realm, options, correlationId);
const refreshToken = await this.loadRefreshToken(response, cacheRecordAccount.homeAccountId, cacheRecordAccount.environment, correlationId);
return this.generateAuthenticationResult(request, {
account: cacheRecordAccount,
idToken,
accessToken,
refreshToken,
}, idTokenClaims, authority);
}
/**
* Helper function to load account to msal-browser cache
* @param idToken
* @param environment
* @param clientInfo
* @param authorityType
* @param requestHomeAccountId
* @returns `AccountEntity`
*/
async loadAccount(request, clientInfo, correlationId, idTokenClaims, authority) {
this.logger.verbose("TokenCache - loading account");
if (request.account) {
const accountEntity = AccountEntity.createFromAccountInfo(request.account);
await this.storage.setAccount(accountEntity, correlationId);
return accountEntity;
}
else if (!authority || (!clientInfo && !idTokenClaims)) {
this.logger.error("TokenCache - if an account is not provided on the request, authority and either clientInfo or idToken must be provided instead.");
throw createBrowserAuthError(unableToLoadToken);
}
const homeAccountId = AccountEntity.generateHomeAccountId(clientInfo, authority.authorityType, this.logger, this.cryptoObj, idTokenClaims);
const claimsTenantId = idTokenClaims?.tid;
const cachedAccount = buildAccountToCache(this.storage, authority, homeAccountId, base64Decode, idTokenClaims, clientInfo, authority.hostnameAndPort, claimsTenantId, undefined, // authCodePayload
undefined, // nativeAccountId
this.logger);
await this.storage.setAccount(cachedAccount, correlationId);
return cachedAccount;
}
/**
* Helper function to load id tokens to msal-browser cache
* @param idToken
* @param homeAccountId
* @param environment
* @param tenantId
* @returns `IdTokenEntity`
*/
async loadIdToken(response, homeAccountId, environment, tenantId, correlationId) {
if (!response.id_token) {
this.logger.verbose("TokenCache - no id token found in response");
return null;
}
this.logger.verbose("TokenCache - loading id token");
const idTokenEntity = CacheHelpers.createIdTokenEntity(homeAccountId, environment, response.id_token, this.config.auth.clientId, tenantId);
await this.storage.setIdTokenCredential(idTokenEntity, correlationId);
return idTokenEntity;
}
/**
* Helper function to load access tokens to msal-browser cache
* @param request
* @param response
* @param homeAccountId
* @param environment
* @param tenantId
* @returns `AccessTokenEntity`
*/
async loadAccessToken(request, response, homeAccountId, environment, tenantId, options, correlationId) {
if (!response.access_token) {
this.logger.verbose("TokenCache - no access token found in response");
return null;
}
else if (!response.expires_in) {
this.logger.error("TokenCache - no expiration set on the access token. Cannot add it to the cache.");
return null;
}
else if (!response.scope &&
(!request.scopes || !request.scopes.length)) {
this.logger.error("TokenCache - scopes not specified in the request or response. Cannot add token to the cache.");
return null;
}
this.logger.verbose("TokenCache - loading access token");
const scopes = response.scope
? ScopeSet.fromString(response.scope)
: new ScopeSet(request.scopes);
const expiresOn = options.expiresOn || response.expires_in + TimeUtils.nowSeconds();
const extendedExpiresOn = options.extendedExpiresOn ||
(response.ext_expires_in || response.expires_in) +
TimeUtils.nowSeconds();
const accessTokenEntity = CacheHelpers.createAccessTokenEntity(homeAccountId, environment, response.access_token, this.config.auth.clientId, tenantId, scopes.printScopes(), expiresOn, extendedExpiresOn, base64Decode);
await this.storage.setAccessTokenCredential(accessTokenEntity, correlationId);
return accessTokenEntity;
}
/**
* Helper function to load refresh tokens to msal-browser cache
* @param request
* @param response
* @param homeAccountId
* @param environment
* @returns `RefreshTokenEntity`
*/
async loadRefreshToken(response, homeAccountId, environment, correlationId) {
if (!response.refresh_token) {
this.logger.verbose("TokenCache - no refresh token found in response");
return null;
}
this.logger.verbose("TokenCache - loading refresh token");
const refreshTokenEntity = CacheHelpers.createRefreshTokenEntity(homeAccountId, environment, response.refresh_token, this.config.auth.clientId, response.foci, undefined, // userAssertionHash
response.refresh_token_expires_in);
await this.storage.setRefreshTokenCredential(refreshTokenEntity, correlationId);
return refreshTokenEntity;
}
/**
* Helper function to generate an `AuthenticationResult` for the result.
* @param request
* @param idTokenObj
* @param cacheRecord
* @param authority
* @returns `AuthenticationResult`
*/
generateAuthenticationResult(request, cacheRecord, idTokenClaims, authority) {
let accessToken = "";
let responseScopes = [];
let expiresOn = null;
let extExpiresOn;
if (cacheRecord?.accessToken) {
accessToken = cacheRecord.accessToken.secret;
responseScopes = ScopeSet.fromString(cacheRecord.accessToken.target).asArray();
// Access token expiresOn stored in seconds, converting to Date for AuthenticationResult
expiresOn = TimeUtils.toDateFromSeconds(cacheRecord.accessToken.expiresOn);
extExpiresOn = TimeUtils.toDateFromSeconds(cacheRecord.accessToken.extendedExpiresOn);
}
const accountEntity = cacheRecord.account;
return {
authority: authority ? authority.canonicalAuthority : "",
uniqueId: cacheRecord.account.localAccountId,
tenantId: cacheRecord.account.realm,
scopes: responseScopes,
account: accountEntity.getAccountInfo(),
idToken: cacheRecord.idToken?.secret || "",
idTokenClaims: idTokenClaims || {},
accessToken: accessToken,
fromCache: true,
expiresOn: expiresOn,
correlationId: request.correlationId || "",
requestId: "",
extExpiresOn: extExpiresOn,
familyId: cacheRecord.refreshToken?.familyId || "",
tokenType: cacheRecord?.accessToken?.tokenType || "",
state: request.state || "",
cloudGraphHostName: accountEntity.cloudGraphHostName || "",
msGraphHost: accountEntity.msGraphHost || "",
fromNativeBroker: false,
};
}
}
export { TokenCache };
//# sourceMappingURL=TokenCache.mjs.map