@azure/msal-common
Version:
Microsoft Authentication Library for js
664 lines (605 loc) • 18 kB
text/typescript
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import {
Constants,
ResponseMode,
CLIENT_INFO,
AuthenticationScheme,
ClaimsRequestKeys,
PasswordGrantConstants,
OIDC_DEFAULT_SCOPES,
ThrottlingConstants,
HeaderNames,
} 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 { RequestValidator } from "./RequestValidator.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";
function instrumentBrokerParams(
parameters: Map<string, string>,
correlationId?: string,
performanceClient?: IPerformanceClient
) {
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
);
}
}
/** @internal */
export class RequestParameterBuilder {
private parameters: Map<string, string>;
private readonly performanceClient?: IPerformanceClient;
private readonly correlationId?: string;
constructor(
correlationId?: string,
performanceClient?: IPerformanceClient
) {
this.parameters = new Map<string, string>();
this.performanceClient = performanceClient;
this.correlationId = correlationId;
}
/**
* add response_type = code
*/
addResponseTypeCode(): void {
this.parameters.set(
AADServerParamKeys.RESPONSE_TYPE,
encodeURIComponent(Constants.CODE_RESPONSE_TYPE)
);
}
/**
* add response_type = token id_token
*/
addResponseTypeForTokenAndIdToken(): void {
this.parameters.set(
AADServerParamKeys.RESPONSE_TYPE,
encodeURIComponent(
`${Constants.TOKEN_RESPONSE_TYPE} ${Constants.ID_TOKEN_RESPONSE_TYPE}`
)
);
}
/**
* add response_mode. defaults to query.
* @param responseMode
*/
addResponseMode(responseMode?: ResponseMode): void {
this.parameters.set(
AADServerParamKeys.RESPONSE_MODE,
encodeURIComponent(responseMode ? responseMode : ResponseMode.QUERY)
);
}
/**
* Add flag to indicate STS should attempt to use WAM if available
*/
addNativeBroker(): void {
this.parameters.set(
AADServerParamKeys.NATIVE_BROKER,
encodeURIComponent("1")
);
}
/**
* add scopes. set addOidcScopes to false to prevent default scopes in non-user scenarios
* @param scopeSet
* @param addOidcScopes
*/
addScopes(
scopes: string[],
addOidcScopes: boolean = true,
defaultScopes: Array<string> = 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);
this.parameters.set(
AADServerParamKeys.SCOPE,
encodeURIComponent(scopeSet.printScopes())
);
}
/**
* add clientId
* @param clientId
*/
addClientId(clientId: string): void {
this.parameters.set(
AADServerParamKeys.CLIENT_ID,
encodeURIComponent(clientId)
);
}
/**
* add redirect_uri
* @param redirectUri
*/
addRedirectUri(redirectUri: string): void {
RequestValidator.validateRedirectUri(redirectUri);
this.parameters.set(
AADServerParamKeys.REDIRECT_URI,
encodeURIComponent(redirectUri)
);
}
/**
* add post logout redirectUri
* @param redirectUri
*/
addPostLogoutRedirectUri(redirectUri: string): void {
RequestValidator.validateRedirectUri(redirectUri);
this.parameters.set(
AADServerParamKeys.POST_LOGOUT_URI,
encodeURIComponent(redirectUri)
);
}
/**
* add id_token_hint to logout request
* @param idTokenHint
*/
addIdTokenHint(idTokenHint: string): void {
this.parameters.set(
AADServerParamKeys.ID_TOKEN_HINT,
encodeURIComponent(idTokenHint)
);
}
/**
* add domain_hint
* @param domainHint
*/
addDomainHint(domainHint: string): void {
this.parameters.set(
AADServerParamKeys.DOMAIN_HINT,
encodeURIComponent(domainHint)
);
}
/**
* add login_hint
* @param loginHint
*/
addLoginHint(loginHint: string): void {
this.parameters.set(
AADServerParamKeys.LOGIN_HINT,
encodeURIComponent(loginHint)
);
}
/**
* Adds the CCS (Cache Credential Service) query parameter for login_hint
* @param loginHint
*/
addCcsUpn(loginHint: string): void {
this.parameters.set(
HeaderNames.CCS_HEADER,
encodeURIComponent(`UPN:${loginHint}`)
);
}
/**
* Adds the CCS (Cache Credential Service) query parameter for account object
* @param loginHint
*/
addCcsOid(clientInfo: ClientInfo): void {
this.parameters.set(
HeaderNames.CCS_HEADER,
encodeURIComponent(`Oid:${clientInfo.uid}@${clientInfo.utid}`)
);
}
/**
* add sid
* @param sid
*/
addSid(sid: string): void {
this.parameters.set(AADServerParamKeys.SID, encodeURIComponent(sid));
}
/**
* add claims
* @param claims
*/
addClaims(claims?: string, clientCapabilities?: Array<string>): void {
const mergedClaims = this.addClientCapabilitiesToClaims(
claims,
clientCapabilities
);
RequestValidator.validateClaims(mergedClaims);
this.parameters.set(
AADServerParamKeys.CLAIMS,
encodeURIComponent(mergedClaims)
);
}
/**
* add correlationId
* @param correlationId
*/
addCorrelationId(correlationId: string): void {
this.parameters.set(
AADServerParamKeys.CLIENT_REQUEST_ID,
encodeURIComponent(correlationId)
);
}
/**
* add library info query params
* @param libraryInfo
*/
addLibraryInfo(libraryInfo: LibraryInfo): void {
// Telemetry Info
this.parameters.set(AADServerParamKeys.X_CLIENT_SKU, libraryInfo.sku);
this.parameters.set(
AADServerParamKeys.X_CLIENT_VER,
libraryInfo.version
);
if (libraryInfo.os) {
this.parameters.set(AADServerParamKeys.X_CLIENT_OS, libraryInfo.os);
}
if (libraryInfo.cpu) {
this.parameters.set(
AADServerParamKeys.X_CLIENT_CPU,
libraryInfo.cpu
);
}
}
/**
* Add client telemetry parameters
* @param appTelemetry
*/
addApplicationTelemetry(appTelemetry: ApplicationTelemetry): void {
if (appTelemetry?.appName) {
this.parameters.set(
AADServerParamKeys.X_APP_NAME,
appTelemetry.appName
);
}
if (appTelemetry?.appVersion) {
this.parameters.set(
AADServerParamKeys.X_APP_VER,
appTelemetry.appVersion
);
}
}
/**
* add prompt
* @param prompt
*/
addPrompt(prompt: string): void {
RequestValidator.validatePrompt(prompt);
this.parameters.set(
`${AADServerParamKeys.PROMPT}`,
encodeURIComponent(prompt)
);
}
/**
* add state
* @param state
*/
addState(state: string): void {
if (state) {
this.parameters.set(
AADServerParamKeys.STATE,
encodeURIComponent(state)
);
}
}
/**
* add nonce
* @param nonce
*/
addNonce(nonce: string): void {
this.parameters.set(
AADServerParamKeys.NONCE,
encodeURIComponent(nonce)
);
}
/**
* add code_challenge and code_challenge_method
* - throw if either of them are not passed
* @param codeChallenge
* @param codeChallengeMethod
*/
addCodeChallengeParams(
codeChallenge: string,
codeChallengeMethod: string
): void {
RequestValidator.validateCodeChallengeParams(
codeChallenge,
codeChallengeMethod
);
if (codeChallenge && codeChallengeMethod) {
this.parameters.set(
AADServerParamKeys.CODE_CHALLENGE,
encodeURIComponent(codeChallenge)
);
this.parameters.set(
AADServerParamKeys.CODE_CHALLENGE_METHOD,
encodeURIComponent(codeChallengeMethod)
);
} else {
throw createClientConfigurationError(
ClientConfigurationErrorCodes.pkceParamsMissing
);
}
}
/**
* add the `authorization_code` passed by the user to exchange for a token
* @param code
*/
addAuthorizationCode(code: string): void {
this.parameters.set(AADServerParamKeys.CODE, encodeURIComponent(code));
}
/**
* add the `authorization_code` passed by the user to exchange for a token
* @param code
*/
addDeviceCode(code: string): void {
this.parameters.set(
AADServerParamKeys.DEVICE_CODE,
encodeURIComponent(code)
);
}
/**
* add the `refreshToken` passed by the user
* @param refreshToken
*/
addRefreshToken(refreshToken: string): void {
this.parameters.set(
AADServerParamKeys.REFRESH_TOKEN,
encodeURIComponent(refreshToken)
);
}
/**
* add the `code_verifier` passed by the user to exchange for a token
* @param codeVerifier
*/
addCodeVerifier(codeVerifier: string): void {
this.parameters.set(
AADServerParamKeys.CODE_VERIFIER,
encodeURIComponent(codeVerifier)
);
}
/**
* add client_secret
* @param clientSecret
*/
addClientSecret(clientSecret: string): void {
this.parameters.set(
AADServerParamKeys.CLIENT_SECRET,
encodeURIComponent(clientSecret)
);
}
/**
* add clientAssertion for confidential client flows
* @param clientAssertion
*/
addClientAssertion(clientAssertion: string): void {
if (clientAssertion) {
this.parameters.set(
AADServerParamKeys.CLIENT_ASSERTION,
encodeURIComponent(clientAssertion)
);
}
}
/**
* add clientAssertionType for confidential client flows
* @param clientAssertionType
*/
addClientAssertionType(clientAssertionType: string): void {
if (clientAssertionType) {
this.parameters.set(
AADServerParamKeys.CLIENT_ASSERTION_TYPE,
encodeURIComponent(clientAssertionType)
);
}
}
/**
* add OBO assertion for confidential client flows
* @param clientAssertion
*/
addOboAssertion(oboAssertion: string): void {
this.parameters.set(
AADServerParamKeys.OBO_ASSERTION,
encodeURIComponent(oboAssertion)
);
}
/**
* add grant type
* @param grantType
*/
addRequestTokenUse(tokenUse: string): void {
this.parameters.set(
AADServerParamKeys.REQUESTED_TOKEN_USE,
encodeURIComponent(tokenUse)
);
}
/**
* add grant type
* @param grantType
*/
addGrantType(grantType: string): void {
this.parameters.set(
AADServerParamKeys.GRANT_TYPE,
encodeURIComponent(grantType)
);
}
/**
* add client info
*
*/
addClientInfo(): void {
this.parameters.set(CLIENT_INFO, "1");
}
/**
* add extraQueryParams
* @param eQParams
*/
addExtraQueryParameters(eQParams: StringDict): void {
Object.entries(eQParams).forEach(([key, value]) => {
if (!this.parameters.has(key) && value) {
this.parameters.set(key, value);
}
});
}
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(ClaimsRequestKeys.ACCESS_TOKEN)) {
// Add access_token key to claims object
mergedClaims[ClaimsRequestKeys.ACCESS_TOKEN] = {};
}
// Add xms_cc claim with provided clientCapabilities to access_token key
mergedClaims[ClaimsRequestKeys.ACCESS_TOKEN][
ClaimsRequestKeys.XMS_CC
] = {
values: clientCapabilities,
};
}
return JSON.stringify(mergedClaims);
}
/**
* adds `username` for Password Grant flow
* @param username
*/
addUsername(username: string): void {
this.parameters.set(
PasswordGrantConstants.username,
encodeURIComponent(username)
);
}
/**
* adds `password` for Password Grant flow
* @param password
*/
addPassword(password: string): void {
this.parameters.set(
PasswordGrantConstants.password,
encodeURIComponent(password)
);
}
/**
* add pop_jwk to query params
* @param cnfString
*/
addPopToken(cnfString: string): void {
if (cnfString) {
this.parameters.set(
AADServerParamKeys.TOKEN_TYPE,
AuthenticationScheme.POP
);
this.parameters.set(
AADServerParamKeys.REQ_CNF,
encodeURIComponent(cnfString)
);
}
}
/**
* add SSH JWK and key ID to query params
*/
addSshJwk(sshJwkString: string): void {
if (sshJwkString) {
this.parameters.set(
AADServerParamKeys.TOKEN_TYPE,
AuthenticationScheme.SSH
);
this.parameters.set(
AADServerParamKeys.REQ_CNF,
encodeURIComponent(sshJwkString)
);
}
}
/**
* add server telemetry fields
* @param serverTelemetryManager
*/
addServerTelemetry(serverTelemetryManager: ServerTelemetryManager): void {
this.parameters.set(
AADServerParamKeys.X_CLIENT_CURR_TELEM,
serverTelemetryManager.generateCurrentRequestHeaderValue()
);
this.parameters.set(
AADServerParamKeys.X_CLIENT_LAST_TELEM,
serverTelemetryManager.generateLastRequestHeaderValue()
);
}
/**
* Adds parameter that indicates to the server that throttling is supported
*/
addThrottling(): void {
this.parameters.set(
AADServerParamKeys.X_MS_LIB_CAPABILITY,
ThrottlingConstants.X_MS_LIB_CAPABILITY_VALUE
);
}
/**
* Adds logout_hint parameter for "silent" logout which prevent server account picker
*/
addLogoutHint(logoutHint: string): void {
this.parameters.set(
AADServerParamKeys.LOGOUT_HINT,
encodeURIComponent(logoutHint)
);
}
addBrokerParameters(params: {
brokerClientId: string;
brokerRedirectUri: string;
}): void {
const brokerParams: StringDict = {};
brokerParams[AADServerParamKeys.BROKER_CLIENT_ID] =
params.brokerClientId;
brokerParams[AADServerParamKeys.BROKER_REDIRECT_URI] =
params.brokerRedirectUri;
this.addExtraQueryParameters(brokerParams);
}
/**
* Utility to create a URL from the params map
*/
createQueryString(): string {
const queryParameterArray: Array<string> = new Array<string>();
this.parameters.forEach((value, key) => {
queryParameterArray.push(`${key}=${value}`);
});
instrumentBrokerParams(
this.parameters,
this.correlationId,
this.performanceClient
);
return queryParameterArray.join("&");
}
}