Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend msal-browser TokenCache loadExternalTokens to load refresh tokens #5233

Merged
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Extend msal-browser TokenCache to load refresh tokens #5233",
"packageName": "@azure/msal-browser",
"email": "louisv@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Include refresh token in ExternalTokenResponse #5233",
"packageName": "@azure/msal-common",
"email": "louisv@microsoft.com",
"dependentChangeType": "patch"
}
10 changes: 7 additions & 3 deletions lib/msal-browser/src/cache/ITokenCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
* Licensed under the MIT License.
*/

import { ExternalTokenResponse } from "@azure/msal-common";
import { ExternalTokenResponse, CacheRecord } from "@azure/msal-common";
import { SilentRequest } from "../request/SilentRequest";
import { LoadTokenOptions } from "./TokenCache";

export interface ITokenCache {

/** API to side-load tokens to MSAL cache */
loadExternalTokens(request: SilentRequest, response: ExternalTokenResponse, options: LoadTokenOptions): void;
/**
* API to side-load tokens to MSAL cache
* @returns A `CacheRecord` containing the entities that were loaded.
*/
loadExternalTokens(request: SilentRequest, response: ExternalTokenResponse, options: LoadTokenOptions): CacheRecord;
tnorling marked this conversation as resolved.
Show resolved Hide resolved

}
114 changes: 95 additions & 19 deletions lib/msal-browser/src/cache/TokenCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License.
*/

import { AccessTokenEntity, ICrypto, IdTokenEntity, Logger, ScopeSet, Authority, AuthorityOptions, ExternalTokenResponse, AccountEntity, AuthToken } from "@azure/msal-common";
import { AccessTokenEntity, ICrypto, IdTokenEntity, Logger, ScopeSet, Authority, AuthorityOptions, ExternalTokenResponse, AccountEntity, AuthToken, RefreshTokenEntity , AuthorityType, CacheRecord } from "@azure/msal-common";
import { BrowserConfiguration } from "../config/Configuration";
import { SilentRequest } from "../request/SilentRequest";
import { BrowserCacheManager } from "./BrowserCacheManager";
Expand Down Expand Up @@ -46,17 +46,23 @@ export class TokenCache implements ITokenCache {
* @param request
* @param response
* @param options
* @returns A `CacheRecord` containing the entities that were loaded.
*/
loadExternalTokens(request: SilentRequest, response: ExternalTokenResponse, options: LoadTokenOptions): void {
loadExternalTokens(request: SilentRequest, response: ExternalTokenResponse, options: LoadTokenOptions): CacheRecord {
this.logger.info("TokenCache - loadExternalTokens called");

if (!response.id_token) {
throw BrowserAuthError.createUnableToLoadTokenError("Please ensure server response includes id token.");
}

if (request.account) {
this.loadIdToken(response.id_token, request.account.homeAccountId, request.account.environment, request.account.tenantId, options);
this.loadAccessToken(request, response, request.account.homeAccountId, request.account.environment, request.account.tenantId, options);
const cacheRecordAccount = this.loadAccount(response.id_token, request.account.environment, undefined, undefined, request.account.homeAccountId);
return new CacheRecord(
cacheRecordAccount,
this.loadIdToken(response.id_token, cacheRecordAccount.homeAccountId, request.account.environment, request.account.tenantId),
this.loadAccessToken(request, response, cacheRecordAccount.homeAccountId, request.account.environment, request.account.tenantId, options),
this.loadRefreshToken(request, response, cacheRecordAccount.homeAccountId, request.account.environment)
);
} else if (request.authority) {

const authorityUrl = Authority.generateAuthority(request.authority, request.azureCloudOptions);
Expand All @@ -72,12 +78,22 @@ export class TokenCache implements ITokenCache {
// "clientInfo" from options takes precedence over "clientInfo" in response
if (options.clientInfo) {
this.logger.trace("TokenCache - homeAccountId from options");
this.loadIdToken(response.id_token, options.clientInfo, authority.hostnameAndPort, authority.tenant, options);
this.loadAccessToken(request, response, options.clientInfo, authority.hostnameAndPort, authority.tenant, options);
const cacheRecordAccount = this.loadAccount(response.id_token, authority.hostnameAndPort, options.clientInfo, authority.authorityType);
return new CacheRecord(
cacheRecordAccount,
this.loadIdToken(response.id_token, cacheRecordAccount.homeAccountId, authority.hostnameAndPort, authority.tenant),
this.loadAccessToken(request, response, cacheRecordAccount.homeAccountId, authority.hostnameAndPort, authority.tenant, options),
this.loadRefreshToken(request, response, cacheRecordAccount.homeAccountId, authority.hostnameAndPort)
);
} else if (response.client_info) {
this.logger.trace("TokenCache - homeAccountId from response");
this.loadIdToken(response.id_token, response.client_info, authority.hostnameAndPort, authority.tenant, options);
this.loadAccessToken(request, response, response.client_info, authority.hostnameAndPort, authority.tenant, options);
const cacheRecordAccount = this.loadAccount(response.id_token, authority.hostnameAndPort, response.client_info, authority.authorityType);
return new CacheRecord(
cacheRecordAccount,
this.loadIdToken(response.id_token, cacheRecordAccount.homeAccountId, authority.hostnameAndPort, authority.tenant),
this.loadAccessToken(request, response, cacheRecordAccount.homeAccountId, authority.hostnameAndPort, authority.tenant, options),
this.loadRefreshToken(request, response, cacheRecordAccount.homeAccountId, authority.hostnameAndPort)
);
} else {
throw BrowserAuthError.createUnableToLoadTokenError("Please provide clientInfo in the response or options.");
}
Expand All @@ -86,26 +102,60 @@ export class TokenCache implements ITokenCache {
}
}

/**
* Helper function to load account to msal-browser cache
* @param idToken
* @param environment
* @param clientInfo
* @param authorityType
* @param requestHomeAccountId
* @returns `AccountEntity`
*/
private loadAccount(idToken: string, environment: string, clientInfo?: string, authorityType?: AuthorityType, requestHomeAccountId?: string): AccountEntity {

const idAuthToken = new AuthToken(idToken, this.cryptoObj);

let homeAccountId;
if (requestHomeAccountId) {
homeAccountId = requestHomeAccountId;
} else if (authorityType !== undefined && clientInfo) {
homeAccountId = AccountEntity.generateHomeAccountId(clientInfo, authorityType, this.logger, this.cryptoObj, idAuthToken);
}

if (!homeAccountId) {
throw BrowserAuthError.createUnableToLoadTokenError("Unexpected missing homeAccountId");
}

const accountEntity = clientInfo ?
AccountEntity.createAccount(clientInfo, homeAccountId, idAuthToken, undefined, undefined, undefined, environment) :
AccountEntity.createGenericAccount(homeAccountId, idAuthToken, undefined, undefined, undefined, environment);

if (this.isBrowserEnvironment) {
this.logger.verbose("TokenCache - loading account");

this.storage.setAccount(accountEntity);
return accountEntity;
} else {
throw BrowserAuthError.createUnableToLoadTokenError("loadExternalTokens is designed to work in browser environments only.");
}
}

/**
* Helper function to load id tokens to msal-browser cache
* @param idToken
* @param homeAccountId
* @param environment
* @param tenantId
* @param options
* @returns `IdTokenEntity`
*/
private loadIdToken(idToken: string, homeAccountId: string, environment: string, tenantId: string, options: LoadTokenOptions): void {
private loadIdToken(idToken: string, homeAccountId: string, environment: string, tenantId: string): IdTokenEntity {

const idTokenEntity = IdTokenEntity.createIdTokenEntity(homeAccountId, environment, idToken, this.config.auth.clientId, tenantId);
const idAuthToken = new AuthToken(idToken, this.cryptoObj);
const accountEntity = options.clientInfo ?
AccountEntity.createAccount(options.clientInfo, homeAccountId, idAuthToken, undefined, undefined, undefined, environment) :
AccountEntity.createGenericAccount(homeAccountId, idAuthToken, undefined, undefined, undefined, environment);

if (this.isBrowserEnvironment) {
this.logger.verbose("TokenCache - loading id token");
this.storage.setAccount(accountEntity);
this.storage.setIdTokenCredential(idTokenEntity);
return idTokenEntity;
} else {
throw BrowserAuthError.createUnableToLoadTokenError("loadExternalTokens is designed to work in browser environments only.");
}
Expand All @@ -115,17 +165,16 @@ export class TokenCache implements ITokenCache {
* Helper function to load access tokens to msal-browser cache
* @param request
* @param response
* @param options
* @param homeAccountId
* @param environment
* @param tenantId
* @returns
* @returns `AccessTokenEntity`
*/
private loadAccessToken(request: SilentRequest, response: ExternalTokenResponse, homeAccountId: string, environment: string, tenantId: string, options: LoadTokenOptions): void {
private loadAccessToken(request: SilentRequest, response: ExternalTokenResponse, homeAccountId: string, environment: string, tenantId: string, options: LoadTokenOptions): AccessTokenEntity | null {

if (!response.access_token) {
this.logger.verbose("TokenCache - No access token provided for caching");
return;
return null;
}

if (!response.expires_in) {
Expand All @@ -145,6 +194,33 @@ export class TokenCache implements ITokenCache {
if (this.isBrowserEnvironment) {
this.logger.verbose("TokenCache - loading access token");
this.storage.setAccessTokenCredential(accessTokenEntity);
return accessTokenEntity;
} else {
throw BrowserAuthError.createUnableToLoadTokenError("loadExternalTokens is designed to work in browser environments only.");
}
}

/**
* Helper function to load refresh tokens to msal-browser cache
* @param request
* @param response
* @param homeAccountId
* @param environment
* @returns `RefreshTokenEntity`
*/
private loadRefreshToken(request: SilentRequest, response: ExternalTokenResponse, homeAccountId: string, environment: string): RefreshTokenEntity | null {

if (!response.refresh_token) {
this.logger.verbose("TokenCache - No refresh token provided for caching");
return null;
}

const refreshTokenEntity = RefreshTokenEntity.createRefreshTokenEntity(homeAccountId, environment, response.refresh_token, this.config.auth.clientId);

if (this.isBrowserEnvironment) {
this.logger.verbose("TokenCache - loading refresh token");
this.storage.setRefreshTokenCredential(refreshTokenEntity);
return refreshTokenEntity;
} else {
throw BrowserAuthError.createUnableToLoadTokenError("loadExternalTokens is designed to work in browser environments only.");
}
Expand Down