Estructura inicial del proyecto

This commit is contained in:
2026-06-02 16:57:08 +00:00
commit 8b306b9afc
9864 changed files with 1435687 additions and 0 deletions

View File

@ -0,0 +1,90 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import {
createClientConfigurationError,
ClientConfigurationErrorCodes,
} from "../error/ClientConfigurationError.js";
import { HeaderNames } from "../utils/Constants.js";
type WWWAuthenticateChallenges = {
nonce?: string;
};
type AuthenticationInfoChallenges = {
nextnonce?: string;
};
/**
* This is a helper class that parses supported HTTP response authentication headers to extract and return
* header challenge values that can be used outside the basic authorization flows.
*/
export class AuthenticationHeaderParser {
private headers: Record<string, string>;
constructor(headers: Record<string, string>) {
this.headers = headers;
}
/**
* This method parses the SHR nonce value out of either the Authentication-Info or WWW-Authenticate authentication headers.
* @returns
*/
getShrNonce(): string {
// Attempt to parse nonce from Authentiacation-Info
const authenticationInfo = this.headers[HeaderNames.AuthenticationInfo];
if (authenticationInfo) {
const authenticationInfoChallenges =
this.parseChallenges<AuthenticationInfoChallenges>(
authenticationInfo
);
if (authenticationInfoChallenges.nextnonce) {
return authenticationInfoChallenges.nextnonce;
}
throw createClientConfigurationError(
ClientConfigurationErrorCodes.invalidAuthenticationHeader
);
}
// Attempt to parse nonce from WWW-Authenticate
const wwwAuthenticate = this.headers[HeaderNames.WWWAuthenticate];
if (wwwAuthenticate) {
const wwwAuthenticateChallenges =
this.parseChallenges<WWWAuthenticateChallenges>(
wwwAuthenticate
);
if (wwwAuthenticateChallenges.nonce) {
return wwwAuthenticateChallenges.nonce;
}
throw createClientConfigurationError(
ClientConfigurationErrorCodes.invalidAuthenticationHeader
);
}
// If neither header is present, throw missing headers error
throw createClientConfigurationError(
ClientConfigurationErrorCodes.missingNonceAuthenticationHeader
);
}
/**
* Parses an HTTP header's challenge set into a key/value map.
* @param header
* @returns
*/
private parseChallenges<T>(header: string): T {
const schemeSeparator = header.indexOf(" ");
const challenges = header.substr(schemeSeparator + 1).split(",");
const challengeMap = {} as T;
challenges.forEach((challenge: string) => {
const [key, value] = challenge.split("=");
// Remove escaped quotation marks (', ") from challenge string to keep only the challenge value
challengeMap[key] = unescape(value.replace(/['"]+/g, ""));
});
return challengeMap;
}
}

View File

@ -0,0 +1,150 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { AuthenticationScheme, HttpMethod } from "../utils/Constants.js";
import type { AzureCloudOptions } from "../config/ClientConfiguration.js";
import { StringDict } from "../utils/MsalTypes.js";
import { StoreInCache } from "./StoreInCache.js";
import { ShrOptions } from "../crypto/SignedHttpRequest.js";
import { createClientAuthError } from "../error/ClientAuthError.js";
import * as ClientAuthErrorCodes from "../error/ClientAuthErrorCodes.js";
/**
* BaseAuthRequest
*/
export type BaseAuthRequest = {
/**
* URL of the authority, the security token service (STS) from which MSAL will acquire tokens. Defaults to https://login.microsoftonline.com/common. If using the same authority for all request, authority should set on client application object and not request, to avoid resolving authority endpoints multiple times.
*/
authority: string;
/**
* Unique GUID set per request to trace a request end-to-end for telemetry purposes.
*/
correlationId: string;
/**
* Array of scopes the application is requesting access to.
*/
scopes: Array<string>;
/**
* The type of token retrieved. Defaults to "Bearer". Can also be type "pop" or "SSH".
*/
authenticationScheme?: AuthenticationScheme;
/**
* A stringified claims request which will be added to all /authorize and /token calls
*/
claims?: string;
/**
* A stringified claims object which will be added to a Signed HTTP Request
*/
shrClaims?: string;
/**
* A server-generated timestamp that has been encrypted and base64URL encoded, which will be added to a Signed HTTP Request.
*/
shrNonce?: string;
/**
* An object containing options for the Signed HTTP Request
*/
shrOptions?: ShrOptions;
/**
* HTTP Request type used to request data from the resource (i.e. "GET", "POST", etc.). Used for proof-of-possession flows.
*/
resourceRequestMethod?: string;
/**
* URI that token will be used for. Used for proof-of-possession flows.
*/
resourceRequestUri?: string;
/**
* A stringified JSON Web Key representing a public key that can be signed by an SSH certificate.
*/
sshJwk?: string;
/**
* Key ID that uniquely identifies the SSH public key mentioned above.
*/
sshKid?: string;
/**
* Convenience string enums for users to provide public/sovereign cloud ids
*/
azureCloudOptions?: AzureCloudOptions;
/**
* Maximum allowed age, in milliseconds, of the user's authentication before a new sign-in is required.
*/
maxAge?: number;
/**
* Object containing boolean values indicating whether to store tokens in the cache or not (default is true)
*/
storeInCache?: StoreInCache;
/**
* Scenario id to track custom user prompts
*/
scenarioId?: string;
/**
* Key ID to identify the public key for PoP token request
*/
popKid?: string;
/**
* Embedded client id. When specified, broker client id (brk_client_id) and redirect uri (brk_redirect_uri) params are set with values from the config, overriding the corresponding extra parameters, if present.
*/
embeddedClientId?: string;
/**
* HTTP method to use for the /authorize request. Defaults to GET, but can be set to POST if the request requires body parameters.
*/
httpMethod?: HttpMethod;
/**
* Resource parameter to be sent with the request. Used for MCP flows.
*/
resource?: string;
/**
* When true and a brokered flow is in effect—i.e., when a broker client id (brk_client_id), typically derived from embeddedClientId or other broker parameters, is included in the request—clientCapabilities from configuration will be excluded from claims. Has no effect when brk_client_id is not present (non-brokered flows).
*/
skipBrokerClaims?: boolean;
/**
* String to string map of custom query parameters added to outgoing token service requests. Unless the parameter is only supported on query strings use extraParameters instead
*/
extraQueryParameters?: StringDict;
/**
* String to string map of custom parameters added to outgoing token service requests.
*/
extraParameters?: StringDict;
};
/**
* Helper to enforce resource parameter presence in token requests when isMcp is set in the configuration.
* If resource parameter is set in both the request and in extraQueryParameters or extraParameters, an error will be thrown.
* This is used for MCP flows.
* @param isMcp - Flag indicating if application is an MCP app, from configuration
* @param request - Auth request
*/
export function enforceResourceParameter(
isMcp: boolean,
request: Partial<BaseAuthRequest>
): void {
if (!isMcp) {
return;
}
if (
request.resource &&
(containsResourceParam(request.extraParameters) ||
containsResourceParam(request.extraQueryParameters))
) {
throw createClientAuthError(
ClientAuthErrorCodes.misplacedResourceParam
);
}
if (!request.resource) {
throw createClientAuthError(
ClientAuthErrorCodes.resourceParameterRequired
);
}
}
function containsResourceParam(params?: StringDict): boolean {
if (!params) {
return false;
}
return Object.prototype.hasOwnProperty.call(params, "resource");
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { BaseAuthRequest } from "./BaseAuthRequest.js";
import { CcsCredential } from "../account/CcsCredential.js";
/**
* Request object passed by user to acquire a token from the server exchanging a valid authorization code (second leg of OAuth2.0 Authorization Code flow)
*/
export type CommonAuthorizationCodeRequest = BaseAuthRequest & {
/**
* The authorization_code that the user acquired in the first leg of the flow.
*/
code: string;
/**
* The redirect URI of your app, where the authority will redirect to after the user inputs credentials and consents. It must exactly match one of the redirect URIs you registered in the portal
*/
redirectUri: string;
/**
* The same code_verifier that was used to obtain the authorization_code. Required if PKCE was used in the authorization code grant request.For more information, see the PKCE RFC: https://tools.ietf.org/html/rfc7636
*/
codeVerifier?: string;
/**
* Enables the acquisition of a SPA authorization code (confidential clients only).
*/
enableSpaAuthorizationCode?: boolean;
/**
* Encoded client_info returned with the authorization code, used to bind the token response to a specific account.
*/
clientInfo?: string;
/**
* Credential used to populate the CCS (Cache Credential Service) header.
*/
ccsCredential?: CcsCredential;
};

View File

@ -0,0 +1,76 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { ResponseMode } from "../utils/Constants.js";
import { BaseAuthRequest } from "./BaseAuthRequest.js";
import { AccountInfo } from "../account/AccountInfo.js";
/**
* Request object passed by user to retrieve a Code from the server (first leg of authorization code grant flow)
*/
export type CommonAuthorizationUrlRequest = BaseAuthRequest & {
/**
* The redirect URI where authentication responses can be received by your application. It must exactly match one of the redirect URIs registered in the Azure portal.
*/
redirectUri: string;
/**
* Specifies the method that should be used to send the authentication result to your app. Can be query, form_post, or fragment. If no value is passed in, it defaults to query.
*/
responseMode: ResponseMode;
/**
* AccountInfo obtained from a getAccount API. Will be used in certain scenarios to generate login_hint if both loginHint and sid params are not provided.
*/
account?: AccountInfo;
/**
* JSON Web Key used when constructing Encrypted Authorize Response (EAR) parameters.
*/
earJwk?: string;
/**
* Used to secure authorization code grant via Proof of Key for Code Exchange (PKCE). For more information, see the PKCE RCF:https://tools.ietf.org/html/rfc7636
*/
codeChallenge?: string;
/**
* The method used to encode the code verifier for the code challenge parameter. Can be "plain" or "S256". If excluded, code challenge is assumed to be plaintext. For more information, see the PKCE RCF: https://tools.ietf.org/html/rfc7636
*/
codeChallengeMethod?: string;
/**
* Provides a hint about the tenant or domain that the user should use to sign in. The value of the domain hint is a registered domain for the tenant.
*/
domainHint?: string;
/**
* Scopes for a different resource when the user needs consent upfront.
*/
extraScopesToConsent?: Array<string>;
/**
* Can be used to pre-fill the username/email address field of the sign-in page for the user, if you know the username/email address ahead of time. Can also be the string value extracted from the login_hint claim on an idToken obtained previously to provide SSO.
*/
loginHint?: string;
/**
* A value included in the request that is returned in the id token. A randomly generated unique value is typically used to mitigate replay attacks.
*/
nonce: string;
/**
* Indicates the type of user interaction that is required.
* login: will force the user to enter their credentials on that request, negating single-sign on
* none: will ensure that the user isn't presented with any interactive prompt. if request can't be completed via single-sign on, the endpoint will return an interaction_required error
* consent: will the trigger the OAuth consent dialog after the user signs in, asking the user to grant permissions to the app
* select_account: will interrupt single sign-=on providing account selection experience listing all the accounts in session or any remembered accounts or an option to choose to use a different account
* create: will direct the user to the account creation experience instead of the log in experience
* no_session: will not read existing session token when authenticating the user. Upon user being successfully authenticated, EVO wont create a new session for the user. FOR INTERNAL USE ONLY.
*/
prompt?: string;
/**
* Session ID, unique identifier for the session. Available as an optional claim on ID tokens. Use login_hint optional claim provided on loginHint paramter instead, when available.
*/
sid?: string;
/**
* A value included in the request that is also returned in the token response. A randomly generated unique value is typically used for preventing cross site request forgery attacks. The state is also used to encode information about the user's state in the app before the authentication request occurred. For security and privacy reasons, we do not recommend putting URLs or other sensitive data directly in the state parameter. Instead, use a key or identifier that corresponds to data stored in browser storage (e.g., localStorage, sessionStorage), allowing your app to securely reference the necessary data after authentication.
*/
state: string;
/**
* Indicates whether this authorization request is being initiated by a platform authentication broker instead of a standard web flow.
*/
platformBroker?: boolean;
};

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { AccountInfo } from "../account/AccountInfo.js";
import { StringDict } from "../utils/MsalTypes.js";
/**
* CommonEndSessionRequest
*/
export type CommonEndSessionRequest = {
/**
* Unique GUID set per request to trace a request end-to-end for telemetry purposes.
*/
correlationId: string;
/**
* Account object that will be logged out of. All tokens tied to this account will be cleared.
*/
account?: AccountInfo | null;
/**
* URI to navigate to after logout page.
*/
postLogoutRedirectUri?: string | null;
/**
* ID Token used by B2C to validate logout if required by the policy
*/
idTokenHint?: string;
/**
* A value included in the request to the logout endpoint which will be returned in the query string upon post logout redirection. For security and privacy reasons, we do not recommend putting URLs or other sensitive data directly in the state parameter. Instead, use a key or identifier that corresponds to data stored in browser storage (e.g., localStorage, sessionStorage), allowing your app to securely reference the necessary data after logout.
*/
state?: string;
/**
* A string that specifies the account that is being logged out in order to skip the server account picker on logout
*/
logoutHint?: string;
/**
* String to string map of custom query parameters added to the /authorize call
*/
extraQueryParameters?: StringDict;
};

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { BaseAuthRequest } from "./BaseAuthRequest.js";
import { CcsCredential } from "../account/CcsCredential.js";
/**
* CommonRefreshTokenRequest
*/
export type CommonRefreshTokenRequest = BaseAuthRequest & {
/**
* A refresh token returned from a previous request to the Identity provider.
*/
refreshToken: string;
/**
* Credential used to populate the CCS (Cache Credential Service) header.
*/
ccsCredential?: CcsCredential;
/**
* Force MSAL to cache a refresh token flow response when there is no account in the cache. Used for migration scenarios.
*/
forceCache?: boolean;
/**
* Redirect URI to send with the refresh token request.
*/
redirectUri?: string;
};

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { AccountInfo } from "../account/AccountInfo.js";
import { BaseAuthRequest } from "./BaseAuthRequest.js";
/**
* SilentFlow parameters passed by the user to retrieve credentials silently
*/
export type CommonSilentFlowRequest = BaseAuthRequest & {
/**
* Account object to lookup the credentials
*/
account: AccountInfo;
/**
* Skip cache lookup and forces network call(s) to get fresh tokens
*/
forceRefresh: boolean;
/**
* RedirectUri registered on the app registration - only required in brokering scenarios
*/
redirectUri?: string;
/**
* If refresh token will expire within the configured value, consider it already expired. Used to pre-emptively invoke interaction when cached refresh token is close to expiry.
*/
refreshTokenExpirationOffsetSeconds?: number;
};

View File

@ -0,0 +1,26 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { StringDict } from "../utils/MsalTypes.js";
export type NativeRequest = {
clientId: string;
authority: string;
correlationId: string;
redirectUri: string;
scopes: Array<string>;
claims?: string;
authenticationScheme?: string;
resourceRequestMethod?: string;
resourceRequestUri?: string;
shrNonce?: string;
accountId?: string;
forceRefresh?: boolean;
resource?: string;
extraParameters?: StringDict;
extraScopesToConsent?: Array<string>;
loginHint?: string;
prompt?: string;
};

View File

@ -0,0 +1,10 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
export type NativeSignOutRequest = {
clientId: string;
accountId: string;
correlationId: string;
};

View File

@ -0,0 +1,668 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import * as Constants from "../utils/Constants.js";
import * as AADServerParamKeys from "../constants/AADServerParamKeys.js";
import { ScopeSet } from "./ScopeSet.js";
import {
createClientConfigurationError,
ClientConfigurationErrorCodes,
} from "../error/ClientConfigurationError.js";
import { StringDict } from "../utils/MsalTypes.js";
import {
ApplicationTelemetry,
LibraryInfo,
} from "../config/ClientConfiguration.js";
import { ServerTelemetryManager } from "../telemetry/server/ServerTelemetryManager.js";
import { ClientInfo } from "../account/ClientInfo.js";
import { IPerformanceClient } from "../telemetry/performance/IPerformanceClient.js";
import { StringUtils } from "../utils/StringUtils.js";
export function instrumentBrokerParams(
parameters: Map<string, string>,
correlationId?: string,
performanceClient?: IPerformanceClient
): void {
if (!correlationId) {
return;
}
const clientId = parameters.get(AADServerParamKeys.CLIENT_ID);
if (clientId && parameters.has(AADServerParamKeys.BROKER_CLIENT_ID)) {
performanceClient?.addFields(
{
embeddedClientId: clientId,
embeddedRedirectUri: parameters.get(
AADServerParamKeys.REDIRECT_URI
),
},
correlationId
);
}
}
/**
* Add the given response_type
* @param parameters
* @param responseType
*/
export function addResponseType(
parameters: Map<string, string>,
responseType: Constants.OAuthResponseType
): void {
parameters.set(AADServerParamKeys.RESPONSE_TYPE, responseType);
}
/**
* add response_mode. defaults to query.
* @param responseMode
*/
export function addResponseMode(
parameters: Map<string, string>,
responseMode?: Constants.ResponseMode
): void {
parameters.set(
AADServerParamKeys.RESPONSE_MODE,
responseMode ? responseMode : Constants.ResponseMode.QUERY
);
}
/**
* Add flag to indicate STS should attempt to use WAM if available
*/
export function addNativeBroker(parameters: Map<string, string>): void {
parameters.set(AADServerParamKeys.NATIVE_BROKER, "1");
}
/**
* add scopes. set addOidcScopes to false to prevent default scopes in non-user scenarios
* @param scopeSet
* @param addOidcScopes
*/
export function addScopes(
parameters: Map<string, string>,
scopes: string[],
addOidcScopes: boolean = true,
defaultScopes: Array<string> = Constants.OIDC_DEFAULT_SCOPES
): void {
// Always add openid to the scopes when adding OIDC scopes
if (
addOidcScopes &&
!defaultScopes.includes("openid") &&
!scopes.includes("openid")
) {
defaultScopes.push("openid");
}
const requestScopes = addOidcScopes
? [...(scopes || []), ...defaultScopes]
: scopes || [];
const scopeSet = new ScopeSet(requestScopes);
parameters.set(AADServerParamKeys.SCOPE, scopeSet.printScopes());
}
/**
* add clientId
* @param clientId
*/
export function addClientId(
parameters: Map<string, string>,
clientId: string
): void {
parameters.set(AADServerParamKeys.CLIENT_ID, clientId);
}
/**
* add redirect_uri
* @param redirectUri
*/
export function addRedirectUri(
parameters: Map<string, string>,
redirectUri: string
): void {
parameters.set(AADServerParamKeys.REDIRECT_URI, redirectUri);
}
/**
* add post logout redirectUri
* @param redirectUri
*/
export function addPostLogoutRedirectUri(
parameters: Map<string, string>,
redirectUri: string
): void {
parameters.set(AADServerParamKeys.POST_LOGOUT_URI, redirectUri);
}
/**
* add id_token_hint to logout request
* @param idTokenHint
*/
export function addIdTokenHint(
parameters: Map<string, string>,
idTokenHint: string
): void {
parameters.set(AADServerParamKeys.ID_TOKEN_HINT, idTokenHint);
}
/**
* add domain_hint
* @param domainHint
*/
export function addDomainHint(
parameters: Map<string, string>,
domainHint: string
): void {
parameters.set(AADServerParamKeys.DOMAIN_HINT, domainHint);
}
/**
* add login_hint
* @param loginHint
*/
export function addLoginHint(
parameters: Map<string, string>,
loginHint: string
): void {
parameters.set(AADServerParamKeys.LOGIN_HINT, loginHint);
}
/**
* Adds the CCS (Cache Credential Service) query parameter for login_hint
* @param loginHint
*/
export function addCcsUpn(
parameters: Map<string, string>,
loginHint: string
): void {
parameters.set(Constants.HeaderNames.CCS_HEADER, `UPN:${loginHint}`);
}
/**
* Adds the CCS (Cache Credential Service) query parameter for account object
* @param loginHint
*/
export function addCcsOid(
parameters: Map<string, string>,
clientInfo: ClientInfo
): void {
parameters.set(
Constants.HeaderNames.CCS_HEADER,
`Oid:${clientInfo.uid}@${clientInfo.utid}`
);
}
/**
* add sid
* @param sid
*/
export function addSid(parameters: Map<string, string>, sid: string): void {
parameters.set(AADServerParamKeys.SID, sid);
}
/**
* Adds claims to request parameters, conditionally excluding clientCapabilities
* when skipBrokerClaims is true and a brokered flow is in effect.
* @param parameters - The request parameters map
* @param claims - The claims string from the request
* @param clientCapabilities - The client capabilities from configuration
* @param skipBrokerClaims - When true and BROKER_CLIENT_ID is present, excludes clientCapabilities from claims
*/
export function addClaims(
parameters: Map<string, string>,
claims?: string,
clientCapabilities?: Array<string>,
skipBrokerClaims?: boolean
): void {
// Skip clientCapabilities if skipBrokerClaims is set to true and this is a brokered authentication flow
const configClaims =
skipBrokerClaims && parameters.has(AADServerParamKeys.BROKER_CLIENT_ID)
? undefined
: clientCapabilities;
if (
!StringUtils.isEmptyObj(claims) ||
(configClaims && configClaims.length > 0)
) {
const mergedClaims = addClientCapabilitiesToClaims(
claims,
configClaims
);
try {
JSON.parse(mergedClaims);
} catch (e) {
throw createClientConfigurationError(
ClientConfigurationErrorCodes.invalidClaims
);
}
parameters.set(AADServerParamKeys.CLAIMS, mergedClaims);
}
}
/**
* add correlationId
* @param correlationId
*/
export function addCorrelationId(
parameters: Map<string, string>,
correlationId: string
): void {
parameters.set(AADServerParamKeys.CLIENT_REQUEST_ID, correlationId);
}
/**
* add library info query params
* @param libraryInfo
*/
export function addLibraryInfo(
parameters: Map<string, string>,
libraryInfo: LibraryInfo
): void {
// Telemetry Info
parameters.set(AADServerParamKeys.X_CLIENT_SKU, libraryInfo.sku);
parameters.set(AADServerParamKeys.X_CLIENT_VER, libraryInfo.version);
if (libraryInfo.os) {
parameters.set(AADServerParamKeys.X_CLIENT_OS, libraryInfo.os);
}
if (libraryInfo.cpu) {
parameters.set(AADServerParamKeys.X_CLIENT_CPU, libraryInfo.cpu);
}
}
/**
* Add client telemetry parameters
* @param appTelemetry
*/
export function addApplicationTelemetry(
parameters: Map<string, string>,
appTelemetry: ApplicationTelemetry
): void {
if (appTelemetry?.appName) {
parameters.set(AADServerParamKeys.X_APP_NAME, appTelemetry.appName);
}
if (appTelemetry?.appVersion) {
parameters.set(AADServerParamKeys.X_APP_VER, appTelemetry.appVersion);
}
}
/**
* add prompt
* @param prompt
*/
export function addPrompt(
parameters: Map<string, string>,
prompt: string
): void {
parameters.set(AADServerParamKeys.PROMPT, prompt);
}
/**
* add state
* @param state
*/
export function addState(parameters: Map<string, string>, state: string): void {
if (state) {
parameters.set(AADServerParamKeys.STATE, state);
}
}
/**
* add nonce
* @param nonce
*/
export function addNonce(parameters: Map<string, string>, nonce: string): void {
parameters.set(AADServerParamKeys.NONCE, nonce);
}
/**
* add code_challenge and code_challenge_method
* - throw if either of them are not passed
* @param codeChallenge
* @param codeChallengeMethod
*/
export function addCodeChallengeParams(
parameters: Map<string, string>,
codeChallenge?: string,
codeChallengeMethod?: string
): void {
if (codeChallenge && codeChallengeMethod) {
parameters.set(AADServerParamKeys.CODE_CHALLENGE, codeChallenge);
parameters.set(
AADServerParamKeys.CODE_CHALLENGE_METHOD,
codeChallengeMethod
);
} else {
throw createClientConfigurationError(
ClientConfigurationErrorCodes.pkceParamsMissing
);
}
}
/**
* add the `authorization_code` passed by the user to exchange for a token
* @param code
*/
export function addAuthorizationCode(
parameters: Map<string, string>,
code: string
): void {
parameters.set(AADServerParamKeys.CODE, code);
}
/**
* add the `authorization_code` passed by the user to exchange for a token
* @param code
*/
export function addDeviceCode(
parameters: Map<string, string>,
code: string
): void {
parameters.set(AADServerParamKeys.DEVICE_CODE, code);
}
/**
* add the `refreshToken` passed by the user
* @param refreshToken
*/
export function addRefreshToken(
parameters: Map<string, string>,
refreshToken: string
): void {
parameters.set(AADServerParamKeys.REFRESH_TOKEN, refreshToken);
}
/**
* add the `code_verifier` passed by the user to exchange for a token
* @param codeVerifier
*/
export function addCodeVerifier(
parameters: Map<string, string>,
codeVerifier: string
): void {
parameters.set(AADServerParamKeys.CODE_VERIFIER, codeVerifier);
}
/**
* add client_secret
* @param clientSecret
*/
export function addClientSecret(
parameters: Map<string, string>,
clientSecret: string
): void {
parameters.set(AADServerParamKeys.CLIENT_SECRET, clientSecret);
}
/**
* add clientAssertion for confidential client flows
* @param clientAssertion
*/
export function addClientAssertion(
parameters: Map<string, string>,
clientAssertion: string
): void {
if (clientAssertion) {
parameters.set(AADServerParamKeys.CLIENT_ASSERTION, clientAssertion);
}
}
/**
* add clientAssertionType for confidential client flows
* @param clientAssertionType
*/
export function addClientAssertionType(
parameters: Map<string, string>,
clientAssertionType: string
): void {
if (clientAssertionType) {
parameters.set(
AADServerParamKeys.CLIENT_ASSERTION_TYPE,
clientAssertionType
);
}
}
/**
* add OBO assertion for confidential client flows
* @param clientAssertion
*/
export function addOboAssertion(
parameters: Map<string, string>,
oboAssertion: string
): void {
parameters.set(AADServerParamKeys.OBO_ASSERTION, oboAssertion);
}
/**
* add grant type
* @param grantType
*/
export function addRequestTokenUse(
parameters: Map<string, string>,
tokenUse: string
): void {
parameters.set(AADServerParamKeys.REQUESTED_TOKEN_USE, tokenUse);
}
/**
* add grant type
* @param grantType
*/
export function addGrantType(
parameters: Map<string, string>,
grantType: string
): void {
parameters.set(AADServerParamKeys.GRANT_TYPE, grantType);
}
/**
* add client info
*
*/
export function addClientInfo(parameters: Map<string, string>): void {
parameters.set(Constants.CLIENT_INFO, "1");
}
/**
* add clidata=1 to request to indicate client data support
*/
export function addCliData(parameters: Map<string, string>): void {
parameters.set(AADServerParamKeys.CLI_DATA, "1");
}
export function addInstanceAware(parameters: Map<string, string>): void {
if (!parameters.has(AADServerParamKeys.INSTANCE_AWARE)) {
parameters.set(AADServerParamKeys.INSTANCE_AWARE, "true");
}
}
/**
* Add extraParameters
* @param extraParams - String dictionary containing extra parameters to be added.
*/
export function addExtraParameters(
parameters: Map<string, string>,
extraParams: StringDict
): void {
Object.entries(extraParams).forEach(([key, value]) => {
if (!parameters.has(key) && value) {
parameters.set(key, value);
}
});
}
export function addClientCapabilitiesToClaims(
claims?: string,
clientCapabilities?: Array<string>
): string {
let mergedClaims: object;
// Parse provided claims into JSON object or initialize empty object
if (!claims) {
mergedClaims = {};
} else {
try {
mergedClaims = JSON.parse(claims);
} catch (e) {
throw createClientConfigurationError(
ClientConfigurationErrorCodes.invalidClaims
);
}
}
if (clientCapabilities && clientCapabilities.length > 0) {
if (
!mergedClaims.hasOwnProperty(
Constants.ClaimsRequestKeys.ACCESS_TOKEN
)
) {
// Add access_token key to claims object
mergedClaims[Constants.ClaimsRequestKeys.ACCESS_TOKEN] = {};
}
// Add xms_cc claim with provided clientCapabilities to access_token key
mergedClaims[Constants.ClaimsRequestKeys.ACCESS_TOKEN][
Constants.ClaimsRequestKeys.XMS_CC
] = {
values: clientCapabilities,
};
}
return JSON.stringify(mergedClaims);
}
/**
* adds `username` for Password Grant flow
* @param username
*/
export function addUsername(
parameters: Map<string, string>,
username: string
): void {
parameters.set(Constants.PasswordGrantConstants.username, username);
}
/**
* adds `password` for Password Grant flow
* @param password
*/
export function addPassword(
parameters: Map<string, string>,
password: string
): void {
parameters.set(Constants.PasswordGrantConstants.password, password);
}
/**
* add pop_jwk to query params
* @param cnfString
*/
export function addPopToken(
parameters: Map<string, string>,
cnfString: string
): void {
if (cnfString) {
parameters.set(
AADServerParamKeys.TOKEN_TYPE,
Constants.AuthenticationScheme.POP
);
parameters.set(AADServerParamKeys.REQ_CNF, cnfString);
}
}
/**
* add SSH JWK and key ID to query params
*/
export function addSshJwk(
parameters: Map<string, string>,
sshJwkString: string
): void {
if (sshJwkString) {
parameters.set(
AADServerParamKeys.TOKEN_TYPE,
Constants.AuthenticationScheme.SSH
);
parameters.set(AADServerParamKeys.REQ_CNF, sshJwkString);
}
}
/**
* add server telemetry fields
* @param serverTelemetryManager
*/
export function addServerTelemetry(
parameters: Map<string, string>,
serverTelemetryManager: ServerTelemetryManager
): void {
parameters.set(
AADServerParamKeys.X_CLIENT_CURR_TELEM,
serverTelemetryManager.generateCurrentRequestHeaderValue()
);
parameters.set(
AADServerParamKeys.X_CLIENT_LAST_TELEM,
serverTelemetryManager.generateLastRequestHeaderValue()
);
}
/**
* Adds parameter that indicates to the server that throttling is supported
*/
export function addThrottling(parameters: Map<string, string>): void {
parameters.set(
AADServerParamKeys.X_MS_LIB_CAPABILITY,
Constants.X_MS_LIB_CAPABILITY_VALUE
);
}
/**
* Adds logout_hint parameter for "silent" logout which prevent server account picker
*/
export function addLogoutHint(
parameters: Map<string, string>,
logoutHint: string
): void {
parameters.set(AADServerParamKeys.LOGOUT_HINT, logoutHint);
}
export function addBrokerParameters(
parameters: Map<string, string>,
brokerClientId: string,
brokerRedirectUri: string
): void {
if (!parameters.has(AADServerParamKeys.BROKER_CLIENT_ID)) {
parameters.set(AADServerParamKeys.BROKER_CLIENT_ID, brokerClientId);
}
if (!parameters.has(AADServerParamKeys.BROKER_REDIRECT_URI)) {
parameters.set(
AADServerParamKeys.BROKER_REDIRECT_URI,
brokerRedirectUri
);
}
}
/**
* Add EAR (Encrypted Authorize Response) request parameters
* @param parameters
* @param jwk
*/
export function addEARParameters(
parameters: Map<string, string>,
jwk: string
): void {
parameters.set(AADServerParamKeys.EAR_JWK, encodeURIComponent(jwk));
// ear_jwe_crypto will always have value: {"alg":"dir","enc":"A256GCM"} so we can hardcode this
const jweCryptoB64Encoded = "eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0";
parameters.set(AADServerParamKeys.EAR_JWE_CRYPTO, jweCryptoB64Encoded);
}
export function addResource(
parameters: Map<string, string>,
resource?: string
): void {
if (resource) {
parameters.set(AADServerParamKeys.RESOURCE, resource);
}
}

View File

@ -0,0 +1,246 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import {
createClientConfigurationError,
ClientConfigurationErrorCodes,
} from "../error/ClientConfigurationError.js";
import { StringUtils } from "../utils/StringUtils.js";
import {
ClientAuthErrorCodes,
createClientAuthError,
} from "../error/ClientAuthError.js";
import {
OIDC_SCOPES,
OIDC_DEFAULT_SCOPES,
OFFLINE_ACCESS_SCOPE,
} from "../utils/Constants.js";
/**
* The ScopeSet class creates a set of scopes. Scopes are case-insensitive, unique values, so the Set object in JS makes
* the most sense to implement for this class. All scopes are trimmed and converted to lower case strings in intersection and union functions
* to ensure uniqueness of strings.
*/
export class ScopeSet {
// Scopes as a Set of strings
private scopes: Set<string>;
constructor(inputScopes: Array<string>) {
// Filter empty string and null/undefined array items
const scopeArr = inputScopes
? StringUtils.trimArrayEntries([...inputScopes])
: [];
const filteredInput = scopeArr
? StringUtils.removeEmptyStringsFromArray(scopeArr)
: [];
// Check if scopes array has at least one member
if (!filteredInput || !filteredInput.length) {
throw createClientConfigurationError(
ClientConfigurationErrorCodes.emptyInputScopesError
);
}
this.scopes = new Set<string>(); // Iterator in constructor not supported by IE11
filteredInput.forEach((scope) => this.scopes.add(scope));
}
/**
* Factory method to create ScopeSet from space-delimited string
* @param inputScopeString
* @param appClientId
* @param scopesRequired
*/
static fromString(inputScopeString: string): ScopeSet {
const scopeString = inputScopeString || "";
const inputScopes: Array<string> = scopeString.split(" ");
return new ScopeSet(inputScopes);
}
/**
* Creates the set of scopes to search for in cache lookups
* @param inputScopeString
* @returns
*/
static createSearchScopes(inputScopeString: Array<string>): ScopeSet {
// Handle empty scopes by using default OIDC scopes for cache lookup
const scopesToUse =
inputScopeString && inputScopeString.length > 0
? inputScopeString
: [...OIDC_DEFAULT_SCOPES];
const scopeSet = new ScopeSet(scopesToUse);
if (!scopeSet.containsOnlyOIDCScopes()) {
scopeSet.removeOIDCScopes();
} else {
scopeSet.removeScope(OFFLINE_ACCESS_SCOPE);
}
return scopeSet;
}
/**
* Check if a given scope is present in this set of scopes.
* @param scope
*/
containsScope(scope: string): boolean {
const lowerCaseScopes = this.printScopesLowerCase().split(" ");
const lowerCaseScopesSet = new ScopeSet(lowerCaseScopes);
// compare lowercase scopes
return scope
? lowerCaseScopesSet.scopes.has(scope.toLowerCase())
: false;
}
/**
* Check if a set of scopes is present in this set of scopes.
* @param scopeSet
*/
containsScopeSet(scopeSet: ScopeSet): boolean {
if (!scopeSet || scopeSet.scopes.size <= 0) {
return false;
}
return (
this.scopes.size >= scopeSet.scopes.size &&
scopeSet.asArray().every((scope) => this.containsScope(scope))
);
}
/**
* Check if set of scopes contains only the defaults
*/
containsOnlyOIDCScopes(): boolean {
let defaultScopeCount = 0;
OIDC_SCOPES.forEach((defaultScope: string) => {
if (this.containsScope(defaultScope)) {
defaultScopeCount += 1;
}
});
return this.scopes.size === defaultScopeCount;
}
/**
* Appends single scope if passed
* @param newScope
*/
appendScope(newScope: string): void {
if (newScope) {
this.scopes.add(newScope.trim());
}
}
/**
* Appends multiple scopes if passed
* @param newScopes
*/
appendScopes(newScopes: Array<string>): void {
try {
newScopes.forEach((newScope) => this.appendScope(newScope));
} catch (e) {
throw createClientAuthError(
ClientAuthErrorCodes.cannotAppendScopeSet
);
}
}
/**
* Removes element from set of scopes.
* @param scope
*/
removeScope(scope: string): void {
if (!scope) {
throw createClientAuthError(
ClientAuthErrorCodes.cannotRemoveEmptyScope
);
}
this.scopes.delete(scope.trim());
}
/**
* Removes default scopes from set of scopes
* Primarily used to prevent cache misses if the default scopes are not returned from the server
*/
removeOIDCScopes(): void {
OIDC_SCOPES.forEach((defaultScope: string) => {
this.scopes.delete(defaultScope);
});
}
/**
* Combines an array of scopes with the current set of scopes.
* @param otherScopes
*/
unionScopeSets(otherScopes: ScopeSet): Set<string> {
if (!otherScopes) {
throw createClientAuthError(
ClientAuthErrorCodes.emptyInputScopeSet
);
}
const unionScopes = new Set<string>(); // Iterator in constructor not supported in IE11
otherScopes.scopes.forEach((scope) =>
unionScopes.add(scope.toLowerCase())
);
this.scopes.forEach((scope) => unionScopes.add(scope.toLowerCase()));
return unionScopes;
}
/**
* Check if scopes intersect between this set and another.
* @param otherScopes
*/
intersectingScopeSets(otherScopes: ScopeSet): boolean {
if (!otherScopes) {
throw createClientAuthError(
ClientAuthErrorCodes.emptyInputScopeSet
);
}
// Do not allow OIDC scopes to be the only intersecting scopes
if (!otherScopes.containsOnlyOIDCScopes()) {
otherScopes.removeOIDCScopes();
}
const unionScopes = this.unionScopeSets(otherScopes);
const sizeOtherScopes = otherScopes.getScopeCount();
const sizeThisScopes = this.getScopeCount();
const sizeUnionScopes = unionScopes.size;
return sizeUnionScopes < sizeThisScopes + sizeOtherScopes;
}
/**
* Returns size of set of scopes.
*/
getScopeCount(): number {
return this.scopes.size;
}
/**
* Returns the scopes as an array of string values
*/
asArray(): Array<string> {
const array: Array<string> = [];
this.scopes.forEach((val) => array.push(val));
return array;
}
/**
* Prints scopes into a space-delimited string
*/
printScopes(): string {
if (this.scopes) {
const scopeArr = this.asArray();
return scopeArr.join(" ");
}
return "";
}
/**
* Prints scopes into a space-delimited lower-case string (used for caching)
*/
printScopesLowerCase(): string {
return this.printScopes().toLowerCase();
}
}

View File

@ -0,0 +1,16 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
/**
* Controls whether tokens should be stored in the cache or not. If set to false, tokens may still be acquired and returned but will not be cached for later retrieval.
*/
export type StoreInCache = {
/* Indicates whether or not the acquired accessToken will be stored in the cache */
accessToken?: boolean;
/* Indicates whether or not the acquired idToken will be stored in the cache */
idToken?: boolean;
/* Indicates whether or not the acquired refreshToken will be stored in the cache */
refreshToken?: boolean;
};