wangzhibo
2026-05-09 53418655c061ac26f66058253f39200aced1ab1d
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
/*
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */
 
import { CustomAuthBrowserConfiguration } from "../../configuration/CustomAuthConfiguration.js";
import { SignOutResult } from "./result/SignOutResult.js";
import { GetAccessTokenResult } from "./result/GetAccessTokenResult.js";
import { CustomAuthSilentCacheClient } from "../interaction_client/CustomAuthSilentCacheClient.js";
import { NoCachedAccountFoundError } from "../../core/error/NoCachedAccountFoundError.js";
import { DefaultScopes } from "../../CustomAuthConstants.js";
import { AccessTokenRetrievalInputs } from "../../CustomAuthActionInputs.js";
import {
    AccountInfo,
    CommonSilentFlowRequest,
    Constants,
    Logger,
    TokenClaims,
} from "@azure/msal-common/browser";
import { SilentRequest } from "../../../request/SilentRequest.js";
import * as ArgumentValidator from "../../core/utils/ArgumentValidator.js";
 
/*
 * Account information.
 */
export class CustomAuthAccountData {
    constructor(
        private readonly account: AccountInfo,
        private readonly config: CustomAuthBrowserConfiguration,
        private readonly cacheClient: CustomAuthSilentCacheClient,
        private readonly logger: Logger,
        private readonly correlationId: string
    ) {
        ArgumentValidator.ensureArgumentIsNotEmptyString(
            "correlationId",
            correlationId
        );
        ArgumentValidator.ensureArgumentIsNotNullOrUndefined(
            "account",
            account,
            correlationId
        );
    }
 
    /**
     * This method triggers a sign-out operation,
     * which removes the current account info and its tokens from browser cache.
     * If sign-out successfully, redirect the page to postLogoutRedirectUri if provided in the configuration.
     * @returns {Promise<SignOutResult>} The result of the SignOut operation.
     */
    async signOut(): Promise<SignOutResult> {
        try {
            const currentAccount = this.cacheClient.getCurrentAccount(
                this.correlationId
            );
 
            if (!currentAccount) {
                throw new NoCachedAccountFoundError(this.correlationId);
            }
 
            this.logger.verbose("Signing out user", this.correlationId);
 
            await this.cacheClient.logout({
                correlationId: this.correlationId,
                account: currentAccount,
            });
 
            this.logger.verbose("User signed out", this.correlationId);
 
            return new SignOutResult();
        } catch (error) {
            this.logger.errorPii(
                `An error occurred during sign out: '${error}'`,
                this.correlationId
            );
 
            return SignOutResult.createWithError(error);
        }
    }
 
    getAccount(): AccountInfo {
        return this.account;
    }
 
    /**
     * Gets the raw id-token of current account.
     * Idtoken is only issued if openid scope is present in the scopes parameter when requesting for tokens,
     * otherwise will return undefined from the response.
     * @returns {string|undefined} The account id-token.
     */
    getIdToken(): string | undefined {
        return this.account.idToken;
    }
 
    /**
     * Gets the id token claims extracted from raw IdToken of current account.
     * @returns {AuthTokenClaims|undefined} The token claims.
     */
    getClaims(): AuthTokenClaims | undefined {
        return this.account.idTokenClaims;
    }
 
    /**
     * Gets the access token of current account from browser cache if it is not expired,
     * otherwise renew the token using cached refresh token if valid.
     * If no refresh token is found or it is expired, then throws error.
     * @param {AccessTokenRetrievalInputs} accessTokenRetrievalInputs - The inputs for retrieving the access token.
     * @returns {Promise<GetAccessTokenResult>} The result of the operation.
     */
    async getAccessToken(
        accessTokenRetrievalInputs: AccessTokenRetrievalInputs
    ): Promise<GetAccessTokenResult> {
        try {
            ArgumentValidator.ensureArgumentIsNotNullOrUndefined(
                "accessTokenRetrievalInputs",
                accessTokenRetrievalInputs,
                this.correlationId
            );
 
            if (accessTokenRetrievalInputs.claims) {
                ArgumentValidator.ensureArgumentIsJSONString(
                    "accessTokenRetrievalInputs.claims",
                    accessTokenRetrievalInputs.claims,
                    this.correlationId
                );
            }
 
            this.logger.verbose("Getting current account.", this.correlationId);
 
            const currentAccount = this.cacheClient.getCurrentAccount(
                this.account.username
            );
 
            if (!currentAccount) {
                throw new NoCachedAccountFoundError(this.correlationId);
            }
 
            this.logger.verbose("Getting access token.", this.correlationId);
 
            const newScopes =
                accessTokenRetrievalInputs.scopes &&
                accessTokenRetrievalInputs.scopes.length > 0
                    ? accessTokenRetrievalInputs.scopes
                    : [...DefaultScopes];
            const commonSilentFlowRequest = this.createCommonSilentFlowRequest(
                currentAccount,
                accessTokenRetrievalInputs.forceRefresh,
                newScopes,
                accessTokenRetrievalInputs.claims
            );
            const result = await this.cacheClient.acquireToken(
                commonSilentFlowRequest
            );
 
            this.logger.verbose(
                "Successfully got access token from cache.",
                this.correlationId
            );
 
            return new GetAccessTokenResult(result);
        } catch (error) {
            this.logger.error(
                "Failed to get access token from cache.",
                this.correlationId
            );
 
            return GetAccessTokenResult.createWithError(error);
        }
    }
 
    private createCommonSilentFlowRequest(
        accountInfo: AccountInfo,
        forceRefresh: boolean = false,
        requestScopes: Array<string>,
        claims?: string
    ): CommonSilentFlowRequest {
        const silentRequest: SilentRequest = {
            authority: this.config.auth.authority,
            correlationId: this.correlationId,
            scopes: requestScopes || [],
            account: accountInfo,
            forceRefresh: forceRefresh || false,
            storeInCache: {
                idToken: true,
                accessToken: true,
                refreshToken: true,
            },
            ...(claims && { claims: claims }),
        };
 
        return {
            ...silentRequest,
            authenticationScheme: Constants.AuthenticationScheme.BEARER,
        } as CommonSilentFlowRequest;
    }
}
 
export type AuthTokenClaims = TokenClaims & {
    [key: string]: string | number | string[] | object | undefined | unknown;
};