Update to @azure/msal-browser@2.0.0 (#24827)
* Update to @azure/msal-browser@2.0.0 * Retain promise during initialization * Set knownAuthority hostname from authority URL * Add KnownAuthorities config option and fix silent sign-in * Set knownAuthorities default to empty list
This commit is contained in:
parent
3e8c5c48f0
commit
ecf2a23d3d
|
|
@ -1,6 +1,5 @@
|
||||||
import * as Msal from 'msal';
|
import * as Msal from '@azure/msal-browser';
|
||||||
import { StringDict } from 'msal/lib-commonjs/MsalTypes';
|
import { StringDict } from '@azure/msal-common';
|
||||||
import { ClientAuthErrorMessage } from 'msal/lib-commonjs/error/ClientAuthError';
|
|
||||||
|
|
||||||
interface AccessTokenRequestOptions {
|
interface AccessTokenRequestOptions {
|
||||||
scopes: string[];
|
scopes: string[];
|
||||||
|
|
@ -52,34 +51,53 @@ interface AuthorizeServiceConfiguration extends Msal.Configuration {
|
||||||
}
|
}
|
||||||
|
|
||||||
class MsalAuthorizeService implements AuthorizeService {
|
class MsalAuthorizeService implements AuthorizeService {
|
||||||
readonly _msalApplication: Msal.UserAgentApplication;
|
private readonly _msalApplication: Msal.PublicClientApplication;
|
||||||
readonly _callbackPromise: Promise<AuthenticationResult>;
|
private _account: Msal.AccountInfo | undefined;
|
||||||
|
private _redirectCallback: Promise<AuthenticationResult | null> | undefined;
|
||||||
|
|
||||||
constructor(private readonly _settings: AuthorizeServiceConfiguration) {
|
constructor(private readonly _settings: AuthorizeServiceConfiguration) {
|
||||||
|
if (this._settings.auth?.knownAuthorities?.length == 0) {
|
||||||
|
this._settings.auth.knownAuthorities = [new URL(this._settings.auth.authority!).hostname]
|
||||||
|
}
|
||||||
|
this._msalApplication = new Msal.PublicClientApplication(this._settings);
|
||||||
|
}
|
||||||
|
|
||||||
// It is important that we capture the callback-url here as msal will remove the auth parameters
|
getAccount() {
|
||||||
// from the url as soon as it gets initialized.
|
if (this._account) {
|
||||||
const callbackUrl = location.href;
|
return this._account;
|
||||||
this._msalApplication = new Msal.UserAgentApplication(this._settings);
|
}
|
||||||
|
|
||||||
// This promise will only resolve in callback-paths, which is where we check it.
|
const accounts = this._msalApplication.getAllAccounts();
|
||||||
this._callbackPromise = this.createCallbackResult(callbackUrl);
|
if (accounts && accounts.length) {
|
||||||
|
return accounts[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUser() {
|
async getUser() {
|
||||||
const account = this._msalApplication.getAccount();
|
const account = this.getAccount();
|
||||||
return account?.idTokenClaims;
|
if (!account) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const silentRequest = {
|
||||||
|
redirectUri: this._settings.auth?.redirectUri,
|
||||||
|
account: account,
|
||||||
|
scopes: this._settings.defaultAccessTokenScopes
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await this._msalApplication.acquireTokenSilent(silentRequest);
|
||||||
|
return response.idTokenClaims;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAccessToken(request?: AccessTokenRequestOptions): Promise<AccessTokenResult> {
|
async getAccessToken(request?: AccessTokenRequestOptions): Promise<AccessTokenResult> {
|
||||||
try {
|
try {
|
||||||
const newToken = await this.getTokenCore(request?.scopes);
|
const newToken = await this.getTokenCore(request?.scopes);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: AccessTokenResultStatus.Success,
|
status: AccessTokenResultStatus.Success,
|
||||||
token: newToken
|
token: newToken
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return {
|
return {
|
||||||
status: AccessTokenResultStatus.RequiresRedirect
|
status: AccessTokenResultStatus.RequiresRedirect
|
||||||
|
|
@ -88,12 +106,18 @@ class MsalAuthorizeService implements AuthorizeService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTokenCore(scopes?: string[]): Promise<AccessToken | undefined> {
|
async getTokenCore(scopes?: string[]): Promise<AccessToken | undefined> {
|
||||||
const tokenScopes = {
|
const account = this.getAccount();
|
||||||
redirectUri: this._settings.auth.redirectUri as string,
|
if (!account) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const silentRequest = {
|
||||||
|
redirectUri: this._settings.auth?.redirectUri,
|
||||||
|
account: account,
|
||||||
scopes: scopes || this._settings.defaultAccessTokenScopes
|
scopes: scopes || this._settings.defaultAccessTokenScopes
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await this._msalApplication.acquireTokenSilent(tokenScopes);
|
const response = await this._msalApplication.acquireTokenSilent(silentRequest);
|
||||||
return {
|
return {
|
||||||
value: response.accessToken,
|
value: response.accessToken,
|
||||||
grantedScopes: response.scopes,
|
grantedScopes: response.scopes,
|
||||||
|
|
@ -106,9 +130,10 @@ class MsalAuthorizeService implements AuthorizeService {
|
||||||
// Before we start any sign-in flow, clear out any previous state so that it doesn't pile up.
|
// Before we start any sign-in flow, clear out any previous state so that it doesn't pile up.
|
||||||
this.purgeState();
|
this.purgeState();
|
||||||
|
|
||||||
const request: Msal.AuthenticationParameters = {
|
const request: Msal.AuthorizationUrlRequest = {
|
||||||
redirectUri: this._settings.auth.redirectUri as string,
|
redirectUri: this._settings.auth?.redirectUri,
|
||||||
state: await this.saveState(state)
|
state: await this.saveState(state),
|
||||||
|
scopes: []
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this._settings.defaultAccessTokenScopes && this._settings.defaultAccessTokenScopes.length > 0) {
|
if (this._settings.defaultAccessTokenScopes && this._settings.defaultAccessTokenScopes.length > 0) {
|
||||||
|
|
@ -130,7 +155,16 @@ class MsalAuthorizeService implements AuthorizeService {
|
||||||
if (this._settings.defaultAccessTokenScopes?.length > 0) {
|
if (this._settings.defaultAccessTokenScopes?.length > 0) {
|
||||||
// This provisions the token as part of the sign-in flow eagerly so that is already in the cache
|
// This provisions the token as part of the sign-in flow eagerly so that is already in the cache
|
||||||
// when the app asks for it.
|
// when the app asks for it.
|
||||||
await this._msalApplication.acquireTokenSilent(request);
|
const account = this.getAccount();
|
||||||
|
if (!account) {
|
||||||
|
return this.error("No account to get tokens for.");
|
||||||
|
}
|
||||||
|
const silentRequest = {
|
||||||
|
redirectUri: request.redirectUri,
|
||||||
|
account: account,
|
||||||
|
scopes: request.scopes,
|
||||||
|
};
|
||||||
|
await this._msalApplication.acquireTokenSilent(silentRequest);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return this.error(e.errorMessage);
|
return this.error(e.errorMessage);
|
||||||
|
|
@ -142,33 +176,45 @@ class MsalAuthorizeService implements AuthorizeService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async signInCore(request: Msal.AuthenticationParameters): Promise<Msal.AuthResponse | Msal.AuthError | undefined> {
|
async signInCore(request: Msal.AuthorizationUrlRequest): Promise<Msal.AuthenticationResult | Msal.AuthError | undefined> {
|
||||||
if (this._settings.loginMode.toLowerCase() === "redirect") {
|
const loginMode = this._settings.loginMode.toLowerCase();
|
||||||
try {
|
if (loginMode === 'redirect') {
|
||||||
this._msalApplication.loginRedirect(request);
|
return this.signInWithRedirect(request);
|
||||||
} catch (e) {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
try {
|
return this.signInWithPopup(request);
|
||||||
return await this._msalApplication.loginPopup(request);
|
}
|
||||||
} catch (e) {
|
}
|
||||||
// If the user explicitly cancelled the pop-up, avoid performing a redirect.
|
|
||||||
if (this.isMsalError(e) && e.errorCode !== ClientAuthErrorMessage.userCancelledError.code) {
|
private async signInWithRedirect(request: Msal.RedirectRequest) {
|
||||||
try {
|
try {
|
||||||
this._msalApplication.loginRedirect(request);
|
return await this._msalApplication.loginRedirect(request);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
return e;
|
|
||||||
}
|
private async signInWithPopup(request: Msal.PopupRequest) {
|
||||||
|
try {
|
||||||
|
return await this._msalApplication.loginPopup(request);
|
||||||
|
} catch (e) {
|
||||||
|
// If the user explicitly cancelled the pop-up, avoid performing a redirect.
|
||||||
|
if (this.isMsalError(e) && e.errorCode !== Msal.BrowserAuthErrorMessage.userCancelledError.code) {
|
||||||
|
this.signInWithRedirect(request);
|
||||||
|
} else {
|
||||||
|
return e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
completeSignIn() {
|
async completeSignIn() {
|
||||||
return this._callbackPromise;
|
// Make sure that the redirect handler has completed execution before
|
||||||
|
// completing sign in.
|
||||||
|
await this._redirectCallback;
|
||||||
|
const account = this.getAccount();
|
||||||
|
if (account) {
|
||||||
|
return this.success(account);
|
||||||
|
}
|
||||||
|
return this.operationCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
async signOut(state: any) {
|
async signOut(state: any) {
|
||||||
|
|
@ -241,7 +287,7 @@ class MsalAuthorizeService implements AuthorizeService {
|
||||||
// msal.js doesn't support the state parameter on logout flows, which forces us to shim our own logout state.
|
// msal.js doesn't support the state parameter on logout flows, which forces us to shim our own logout state.
|
||||||
// The format then is different, as msal follows the pattern state=<<guid>>|<<user_state>> and our format
|
// The format then is different, as msal follows the pattern state=<<guid>>|<<user_state>> and our format
|
||||||
// simple uses <<base64urlIdentifier>>.
|
// simple uses <<base64urlIdentifier>>.
|
||||||
const appState = !isLogout ? this._msalApplication.getAccountState(state[0]) : state[0];
|
const appState = !isLogout ? this.getAccountState(state[0]) : state[0];
|
||||||
const stateKey = `${AuthenticationService._infrastructureKey}.AuthorizeService.${appState}`;
|
const stateKey = `${AuthenticationService._infrastructureKey}.AuthorizeService.${appState}`;
|
||||||
const stateString = sessionStorage.getItem(stateKey);
|
const stateString = sessionStorage.getItem(stateKey);
|
||||||
if (stateString) {
|
if (stateString) {
|
||||||
|
|
@ -262,37 +308,35 @@ class MsalAuthorizeService implements AuthorizeService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createCallbackResult(callbackUrl: string): Promise<AuthenticationResult> {
|
async initializeMsalHandler() {
|
||||||
// msal.js requires a callback to be registered during app initialization to handle redirect flows.
|
this._redirectCallback = this._msalApplication.handleRedirectPromise().then(
|
||||||
// To map that behavior to our API we register a callback early and store the result of that callback
|
(result: Msal.AuthenticationResult | null) => this.handleResult(result)
|
||||||
// as a promise on an instance field to be able to serve the state back to the main app.
|
).catch((error: any) => {
|
||||||
const promiseFactory = (resolve: (result: Msal.AuthResponse) => void, reject: (error: Msal.AuthError) => void): void => {
|
if (this.isMsalError(error)) {
|
||||||
this._msalApplication.handleRedirectCallback(
|
return this.error(error.errorMessage);
|
||||||
authenticationResponse => {
|
} else {
|
||||||
resolve(authenticationResponse);
|
return this.error(error);
|
||||||
},
|
}
|
||||||
authenticationError => {
|
})
|
||||||
reject(authenticationError);
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
private handleResult(result: Msal.AuthenticationResult | null) {
|
||||||
// Evaluate the promise to capture any authentication errors
|
if (result != null) {
|
||||||
await new Promise<Msal.AuthResponse>(promiseFactory);
|
this._account = result.account;
|
||||||
// See https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki/FAQs#q6-how-to-avoid-page-reloads-when-acquiring-and-renewing-tokens-silently
|
return this.success(result.state);
|
||||||
if (window !== window.parent && !window.opener) {
|
} else {
|
||||||
return this.operationCompleted();
|
return this.operationCompleted();
|
||||||
} else {
|
}
|
||||||
const state = await this.retrieveState(callbackUrl);
|
}
|
||||||
return this.success(state);
|
|
||||||
}
|
private getAccountState(state: string) {
|
||||||
} catch (e) {
|
if (state) {
|
||||||
if (this.isMsalError(e)) {
|
const splitIndex = state.indexOf("|");
|
||||||
return this.error(e.errorMessage);
|
if (splitIndex > -1 && splitIndex + 1 < state.length) {
|
||||||
} else {
|
return state.substring(splitIndex + 1);
|
||||||
return this.error(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
private isMsalError(resultOrError: any): resultOrError is Msal.AuthError {
|
private isMsalError(resultOrError: any): resultOrError is Msal.AuthError {
|
||||||
|
|
@ -319,14 +363,15 @@ class MsalAuthorizeService implements AuthorizeService {
|
||||||
export class AuthenticationService {
|
export class AuthenticationService {
|
||||||
|
|
||||||
static _infrastructureKey = 'Microsoft.Authentication.WebAssembly.Msal';
|
static _infrastructureKey = 'Microsoft.Authentication.WebAssembly.Msal';
|
||||||
static _initialized = false;
|
static _initialized: Promise<void>;
|
||||||
static instance: MsalAuthorizeService;
|
static instance: MsalAuthorizeService;
|
||||||
|
|
||||||
public static async init(settings: AuthorizeServiceConfiguration) {
|
public static async init(settings: AuthorizeServiceConfiguration) {
|
||||||
if (!AuthenticationService._initialized) {
|
if (!AuthenticationService._initialized) {
|
||||||
AuthenticationService._initialized = true;
|
|
||||||
AuthenticationService.instance = new MsalAuthorizeService(settings);
|
AuthenticationService.instance = new MsalAuthorizeService(settings);
|
||||||
|
AuthenticationService._initialized = AuthenticationService.instance.initializeMsalHandler();
|
||||||
}
|
}
|
||||||
|
return AuthenticationService._initialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getUser() {
|
public static getUser() {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,6 @@
|
||||||
"webpack-cli": "^3.3.10"
|
"webpack-cli": "^3.3.10"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"msal": "^1.2.1"
|
"@azure/msal-browser": "^2.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,20 @@
|
||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@azure/msal-browser@^2.0.0":
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-2.0.0.tgz#09eb3ed2112bdcd11c751f1c0b9cec588b49b8c6"
|
||||||
|
integrity sha512-0L4XksaXmtl870bQTPxbHCkxMEMmSbsgkkVpb6bvXg8ngOLWnkxvV6tstj84JtQHcJPjNYkYY41jgBQxgC4/KQ==
|
||||||
|
dependencies:
|
||||||
|
"@azure/msal-common" "1.0.0"
|
||||||
|
|
||||||
|
"@azure/msal-common@1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-1.0.0.tgz#421f4859e6cb68cfacb03bb2c6c873efdffc76f0"
|
||||||
|
integrity sha512-l/+1Z9kQWLAlMwJ/c3MGhy4ujtEAK/3CMUaUXHvjUIsQknLFRb9+b3id5YSuToPfAvdUkAQGDZiQXosv1I+eLA==
|
||||||
|
dependencies:
|
||||||
|
debug "^4.1.1"
|
||||||
|
|
||||||
"@webassemblyjs/ast@1.8.5":
|
"@webassemblyjs/ast@1.8.5":
|
||||||
version "1.8.5"
|
version "1.8.5"
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359"
|
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359"
|
||||||
|
|
@ -690,6 +704,13 @@ debug@^2.2.0, debug@^2.3.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "2.0.0"
|
ms "2.0.0"
|
||||||
|
|
||||||
|
debug@^4.1.1:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
|
||||||
|
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
|
||||||
|
dependencies:
|
||||||
|
ms "^2.1.1"
|
||||||
|
|
||||||
decamelize@^1.2.0:
|
decamelize@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||||
|
|
@ -1667,12 +1688,10 @@ ms@2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||||
|
|
||||||
msal@^1.2.1:
|
ms@^2.1.1:
|
||||||
version "1.2.1"
|
version "2.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/msal/-/msal-1.2.1.tgz#08133e37ab0b9741866c89a3fadc55aadb980723"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||||
integrity sha512-Zo28eyRtT/Un+zcpMfPtTPD+eo/OqzsRER0k5dyk8Mje/K1oLlaEOAgZHlJs59Y2xyuVg8OrcKqSn/1MeNjZYw==
|
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||||
dependencies:
|
|
||||||
tslib "^1.9.3"
|
|
||||||
|
|
||||||
nan@^2.12.1:
|
nan@^2.12.1:
|
||||||
version "2.14.0"
|
version "2.14.0"
|
||||||
|
|
@ -2485,7 +2504,7 @@ ts-loader@^6.2.1:
|
||||||
micromatch "^4.0.0"
|
micromatch "^4.0.0"
|
||||||
semver "^6.0.0"
|
semver "^6.0.0"
|
||||||
|
|
||||||
tslib@^1.9.0, tslib@^1.9.3:
|
tslib@^1.9.0:
|
||||||
version "1.10.0"
|
version "1.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
||||||
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
|
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Microsoft.Authentication.WebAssembly.Msal
|
namespace Microsoft.Authentication.WebAssembly.Msal
|
||||||
{
|
{
|
||||||
|
|
@ -48,5 +49,10 @@ namespace Microsoft.Authentication.WebAssembly.Msal
|
||||||
/// Gets or sets whether or not to navigate to the login request url after a successful login.
|
/// Gets or sets whether or not to navigate to the login request url after a successful login.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool NavigateToLoginRequestUrl { get; set; } = false;
|
public bool NavigateToLoginRequestUrl { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the set of known authority host names for the application.
|
||||||
|
/// </summary>
|
||||||
|
public IList<string> KnownAuthorities { get; set; } = new List<string>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue