From c5608c8b1f4b74078597628fe22d905f840db3e3 Mon Sep 17 00:00:00 2001 From: Prithvi Kanherkar Date: Fri, 14 Aug 2020 18:26:27 -0700 Subject: [PATCH 01/17] Returns token as Proof-of-possession token --- lib/msal-browser/src/cache/DatabaseStorage.ts | 84 +++++++++++++++++++ lib/msal-browser/src/crypto/BrowserCrypto.ts | 73 +++++++++++++++- lib/msal-browser/src/crypto/CryptoOps.ts | 76 ++++++++++++++--- .../src/utils/BrowserStringUtils.ts | 13 +++ .../src/account/{IdToken.ts => JwtToken.ts} | 28 +++---- .../{IdTokenClaims.ts => TokenClaims.ts} | 7 +- .../src/cache/entities/AccessTokenEntity.ts | 7 +- .../src/cache/entities/AccountEntity.ts | 6 +- .../src/client/AuthorizationCodeClient.ts | 6 +- .../src/client/DeviceCodeClient.ts | 10 ++- .../src/client/RefreshTokenClient.ts | 4 +- .../src/client/SilentFlowClient.ts | 26 ++++-- .../src/config/ClientConfiguration.ts | 4 + lib/msal-common/src/crypto/ICrypto.ts | 11 ++- .../src/crypto/PopTokenGenerator.ts | 24 +++++- .../src/crypto/SignedHttpRequest.ts | 15 ++++ lib/msal-common/src/error/ClientAuthError.ts | 28 +++---- lib/msal-common/src/index.ts | 6 +- .../src/request/AuthorizationCodeRequest.ts | 2 + .../src/request/AuthorizationUrlRequest.ts | 2 + .../src/request/BaseAuthRequest.ts | 4 + .../src/request/DeviceCodeRequest.ts | 2 + .../src/request/RefreshTokenRequest.ts | 2 + .../src/request/RequestParameterBuilder.ts | 2 +- .../src/request/SilentFlowRequest.ts | 2 + .../src/response/AuthenticationResult.ts | 1 + .../src/response/ResponseHandler.ts | 37 +++++--- lib/msal-common/src/url/IUri.ts | 1 + lib/msal-common/src/url/UrlString.ts | 3 +- lib/msal-common/src/utils/StringUtils.ts | 4 +- .../app/default/authConfig.js | 14 ++-- 31 files changed, 410 insertions(+), 94 deletions(-) create mode 100644 lib/msal-browser/src/cache/DatabaseStorage.ts rename lib/msal-common/src/account/{IdToken.ts => JwtToken.ts} (54%) rename lib/msal-common/src/account/{IdTokenClaims.ts => TokenClaims.ts} (79%) create mode 100644 lib/msal-common/src/crypto/SignedHttpRequest.ts diff --git a/lib/msal-browser/src/cache/DatabaseStorage.ts b/lib/msal-browser/src/cache/DatabaseStorage.ts new file mode 100644 index 0000000000..03d227b2bb --- /dev/null +++ b/lib/msal-browser/src/cache/DatabaseStorage.ts @@ -0,0 +1,84 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +interface IDBOpenDBRequestEvent extends Event { + target: IDBOpenDBRequest & EventTarget; +} + +interface IDBOpenOnUpgradeNeededEvent extends IDBVersionChangeEvent { + target: IDBOpenDBRequest & EventTarget; +} + +interface IDBRequestEvent extends Event { + target: IDBRequest & EventTarget; +} + +/** + * Storage wrapper for IndexedDB storage in browsers: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API + */ +export class DatabaseStorage{ + private _db : IDBDatabase; + private _dbName: string; + private _tableName: string; + private _version: number; + + constructor(dbName: string, tableName: string, version: number) { + this._dbName = dbName; + this._tableName = tableName; + this._version = version; + } + + /** + * Opens IndexedDB instance. + */ + async open(): Promise { + return new Promise((resolve, reject) => { + // TODO: Add timeouts? + const openDB = window.indexedDB.open(this._dbName, this._version); + openDB.addEventListener("upgradeneeded", (e: IDBOpenOnUpgradeNeededEvent) => { + e.target.result.createObjectStore(this._tableName); + }); + openDB.addEventListener("success", (e: IDBOpenDBRequestEvent) => { + this._db = e.target.result; + resolve(); + }); + + openDB.addEventListener("error", error => reject(error)); + }); + } + + /** + * Retrieves item from IndexedDB instance. + * @param key + */ + async get(key: string): Promise { + return new Promise((resolve, reject) => { + // TODO: Add timeouts? + const transaction = this._db.transaction([this._tableName], "readonly"); + + const objectStore = transaction.objectStore(this._tableName); + const dbGet = objectStore.get(key); + dbGet.addEventListener("success", (e: IDBRequestEvent) => resolve(e.target.result)); + dbGet.addEventListener("error", e => reject(e)); + }); + } + + /** + * Adds item to IndexedDB under given key + * @param key + * @param payload + */ + async put(key: string, payload: T): Promise { + return new Promise((resolve: any, reject: any) => { + // TODO: Add timeouts? + const transaction = this._db.transaction([this._tableName], "readwrite"); + const objectStore = transaction.objectStore(this._tableName); + + const dbPut = objectStore.put(payload, key); + dbPut.addEventListener("success", (e: IDBRequestEvent) => resolve(e.target.result)); + dbPut.addEventListener("error", e => reject(e)); + }); + } +} diff --git a/lib/msal-browser/src/crypto/BrowserCrypto.ts b/lib/msal-browser/src/crypto/BrowserCrypto.ts index 6bddea2b48..7946e2d409 100644 --- a/lib/msal-browser/src/crypto/BrowserCrypto.ts +++ b/lib/msal-browser/src/crypto/BrowserCrypto.ts @@ -49,7 +49,6 @@ export class BrowserCrypto { modulusLength: keygenConfigModulusLength, publicExponent: keygenConfigPublicExponent }; - console.log(this._keygenAlgorithmOptions); } /** @@ -96,6 +95,33 @@ export class BrowserCrypto { return this.hasIECrypto() ? this.msCryptoExportKey(key, format) : window.crypto.subtle.exportKey(format, key); } + /** + * Imports key as given KeyFormat, can set extractable and usages. + * @param key + * @param format + * @param extractable + * @param usages + */ + async importKey(key: JsonWebKey, format: KeyFormat, extractable: boolean, usages: Array): Promise { + const keyString = BrowserCrypto.getJwkString(key); + const keyBuffer = BrowserStringUtils.stringToArrayBuffer(keyString); + + return this.hasIECrypto() ? + this.msCryptoImportKey(keyBuffer, format, extractable, usages) + : window.crypto.subtle.importKey(format, key, this._keygenAlgorithmOptions, extractable, usages); + } + + /** + * Signs given data with given key + * @param key + * @param data + */ + async sign(key: CryptoKey, data: ArrayBuffer): Promise { + return this.hasIECrypto() ? + this.msCryptoSign(key, data) + : window.crypto.subtle.sign(this._keygenAlgorithmOptions, key, data); + } + /** * Check whether IE crypto or other browser cryptography is available. */ @@ -143,6 +169,11 @@ export class BrowserCrypto { }); } + /** + * IE Helper function for generating a keypair + * @param extractable + * @param usages + */ private async msCryptoGenerateKey(extractable: boolean, usages: Array): Promise { return new Promise((resolve: any, reject: any) => { const msGenerateKey = window["msCrypto"].subtle.generateKey(this._keygenAlgorithmOptions, extractable, usages); @@ -157,7 +188,7 @@ export class BrowserCrypto { } /** - * IE Helper function for exporting keys + * IE Helper function for exportKey * @param key * @param format */ @@ -187,6 +218,44 @@ export class BrowserCrypto { }); } + /** + * IE Helper function for importKey + * @param key + * @param format + * @param extractable + * @param usages + */ + private async msCryptoImportKey(keyBuffer: ArrayBuffer, format: KeyFormat, extractable: boolean, usages: Array): Promise { + return new Promise((resolve: any, reject: any) => { + const msImportKey = window["msCrypto"].subtle.importKey(format, keyBuffer, this._keygenAlgorithmOptions, extractable, usages); + msImportKey.addEventListener("complete", (e: { target: { result: CryptoKey | PromiseLike; }; }) => { + resolve(e.target.result); + }); + + msImportKey.addEventListener("error", (error: any) => { + reject(error); + }); + }); + } + + /** + * IE Helper function for sign JWT + * @param key + * @param data + */ + private async msCryptoSign(key: CryptoKey, data: ArrayBuffer): Promise { + return new Promise((resolve: any, reject: any) => { + const msSign = window["msCrypto"].subtle.sign(this._keygenAlgorithmOptions, key, data); + msSign.addEventListener("complete", (e: { target: { result: ArrayBuffer | PromiseLike; }; }) => { + resolve(e.target.result); + }); + + msSign.addEventListener("error", (error: any) => { + reject(error); + }); + }); + } + /** * Returns stringified jwk. * @param jwk diff --git a/lib/msal-browser/src/crypto/CryptoOps.ts b/lib/msal-browser/src/crypto/CryptoOps.ts index 99113c9118..d3e638e5fc 100644 --- a/lib/msal-browser/src/crypto/CryptoOps.ts +++ b/lib/msal-browser/src/crypto/CryptoOps.ts @@ -2,14 +2,22 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ -import { ICrypto, PkceCodes } from "@azure/msal-common"; +import { ICrypto, PkceCodes, SignedHttpRequest } from "@azure/msal-common"; import { GuidGenerator } from "./GuidGenerator"; import { Base64Encode } from "../encode/Base64Encode"; import { Base64Decode } from "../encode/Base64Decode"; import { PkceGenerator } from "./PkceGenerator"; import { BrowserCrypto, KeyFormat } from "./BrowserCrypto"; +import { DatabaseStorage } from "../cache/DatabaseStorage"; import { BrowserStringUtils } from "../utils/BrowserStringUtils"; +type CachedKeyPair = { + publicKey: CryptoKey, + privateKey: CryptoKey, + requestMethod: string, + requestUri: string +}; + /** * This class implements MSAL's crypto interface, which allows it to perform base64 encoding and decoding, generating cryptographically random GUIDs and * implementing Proof Key for Code Exchange specs for the OAuth Authorization Code Flow using PKCE (rfc here: https://tools.ietf.org/html/rfc7636). @@ -26,6 +34,11 @@ export class CryptoOps implements ICrypto { private static EXTRACTABLE: boolean = true; private static POP_HASH_LENGTH = 43; // 256 bit digest / 6 bits per char = 43 + private static DB_VERSION = 1; + private static DB_NAME = "msal.db"; + private static TABLE_NAME =`${CryptoOps.DB_NAME}.keys`; + private _cache: DatabaseStorage; + constructor() { // Browser crypto needs to be validated first before any other classes can be set. this.browserCrypto = new BrowserCrypto(); @@ -33,16 +46,8 @@ export class CryptoOps implements ICrypto { this.b64Decode = new Base64Decode(); this.guidGenerator = new GuidGenerator(this.browserCrypto); this.pkceGenerator = new PkceGenerator(this.browserCrypto); - } - - async getPublicKeyThumprint(): Promise { - const keyPair = await this.browserCrypto.generateKeyPair(CryptoOps.EXTRACTABLE, CryptoOps.POP_KEY_USAGES); - // TODO: Store keypair - const publicKeyJwk: JsonWebKey = await this.browserCrypto.exportKey(keyPair.publicKey, KeyFormat.jwk); - const publicJwkString: string = BrowserCrypto.getJwkString(publicKeyJwk); - const publicJwkBuffer: ArrayBuffer = await this.browserCrypto.sha256Digest(publicJwkString); - const publicJwkDigest: string = this.b64Encode.urlEncodeArr(new Uint8Array(publicJwkBuffer)); - return this.base64Encode(publicJwkDigest).substr(0, CryptoOps.POP_HASH_LENGTH); + this._cache = new DatabaseStorage(CryptoOps.DB_NAME, CryptoOps.TABLE_NAME, CryptoOps.DB_VERSION); + this._cache.open(); } /** @@ -75,4 +80,53 @@ export class CryptoOps implements ICrypto { async generatePkceCodes(): Promise { return this.pkceGenerator.generateCodes(); } + + /** + * Generates a keypair, stores it and returns a thumbprint + * @param resourceRequestMethod + * @param resourceRequestUri + */ + async getPublicKeyThumprint(resourceRequestMethod: string, resourceRequestUri: string): Promise { + const keyPair = await this.browserCrypto.generateKeyPair(CryptoOps.EXTRACTABLE, CryptoOps.POP_KEY_USAGES); + const publicKeyJwk: JsonWebKey = await this.browserCrypto.exportKey(keyPair.publicKey, KeyFormat.jwk); + const privateKeyJwk: JsonWebKey = await this.browserCrypto.exportKey(keyPair.privateKey, KeyFormat.jwk); + const publicJwkString: string = BrowserCrypto.getJwkString(publicKeyJwk); + const publicJwkBuffer: ArrayBuffer = await this.browserCrypto.sha256Digest(publicJwkString); + const publicJwkDigest: string = this.b64Encode.urlEncodeArr(new Uint8Array(publicJwkBuffer)); + const unextractablePrivateKey: CryptoKey = await this.browserCrypto.importKey(privateKeyJwk, KeyFormat.jwk, false, ["sign"]); + const publicKeyHash = this.base64Encode(publicJwkDigest).substr(0, CryptoOps.POP_HASH_LENGTH); + this._cache.put(publicKeyHash, { + privateKey: unextractablePrivateKey, + publicKey: keyPair.publicKey, + requestMethod: resourceRequestMethod, + requestUri: resourceRequestUri + }); + return publicKeyHash; + } + + /** + * Signs the given object as a jwt payload with private key retrieved by given kid. + * @param payload + * @param kid + */ + async signJwt(payload: SignedHttpRequest, kid: string): Promise { + const cachedKeyPair: CachedKeyPair = await this._cache.get(kid); + const publicKeyJwk = await this.browserCrypto.exportKey(cachedKeyPair.publicKey, KeyFormat.jwk); + const publicKeyJwkString = BrowserCrypto.getJwkString(publicKeyJwk); + + const header = { + alg: publicKeyJwk.alg, + type: KeyFormat.jwk, + jwk: JSON.parse(publicKeyJwkString) + }; + + const encodedHeader = this.b64Encode.urlEncode(JSON.stringify(header)); + const encodedPayload = this.b64Encode.urlEncode(JSON.stringify(payload)); + const tokenString = `${encodedHeader}.${encodedPayload}`; + const tokenBuffer = BrowserStringUtils.stringToArrayBuffer(tokenString); + const signatureBuffer = await this.browserCrypto.sign(cachedKeyPair.privateKey, tokenBuffer); + const encodedSignature = this.b64Encode.urlEncode(BrowserStringUtils.utf8ArrToString(new Uint8Array(signatureBuffer))); + + return `${tokenString}.${encodedSignature}`; + } } diff --git a/lib/msal-browser/src/utils/BrowserStringUtils.ts b/lib/msal-browser/src/utils/BrowserStringUtils.ts index b60dcb386e..6f0d8673ba 100644 --- a/lib/msal-browser/src/utils/BrowserStringUtils.ts +++ b/lib/msal-browser/src/utils/BrowserStringUtils.ts @@ -68,6 +68,19 @@ export class BrowserStringUtils { return aBytes; } + /** + * Converst string to ArrayBuffer + * @param dataString + */ + static stringToArrayBuffer(dataString: string): ArrayBuffer { + const data = new ArrayBuffer(dataString.length); + const dataView = new Uint8Array(data); + for (let i: number = 0; i < dataString.length; i++) { + dataView[i] = dataString.charCodeAt(i); + } + return data; + } + /** * Converts Uint8Array to a string * @param aBytes diff --git a/lib/msal-common/src/account/IdToken.ts b/lib/msal-common/src/account/JwtToken.ts similarity index 54% rename from lib/msal-common/src/account/IdToken.ts rename to lib/msal-common/src/account/JwtToken.ts index 36bcb296fe..b1375941e6 100644 --- a/lib/msal-common/src/account/IdToken.ts +++ b/lib/msal-common/src/account/JwtToken.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ -import { IdTokenClaims } from "./IdTokenClaims"; +import { TokenClaims } from "./TokenClaims"; import { DecodedJwt } from "./DecodedJwt"; import { ClientAuthError } from "../error/ClientAuthError"; import { StringUtils } from "../utils/StringUtils"; @@ -11,19 +11,19 @@ import { ICrypto } from "../crypto/ICrypto"; /** * Id Token representation class. Parses id token string and generates claims object. */ -export class IdToken { +export class JwtToken { // Raw Id Token string - rawIdToken: string; + rawToken: string; // Claims inside Id Token - claims: IdTokenClaims; - constructor(rawIdToken: string, crypto: ICrypto) { - if (StringUtils.isEmpty(rawIdToken)) { - throw ClientAuthError.createIdTokenNullOrEmptyError(rawIdToken); + claims: TokenClaims; + constructor(rawToken: string, crypto: ICrypto) { + if (StringUtils.isEmpty(rawToken)) { + throw ClientAuthError.createTokenNullOrEmptyError(rawToken); } - this.rawIdToken = rawIdToken; - this.claims = IdToken.extractIdToken(rawIdToken, crypto); + this.rawToken = rawToken; + this.claims = JwtToken.extractTokenClaims(rawToken, crypto); } /** @@ -31,19 +31,19 @@ export class IdToken { * * @param encodedIdToken */ - static extractIdToken(encodedIdToken: string, crypto: ICrypto): IdTokenClaims { + static extractTokenClaims(encodedIdToken: string, crypto: ICrypto): TokenClaims { // id token will be decoded to get the username const decodedToken: DecodedJwt = StringUtils.decodeJwt(encodedIdToken); if (!decodedToken) { return null; } try { - const base64IdTokenPayload = decodedToken.JWSPayload; + const base64TokenPayload = decodedToken.JWSPayload; // base64Decode() should throw an error if there is an issue - const base64Decoded = crypto.base64Decode(base64IdTokenPayload); - return JSON.parse(base64Decoded) as IdTokenClaims; + const base64Decoded = crypto.base64Decode(base64TokenPayload); + return JSON.parse(base64Decoded) as TokenClaims; } catch (err) { - throw ClientAuthError.createIdTokenParsingError(err); + throw ClientAuthError.createTokenParsingError(err); } } } diff --git a/lib/msal-common/src/account/IdTokenClaims.ts b/lib/msal-common/src/account/TokenClaims.ts similarity index 79% rename from lib/msal-common/src/account/IdTokenClaims.ts rename to lib/msal-common/src/account/TokenClaims.ts index a6eda5709b..066f5e48ce 100644 --- a/lib/msal-common/src/account/IdTokenClaims.ts +++ b/lib/msal-common/src/account/TokenClaims.ts @@ -6,7 +6,7 @@ /** * Type which describes Id Token claims known by MSAL. */ -export type IdTokenClaims = { +export type TokenClaims = { iss?: string, oid?: string, sub?: string, @@ -19,5 +19,8 @@ export type IdTokenClaims = { exp?: number, home_oid?: string, sid?: string, - cloud_instance_host_name?: string + cloud_instance_host_name?: string, + cnf?: { + kid: string; + }; }; diff --git a/lib/msal-common/src/cache/entities/AccessTokenEntity.ts b/lib/msal-common/src/cache/entities/AccessTokenEntity.ts index 52ddcfa309..1bd3296cd3 100644 --- a/lib/msal-common/src/cache/entities/AccessTokenEntity.ts +++ b/lib/msal-common/src/cache/entities/AccessTokenEntity.ts @@ -4,8 +4,9 @@ */ import { CredentialEntity } from "./CredentialEntity"; -import { CredentialType } from "../../utils/Constants"; +import { CredentialType, AuthenticationType } from "../../utils/Constants"; import { TimeUtils } from "../../utils/TimeUtils"; +import { StringUtils } from "../../utils/StringUtils"; /** * ACCESS_TOKEN Credential Type @@ -60,7 +61,8 @@ export class AccessTokenEntity extends CredentialEntity { tenantId: string, scopes: string, expiresOn: number, - extExpiresOn: number + extExpiresOn: number, + tokenType?: string ): AccessTokenEntity { const atEntity: AccessTokenEntity = new AccessTokenEntity(); @@ -81,6 +83,7 @@ export class AccessTokenEntity extends CredentialEntity { atEntity.realm = tenantId; atEntity.target = scopes; + atEntity.tokenType = StringUtils.isEmpty(tokenType) ? AuthenticationType.BEARER : tokenType; return atEntity; } } diff --git a/lib/msal-common/src/cache/entities/AccountEntity.ts b/lib/msal-common/src/cache/entities/AccountEntity.ts index d2820a8ca5..a1402bd60d 100644 --- a/lib/msal-common/src/cache/entities/AccountEntity.ts +++ b/lib/msal-common/src/cache/entities/AccountEntity.ts @@ -9,7 +9,7 @@ import { CacheType, } from "../../utils/Constants"; import { Authority } from "../../authority/Authority"; -import { IdToken } from "../../account/IdToken"; +import { JwtToken } from "../../account/JwtToken"; import { ICrypto } from "../../crypto/ICrypto"; import { buildClientInfo } from "../../account/ClientInfo"; import { StringUtils } from "../../utils/StringUtils"; @@ -125,7 +125,7 @@ export class AccountEntity { static createAccount( clientInfo: string, authority: Authority, - idToken: IdToken, + idToken: JwtToken, crypto: ICrypto ): AccountEntity { const account: AccountEntity = new AccountEntity(); @@ -164,7 +164,7 @@ export class AccountEntity { */ static createADFSAccount( authority: Authority, - idToken: IdToken + idToken: JwtToken ): AccountEntity { const account: AccountEntity = new AccountEntity(); diff --git a/lib/msal-common/src/client/AuthorizationCodeClient.ts b/lib/msal-common/src/client/AuthorizationCodeClient.ts index c70715a22c..14de0bca3a 100644 --- a/lib/msal-common/src/client/AuthorizationCodeClient.ts +++ b/lib/msal-common/src/client/AuthorizationCodeClient.ts @@ -71,9 +71,7 @@ export class AuthorizationCodeClient extends BaseClient { // Validate response. This function throws a server error if an error is returned by the server. responseHandler.validateTokenResponse(response.body); - const tokenResponse = responseHandler.handleServerTokenResponse(response.body, this.authority, cachedNonce, cachedState); - - return tokenResponse; + return await responseHandler.handleServerTokenResponse(response.body, this.authority, cachedNonce, cachedState, request.resourceRequestMethod, request.resourceRequestUri); } /** @@ -176,7 +174,7 @@ export class AuthorizationCodeClient extends BaseClient { if (request.authenticationScheme === AuthenticationType.POP) { const popTokenGenerator = new PopTokenGenerator(this.cryptoUtils); - parameterBuilder.addPopToken(await popTokenGenerator.generateCnf()); + parameterBuilder.addPopToken(await popTokenGenerator.generateCnf(request.resourceRequestMethod, request.resourceRequestUri)); } const correlationId = request.correlationId || this.config.cryptoInterface.createNewGuid(); diff --git a/lib/msal-common/src/client/DeviceCodeClient.ts b/lib/msal-common/src/client/DeviceCodeClient.ts index cb837743a1..e63f2d0d2f 100644 --- a/lib/msal-common/src/client/DeviceCodeClient.ts +++ b/lib/msal-common/src/client/DeviceCodeClient.ts @@ -47,12 +47,14 @@ export class DeviceCodeClient extends BaseClient { // Validate response. This function throws a server error if an error is returned by the server. responseHandler.validateTokenResponse(response); - const tokenResponse = responseHandler.handleServerTokenResponse( + return await responseHandler.handleServerTokenResponse( response, - this.authority + this.authority, + "", + "", + request.resourceRequestMethod, + request.resourceRequestUri ); - - return tokenResponse; } /** diff --git a/lib/msal-common/src/client/RefreshTokenClient.ts b/lib/msal-common/src/client/RefreshTokenClient.ts index 1c2b03379c..f6ffbf6b31 100644 --- a/lib/msal-common/src/client/RefreshTokenClient.ts +++ b/lib/msal-common/src/client/RefreshTokenClient.ts @@ -34,12 +34,10 @@ export class RefreshTokenClient extends BaseClient { ); responseHandler.validateTokenResponse(response.body); - const tokenResponse = responseHandler.handleServerTokenResponse( + return await responseHandler.handleServerTokenResponse( response.body, this.authority ); - - return tokenResponse; } private async executeTokenRequest(request: RefreshTokenRequest, authority: Authority) diff --git a/lib/msal-common/src/client/SilentFlowClient.ts b/lib/msal-common/src/client/SilentFlowClient.ts index c9853864d3..584612b67e 100644 --- a/lib/msal-common/src/client/SilentFlowClient.ts +++ b/lib/msal-common/src/client/SilentFlowClient.ts @@ -12,7 +12,7 @@ import { IdTokenEntity } from "../cache/entities/IdTokenEntity"; import { AccessTokenEntity } from "../cache/entities/AccessTokenEntity"; import { RefreshTokenEntity } from "../cache/entities/RefreshTokenEntity"; import { ScopeSet } from "../request/ScopeSet"; -import { IdToken } from "../account/IdToken"; +import { JwtToken } from "../account/JwtToken"; import { TimeUtils } from "../utils/TimeUtils"; import { RefreshTokenRequest } from "../request/RefreshTokenRequest"; import { RefreshTokenClient } from "./RefreshTokenClient"; @@ -77,14 +77,22 @@ export class SilentFlowClient extends BaseClient { // Return tokens from cache this.config.serverTelemetryManager.incrementCacheHits(); const cachedIdToken = this.readIdTokenFromCache(homeAccountId, environment, cachedAccount.realm); - const idTokenObj = new IdToken(cachedIdToken.secret, this.config.cryptoInterface); - - return ResponseHandler.generateAuthenticationResult({ - account: cachedAccount, - accessToken: cachedAccessToken, - idToken: cachedIdToken, - refreshToken: cachedRefreshToken - }, idTokenObj, true); + const idTokenObj = new JwtToken(cachedIdToken.secret, this.config.cryptoInterface); + + return await ResponseHandler.generateAuthenticationResult( + this.cryptoUtils, + { + account: cachedAccount, + accessToken: cachedAccessToken, + idToken: cachedIdToken, + refreshToken: cachedRefreshToken + }, + idTokenObj, + true, + null, + request.resourceRequestMethod, + request.resourceRequestUri + ); } /** diff --git a/lib/msal-common/src/config/ClientConfiguration.ts b/lib/msal-common/src/config/ClientConfiguration.ts index 957322ecd3..bed2869206 100644 --- a/lib/msal-common/src/config/ClientConfiguration.ts +++ b/lib/msal-common/src/config/ClientConfiguration.ts @@ -149,6 +149,10 @@ const DEFAULT_CRYPTO_IMPLEMENTATION: ICrypto = { async getPublicKeyThumprint(): Promise { const notImplErr = "Crypto interface - getPublicKeyThumprint() has not been implemented"; throw AuthError.createUnexpectedError(notImplErr); + }, + async signJwt(): Promise { + const notImplErr = "Crypto interface - signJwt() has not been implemented"; + throw AuthError.createUnexpectedError(notImplErr); } }; diff --git a/lib/msal-common/src/crypto/ICrypto.ts b/lib/msal-common/src/crypto/ICrypto.ts index c49cf2ce0b..f32f7eb4b3 100644 --- a/lib/msal-common/src/crypto/ICrypto.ts +++ b/lib/msal-common/src/crypto/ICrypto.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. */ +import { SignedHttpRequest } from "./SignedHttpRequest"; + /** * The PkceCodes type describes the structure * of objects that contain PKCE code @@ -37,6 +39,13 @@ export interface ICrypto { generatePkceCodes(): Promise; /** * Generates an JWK RSA S256 Thumbprint + * @param resourceRequestMethod + * @param resourceRequestUri + */ + getPublicKeyThumprint(resourceRequestMethod: string, resourceRequestUri: string): Promise; + /** + * Returns a signed proof-of-possession token with a given acces token that contains a cnf claim with the required kid. + * @param accessToken */ - getPublicKeyThumprint(): Promise; + signJwt(payload: SignedHttpRequest, kid: string): Promise; } diff --git a/lib/msal-common/src/crypto/PopTokenGenerator.ts b/lib/msal-common/src/crypto/PopTokenGenerator.ts index 628579316a..a2eb3d217e 100644 --- a/lib/msal-common/src/crypto/PopTokenGenerator.ts +++ b/lib/msal-common/src/crypto/PopTokenGenerator.ts @@ -1,4 +1,9 @@ import { ICrypto } from "./ICrypto"; +import { JwtToken } from "../account/JwtToken"; +import { TokenClaims } from "../account/TokenClaims"; +import { TimeUtils } from "../utils/TimeUtils"; +import { UrlString } from "../url/UrlString"; +import { IUri } from "../url/IUri"; /** * See eSTS docs for more info. @@ -25,12 +30,27 @@ export class PopTokenGenerator { this.cryptoUtils = cryptoUtils; } - async generateCnf(): Promise { + async generateCnf(resourceRequestMethod: string, resourceRequestUri: string): Promise { const reqCnf: ReqCnf = { - kid: await this.cryptoUtils.getPublicKeyThumprint(), + kid: await this.cryptoUtils.getPublicKeyThumprint(resourceRequestMethod, resourceRequestUri), xms_ksl: KeyLocation.SW }; return this.cryptoUtils.base64Encode(JSON.stringify(reqCnf)); } + + async signPopToken(accessToken: string, resourceRequestMethod: string, resourceRequestUri: string): Promise { + const tokenClaims: TokenClaims = JwtToken.extractTokenClaims(accessToken, this.cryptoUtils); + const resourceUrlString: UrlString = new UrlString(resourceRequestUri); + const resourceUrlComponents: IUri = resourceUrlString.getUrlComponents(); + return await this.cryptoUtils.signJwt({ + at: accessToken, + ts: `${TimeUtils.nowSeconds()}`, + m: resourceRequestMethod.toUpperCase(), + u: resourceUrlComponents.HostNameAndPort || "", + nonce: this.cryptoUtils.createNewGuid(), + p: resourceUrlComponents.AbsolutePath, + q: [[], resourceUrlComponents.QueryString] + }, tokenClaims.cnf.kid); + } } diff --git a/lib/msal-common/src/crypto/SignedHttpRequest.ts b/lib/msal-common/src/crypto/SignedHttpRequest.ts new file mode 100644 index 0000000000..30110f4143 --- /dev/null +++ b/lib/msal-common/src/crypto/SignedHttpRequest.ts @@ -0,0 +1,15 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +export type SignedHttpRequest = { + at?: string; + cnf?: string; + m?: string; + u?: string; + p?: string; + q?: [Array, string]; + ts?: string; + nonce?: string; +}; diff --git a/lib/msal-common/src/error/ClientAuthError.ts b/lib/msal-common/src/error/ClientAuthError.ts index 351a8bfdab..7ef63afa66 100644 --- a/lib/msal-common/src/error/ClientAuthError.ts +++ b/lib/msal-common/src/error/ClientAuthError.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ import { AuthError } from "./AuthError"; -import { IdToken } from "../account/IdToken"; +import { JwtToken } from "../account/JwtToken"; import { ScopeSet } from "../request/ScopeSet"; /** @@ -18,13 +18,13 @@ export const ClientAuthErrorMessage = { code: "client_info_empty_error", desc: "The client info was empty. Please review the trace to determine the root cause." }, - idTokenParsingError: { - code: "id_token_parsing_error", - desc: "ID token cannot be parsed. Please review stack trace to determine root cause." + tokenParsingError: { + code: "token_parsing_error", + desc: "Token cannot be parsed. Please review stack trace to determine root cause." }, - nullOrEmptyIdToken: { - code: "null_or_empty_id_token", - desc: "The idToken is null or empty. Please review the trace to determine the root cause." + nullOrEmptyToken: { + code: "null_or_empty_token", + desc: "The token is null or empty. Please review the trace to determine the root cause." }, endpointResolutionError: { code: "endpoints_resolution_error", @@ -191,18 +191,18 @@ export class ClientAuthError extends AuthError { * Creates an error thrown when the id token extraction errors out. * @param err */ - static createIdTokenParsingError(caughtExtractionError: string): ClientAuthError { - return new ClientAuthError(ClientAuthErrorMessage.idTokenParsingError.code, - `${ClientAuthErrorMessage.idTokenParsingError.desc} Failed with error: ${caughtExtractionError}`); + static createTokenParsingError(caughtExtractionError: string): ClientAuthError { + return new ClientAuthError(ClientAuthErrorMessage.tokenParsingError.code, + `${ClientAuthErrorMessage.tokenParsingError.desc} Failed with error: ${caughtExtractionError}`); } /** * Creates an error thrown when the id token string is null or empty. * @param invalidRawTokenString */ - static createIdTokenNullOrEmptyError(invalidRawTokenString: string) : ClientAuthError { - return new ClientAuthError(ClientAuthErrorMessage.nullOrEmptyIdToken.code, - `${ClientAuthErrorMessage.nullOrEmptyIdToken.desc} Raw ID Token Value: ${invalidRawTokenString}`); + static createTokenNullOrEmptyError(invalidRawTokenString: string) : ClientAuthError { + return new ClientAuthError(ClientAuthErrorMessage.nullOrEmptyToken.code, + `${ClientAuthErrorMessage.nullOrEmptyToken.desc} Raw ID Token Value: ${invalidRawTokenString}`); } /** @@ -268,7 +268,7 @@ export class ClientAuthError extends AuthError { * Throws error if idToken is not correctly formed * @param idToken */ - static createInvalidIdTokenError(idToken: IdToken) : ClientAuthError { + static createInvalidIdTokenError(idToken: JwtToken) : ClientAuthError { return new ClientAuthError(ClientAuthErrorMessage.invalidIdToken.code, `${ClientAuthErrorMessage.invalidIdToken.desc} Given token: ${JSON.stringify(idToken)}`); } diff --git a/lib/msal-common/src/index.ts b/lib/msal-common/src/index.ts index f2578cb2dc..f35a44c82f 100644 --- a/lib/msal-common/src/index.ts +++ b/lib/msal-common/src/index.ts @@ -7,8 +7,8 @@ export { AuthOptions, SystemOptions, LoggerOptions, DEFAULT_SYSTEM_OPTIONS } fro export { ClientConfiguration } from "./config/ClientConfiguration"; // Account export { AccountInfo } from "./account/AccountInfo"; -export { IdToken } from "./account/IdToken"; -export { IdTokenClaims } from "./account/IdTokenClaims"; +export { JwtToken as IdToken } from "./account/JwtToken"; +export { TokenClaims as IdTokenClaims } from "./account/TokenClaims"; // Authority export { Authority } from "./authority/Authority"; export { CloudDiscoveryMetadata } from "./authority/CloudDiscoveryMetadata"; @@ -31,7 +31,7 @@ export { IUri } from "./url/IUri"; export { UrlString } from "./url/UrlString"; // Crypto Interface export { ICrypto, PkceCodes } from "./crypto/ICrypto"; - +export { SignedHttpRequest } from "./crypto/SignedHttpRequest"; // Request and Response export { BaseAuthRequest } from "./request/BaseAuthRequest"; export { AuthorizationUrlRequest } from "./request/AuthorizationUrlRequest"; diff --git a/lib/msal-common/src/request/AuthorizationCodeRequest.ts b/lib/msal-common/src/request/AuthorizationCodeRequest.ts index 4601c53f7b..e290215c12 100644 --- a/lib/msal-common/src/request/AuthorizationCodeRequest.ts +++ b/lib/msal-common/src/request/AuthorizationCodeRequest.ts @@ -15,6 +15,8 @@ import { AuthenticationType } from "../utils/Constants"; * - redirectUri - 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. * - code - The authorization_code that the user acquired in the first leg of the flow. * - codeVerifier - 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 + * - resourceRequestMethod - HTTP Request type used to request data from the resource (i.e. "GET", "POST", etc.). Used for proof-of-possession flows. + * - resourceRequestUri - URI that token will be used for. Used for proof-of-possession flows. */ export type AuthorizationCodeRequest = BaseAuthRequest & { redirectUri: string; diff --git a/lib/msal-common/src/request/AuthorizationUrlRequest.ts b/lib/msal-common/src/request/AuthorizationUrlRequest.ts index 44726d5c52..5622acb580 100644 --- a/lib/msal-common/src/request/AuthorizationUrlRequest.ts +++ b/lib/msal-common/src/request/AuthorizationUrlRequest.ts @@ -30,6 +30,8 @@ import { BaseAuthRequest } from "./BaseAuthRequest"; * - extraQueryParameters - String to string map of custom query parameters. * - claims - In cases where Azure AD tenant admin has enabled conditional access policies, and the policy has not been met, exceptions will contain claims that need to be consented to. * - nonce - 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. + * - resourceRequestMethod - HTTP Request type used to request data from the resource (i.e. "GET", "POST", etc.). Used for proof-of-possession flows. + * - resourceRequestUri - URI that token will be used for. Used for proof-of-possession flows. */ export type AuthorizationUrlRequest = BaseAuthRequest & { authenticationScheme?: AuthenticationType, diff --git a/lib/msal-common/src/request/BaseAuthRequest.ts b/lib/msal-common/src/request/BaseAuthRequest.ts index b6259d9f99..6b5300eaf0 100644 --- a/lib/msal-common/src/request/BaseAuthRequest.ts +++ b/lib/msal-common/src/request/BaseAuthRequest.ts @@ -8,9 +8,13 @@ * - scopes - Array of scopes the application is requesting access to. * - authority - 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. * - correlationId - Unique GUID set per request to trace a request end-to-end for telemetry purposes. + * - resourceRequestMethod - HTTP Request type used to request data from the resource (i.e. "GET", "POST", etc.). Used for proof-of-possession flows. + * - resourceRequestUri - URI that token will be used for. Used for proof-of-possession flows. */ export type BaseAuthRequest = { scopes: Array; authority?: string; correlationId?: string; + resourceRequestMethod?: string; + resourceRequestUri?: string; }; diff --git a/lib/msal-common/src/request/DeviceCodeRequest.ts b/lib/msal-common/src/request/DeviceCodeRequest.ts index e8ee02bc44..361cae329f 100644 --- a/lib/msal-common/src/request/DeviceCodeRequest.ts +++ b/lib/msal-common/src/request/DeviceCodeRequest.ts @@ -13,6 +13,8 @@ import { BaseAuthRequest } from "./BaseAuthRequest"; * - correlationId - Unique GUID set per request to trace a request end-to-end for telemetry purposes. * - deviceCodeCallback - Callback containing device code response. Message should be shown to end user. End user can then navigate to the verification_uri, input the user_code, and input credentials. * - cancel - Boolean to cancel polling of device code endpoint. While the user authenticates on a separate device, MSAL polls the the token endpoint of security token service for the interval specified in the device code response (usually 15 minutes). To stop polling and cancel the request, set cancel=true. + * - resourceRequestMethod - HTTP Request type used to request data from the resource (i.e. "GET", "POST", etc.). Used for proof-of-possession flows. + * - resourceRequestUri - URI that token will be used for. Used for proof-of-possession flows. */ export type DeviceCodeRequest = BaseAuthRequest & { deviceCodeCallback: (response: DeviceCodeResponse) => void; diff --git a/lib/msal-common/src/request/RefreshTokenRequest.ts b/lib/msal-common/src/request/RefreshTokenRequest.ts index 1e555f76b5..39346b60b7 100644 --- a/lib/msal-common/src/request/RefreshTokenRequest.ts +++ b/lib/msal-common/src/request/RefreshTokenRequest.ts @@ -11,6 +11,8 @@ import { BaseAuthRequest } from "./BaseAuthRequest"; * - authority - URL of the authority, the security token service (STS) from which MSAL will acquire tokens. * - correlationId - Unique GUID set per request to trace a request end-to-end for telemetry purposes. * - refreshToken - A refresh token returned from a previous request to the Identity provider. + * - resourceRequestMethod - HTTP Request type used to request data from the resource (i.e. "GET", "POST", etc.). Used for proof-of-possession flows. + * - resourceRequestUri - URI that token will be used for. Used for proof-of-possession flows. */ export type RefreshTokenRequest = BaseAuthRequest & { refreshToken: string; diff --git a/lib/msal-common/src/request/RequestParameterBuilder.ts b/lib/msal-common/src/request/RequestParameterBuilder.ts index cf08d13b07..3b6d91b44d 100644 --- a/lib/msal-common/src/request/RequestParameterBuilder.ts +++ b/lib/msal-common/src/request/RequestParameterBuilder.ts @@ -249,7 +249,7 @@ export class RequestParameterBuilder { * add pop_jwk to query params * @param cnfString */ - addPopToken(cnfString: string) { + addPopToken(cnfString: string): void { if (!StringUtils.isEmpty(cnfString)) { this.parameters.set(AADServerParamKeys.TOKEN_TYPE, AuthenticationType.POP); this.parameters.set(AADServerParamKeys.REQ_CNF, encodeURIComponent(cnfString)); diff --git a/lib/msal-common/src/request/SilentFlowRequest.ts b/lib/msal-common/src/request/SilentFlowRequest.ts index 76985e9214..14c7404ae3 100644 --- a/lib/msal-common/src/request/SilentFlowRequest.ts +++ b/lib/msal-common/src/request/SilentFlowRequest.ts @@ -13,6 +13,8 @@ import { BaseAuthRequest } from "./BaseAuthRequest"; * - correlationId - Unique GUID set per request to trace a request end-to-end for telemetry purposes. * - account - Account entity to lookup the credentials. * - forceRefresh - Forces silent requests to make network calls if true. + * - resourceRequestMethod - HTTP Request type used to request data from the resource (i.e. "GET", "POST", etc.). Used for proof-of-possession flows. + * - resourceRequestUri - URI that token will be used for. Used for proof-of-possession flows. */ export type SilentFlowRequest = BaseAuthRequest & { account: AccountInfo; diff --git a/lib/msal-common/src/response/AuthenticationResult.ts b/lib/msal-common/src/response/AuthenticationResult.ts index d422bb5c90..66215d7cea 100644 --- a/lib/msal-common/src/response/AuthenticationResult.ts +++ b/lib/msal-common/src/response/AuthenticationResult.ts @@ -30,6 +30,7 @@ export type AuthenticationResult = { accessToken: string; fromCache: boolean; expiresOn: Date; + tokenType: string; extExpiresOn?: Date; state?: string; familyId?: string; diff --git a/lib/msal-common/src/response/ResponseHandler.ts b/lib/msal-common/src/response/ResponseHandler.ts index 31969371a6..05b7ddb26b 100644 --- a/lib/msal-common/src/response/ResponseHandler.ts +++ b/lib/msal-common/src/response/ResponseHandler.ts @@ -10,7 +10,7 @@ import { StringUtils } from "../utils/StringUtils"; import { ServerAuthorizationCodeResponse } from "./ServerAuthorizationCodeResponse"; import { Logger } from "../logger/Logger"; import { ServerError } from "../error/ServerError"; -import { IdToken } from "../account/IdToken"; +import { JwtToken } from "../account/JwtToken"; import { ScopeSet } from "../request/ScopeSet"; import { TimeUtils } from "../utils/TimeUtils"; import { AuthenticationResult } from "./AuthenticationResult"; @@ -25,6 +25,8 @@ import { CacheRecord } from "../cache/entities/CacheRecord"; import { TrustedAuthority } from "../authority/TrustedAuthority"; import { CacheManager } from "../cache/CacheManager"; import { ProtocolUtils, LibraryStateObject, RequestStateObject } from "../utils/ProtocolUtils"; +import { AuthenticationType } from "../utils/Constants"; +import { PopTokenGenerator } from "../crypto/PopTokenGenerator"; /** * Class that handles response parsing. @@ -98,9 +100,15 @@ export class ResponseHandler { * @param serverTokenResponse * @param authority */ - handleServerTokenResponse(serverTokenResponse: ServerAuthorizationTokenResponse, authority: Authority, cachedNonce?: string, cachedState?: string): AuthenticationResult { + async handleServerTokenResponse( + serverTokenResponse: ServerAuthorizationTokenResponse, + authority: Authority, + cachedNonce?: string, + cachedState?: string, + resourceRequestMethod?: string, + resourceRequestUri?: string): Promise { // create an idToken object (not entity) - const idTokenObj = new IdToken(serverTokenResponse.id_token, this.cryptoObj); + const idTokenObj = new JwtToken(serverTokenResponse.id_token, this.cryptoObj); // token nonce check (TODO: Add a warning if no nonce is given?) if (!StringUtils.isEmpty(cachedNonce)) { @@ -118,7 +126,7 @@ export class ResponseHandler { const cacheRecord = this.generateCacheRecord(serverTokenResponse, idTokenObj, authority, requestStateObj && requestStateObj.libraryState); this.cacheStorage.saveCacheRecord(cacheRecord); - return ResponseHandler.generateAuthenticationResult(cacheRecord, idTokenObj, false, requestStateObj); + return await ResponseHandler.generateAuthenticationResult(this.cryptoObj, cacheRecord, idTokenObj, false, requestStateObj, resourceRequestMethod, resourceRequestUri); } /** @@ -127,7 +135,7 @@ export class ResponseHandler { * @param idTokenObj * @param authority */ - private generateCacheRecord(serverTokenResponse: ServerAuthorizationTokenResponse, idTokenObj: IdToken, authority: Authority, libraryState?: LibraryStateObject): CacheRecord { + private generateCacheRecord(serverTokenResponse: ServerAuthorizationTokenResponse, idTokenObj: JwtToken, authority: Authority, libraryState?: LibraryStateObject): CacheRecord { // Account const cachedAccount = this.generateAccountEntity( serverTokenResponse, @@ -175,7 +183,8 @@ export class ResponseHandler { idTokenObj.claims.tid, responseScopes.printScopesLowerCase(), tokenExpirationSeconds, - extendedTokenExpirationSeconds + extendedTokenExpirationSeconds, + serverTokenResponse.token_type ); } @@ -200,7 +209,7 @@ export class ResponseHandler { * @param idToken * @param authority */ - private generateAccountEntity(serverTokenResponse: ServerAuthorizationTokenResponse, idToken: IdToken, authority: Authority): AccountEntity { + private generateAccountEntity(serverTokenResponse: ServerAuthorizationTokenResponse, idToken: JwtToken, authority: Authority): AccountEntity { const authorityType = authority.authorityType; if (StringUtils.isEmpty(serverTokenResponse.client_info)) { @@ -222,33 +231,41 @@ export class ResponseHandler { * @param fromTokenCache * @param stateString */ - static generateAuthenticationResult(cacheRecord: CacheRecord, idTokenObj: IdToken, fromTokenCache: boolean, requestState?: RequestStateObject): AuthenticationResult { + static async generateAuthenticationResult(cryptoObj: ICrypto, cacheRecord: CacheRecord, idTokenObj: JwtToken, fromTokenCache: boolean, requestState?: RequestStateObject, resourceRequestMethod?: string, resourceRequestUri?: string): Promise { let accessToken: string = ""; let responseScopes: Array = []; let expiresOn: Date = null; let extExpiresOn: Date = null; let familyId: string = null; if (cacheRecord.accessToken) { - accessToken = cacheRecord.accessToken.secret; + if (cacheRecord.accessToken.tokenType === AuthenticationType.POP) { + const popTokenGenerator: PopTokenGenerator = new PopTokenGenerator(cryptoObj); + accessToken = await popTokenGenerator.signPopToken(cacheRecord.accessToken.secret, resourceRequestMethod, resourceRequestUri); + } else { + accessToken = cacheRecord.accessToken.secret; + } responseScopes = ScopeSet.fromString(cacheRecord.accessToken.target).asArray(); expiresOn = new Date(Number(cacheRecord.accessToken.expiresOn) * 1000); extExpiresOn = new Date(Number(cacheRecord.accessToken.extendedExpiresOn) * 1000); } + if (cacheRecord.refreshToken) { familyId = cacheRecord.refreshToken.familyId || null; } + return { uniqueId: idTokenObj.claims.oid || idTokenObj.claims.sub, tenantId: idTokenObj.claims.tid, scopes: responseScopes, account: cacheRecord.account.getAccountInfo(), - idToken: idTokenObj.rawIdToken, + idToken: idTokenObj.rawToken, idTokenClaims: idTokenObj.claims, accessToken: accessToken, fromCache: fromTokenCache, expiresOn: expiresOn, extExpiresOn: extExpiresOn, familyId: familyId, + tokenType: cacheRecord.accessToken.tokenType, state: requestState ? requestState.userRequestState : "" }; } diff --git a/lib/msal-common/src/url/IUri.ts b/lib/msal-common/src/url/IUri.ts index f31cdec495..2a97323ea7 100644 --- a/lib/msal-common/src/url/IUri.ts +++ b/lib/msal-common/src/url/IUri.ts @@ -13,4 +13,5 @@ export interface IUri { Search: string; Hash: string; PathSegments: string[]; + QueryString: string; } diff --git a/lib/msal-common/src/url/UrlString.ts b/lib/msal-common/src/url/UrlString.ts index ebd134758a..aaf6fb13d1 100644 --- a/lib/msal-common/src/url/UrlString.ts +++ b/lib/msal-common/src/url/UrlString.ts @@ -131,7 +131,8 @@ export class UrlString { const urlComponents = { Protocol: match[1], HostNameAndPort: match[4], - AbsolutePath: match[5] + AbsolutePath: match[5], + QueryString: match[7] } as IUri; let pathSegments = urlComponents.AbsolutePath.split("/"); diff --git a/lib/msal-common/src/utils/StringUtils.ts b/lib/msal-common/src/utils/StringUtils.ts index b424b11534..38d0115262 100644 --- a/lib/msal-common/src/utils/StringUtils.ts +++ b/lib/msal-common/src/utils/StringUtils.ts @@ -17,12 +17,12 @@ export class StringUtils { */ static decodeJwt(jwtToken: string): DecodedJwt { if (StringUtils.isEmpty(jwtToken)) { - throw ClientAuthError.createIdTokenNullOrEmptyError(jwtToken); + throw ClientAuthError.createTokenNullOrEmptyError(jwtToken); } const idTokenPartsRegex = /^([^\.\s]*)\.([^\.\s]+)\.([^\.\s]*)$/; const matches = idTokenPartsRegex.exec(jwtToken); if (!matches || matches.length < 4) { - throw ClientAuthError.createIdTokenParsingError(`Given token is malformed: ${JSON.stringify(jwtToken)}`); + throw ClientAuthError.createTokenParsingError(`Given token is malformed: ${JSON.stringify(jwtToken)}`); } const crackedToken: DecodedJwt = { header: matches[1], diff --git a/samples/msal-browser-samples/VanillaJSTestApp2.0/app/default/authConfig.js b/samples/msal-browser-samples/VanillaJSTestApp2.0/app/default/authConfig.js index cfcb622a20..7913deb82d 100644 --- a/samples/msal-browser-samples/VanillaJSTestApp2.0/app/default/authConfig.js +++ b/samples/msal-browser-samples/VanillaJSTestApp2.0/app/default/authConfig.js @@ -33,18 +33,20 @@ const msalConfig = { } }; -// Add here scopes for id token to be used at MS Identity Platform endpoints. -const loginRequest = { - scopes: ["User.Read"], - authenticationType: "pop" -}; - // Add here the endpoints for MS Graph API services you would like to use. const graphConfig = { graphMeEndpoint: "https://graph.microsoft-ppe.com/v1.0/me", graphMailEndpoint: "https://graph.microsoft-ppe.com/v1.0/me/messages" }; +// Add here scopes for id token to be used at MS Identity Platform endpoints. +const loginRequest = { + scopes: ["User.Read"], + authenticationScheme: "pop", + resourceRequestMethod: "POST", + resourceRequestUri: graphConfig.graphMeEndpoint +}; + // Add here scopes for access token to be used at MS Graph API endpoints. const tokenRequest = { scopes: ["Mail.Read"], From efe1dea288a1667f50282329c39c8e05297c2909 Mon Sep 17 00:00:00 2001 From: Prithvi Kanherkar Date: Fri, 14 Aug 2020 18:28:44 -0700 Subject: [PATCH 02/17] Revert "Returns token as Proof-of-possession token" This reverts commit c5608c8b1f4b74078597628fe22d905f840db3e3. --- lib/msal-browser/src/cache/DatabaseStorage.ts | 84 ------------------- lib/msal-browser/src/crypto/BrowserCrypto.ts | 73 +--------------- lib/msal-browser/src/crypto/CryptoOps.ts | 76 +++-------------- .../src/utils/BrowserStringUtils.ts | 13 --- .../src/account/{JwtToken.ts => IdToken.ts} | 28 +++---- .../{TokenClaims.ts => IdTokenClaims.ts} | 7 +- .../src/cache/entities/AccessTokenEntity.ts | 7 +- .../src/cache/entities/AccountEntity.ts | 6 +- .../src/client/AuthorizationCodeClient.ts | 6 +- .../src/client/DeviceCodeClient.ts | 10 +-- .../src/client/RefreshTokenClient.ts | 4 +- .../src/client/SilentFlowClient.ts | 26 ++---- .../src/config/ClientConfiguration.ts | 4 - lib/msal-common/src/crypto/ICrypto.ts | 11 +-- .../src/crypto/PopTokenGenerator.ts | 24 +----- .../src/crypto/SignedHttpRequest.ts | 15 ---- lib/msal-common/src/error/ClientAuthError.ts | 28 +++---- lib/msal-common/src/index.ts | 6 +- .../src/request/AuthorizationCodeRequest.ts | 2 - .../src/request/AuthorizationUrlRequest.ts | 2 - .../src/request/BaseAuthRequest.ts | 4 - .../src/request/DeviceCodeRequest.ts | 2 - .../src/request/RefreshTokenRequest.ts | 2 - .../src/request/RequestParameterBuilder.ts | 2 +- .../src/request/SilentFlowRequest.ts | 2 - .../src/response/AuthenticationResult.ts | 1 - .../src/response/ResponseHandler.ts | 37 +++----- lib/msal-common/src/url/IUri.ts | 1 - lib/msal-common/src/url/UrlString.ts | 3 +- lib/msal-common/src/utils/StringUtils.ts | 4 +- .../app/default/authConfig.js | 14 ++-- 31 files changed, 94 insertions(+), 410 deletions(-) delete mode 100644 lib/msal-browser/src/cache/DatabaseStorage.ts rename lib/msal-common/src/account/{JwtToken.ts => IdToken.ts} (54%) rename lib/msal-common/src/account/{TokenClaims.ts => IdTokenClaims.ts} (79%) delete mode 100644 lib/msal-common/src/crypto/SignedHttpRequest.ts diff --git a/lib/msal-browser/src/cache/DatabaseStorage.ts b/lib/msal-browser/src/cache/DatabaseStorage.ts deleted file mode 100644 index 03d227b2bb..0000000000 --- a/lib/msal-browser/src/cache/DatabaseStorage.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. - */ - -interface IDBOpenDBRequestEvent extends Event { - target: IDBOpenDBRequest & EventTarget; -} - -interface IDBOpenOnUpgradeNeededEvent extends IDBVersionChangeEvent { - target: IDBOpenDBRequest & EventTarget; -} - -interface IDBRequestEvent extends Event { - target: IDBRequest & EventTarget; -} - -/** - * Storage wrapper for IndexedDB storage in browsers: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API - */ -export class DatabaseStorage{ - private _db : IDBDatabase; - private _dbName: string; - private _tableName: string; - private _version: number; - - constructor(dbName: string, tableName: string, version: number) { - this._dbName = dbName; - this._tableName = tableName; - this._version = version; - } - - /** - * Opens IndexedDB instance. - */ - async open(): Promise { - return new Promise((resolve, reject) => { - // TODO: Add timeouts? - const openDB = window.indexedDB.open(this._dbName, this._version); - openDB.addEventListener("upgradeneeded", (e: IDBOpenOnUpgradeNeededEvent) => { - e.target.result.createObjectStore(this._tableName); - }); - openDB.addEventListener("success", (e: IDBOpenDBRequestEvent) => { - this._db = e.target.result; - resolve(); - }); - - openDB.addEventListener("error", error => reject(error)); - }); - } - - /** - * Retrieves item from IndexedDB instance. - * @param key - */ - async get(key: string): Promise { - return new Promise((resolve, reject) => { - // TODO: Add timeouts? - const transaction = this._db.transaction([this._tableName], "readonly"); - - const objectStore = transaction.objectStore(this._tableName); - const dbGet = objectStore.get(key); - dbGet.addEventListener("success", (e: IDBRequestEvent) => resolve(e.target.result)); - dbGet.addEventListener("error", e => reject(e)); - }); - } - - /** - * Adds item to IndexedDB under given key - * @param key - * @param payload - */ - async put(key: string, payload: T): Promise { - return new Promise((resolve: any, reject: any) => { - // TODO: Add timeouts? - const transaction = this._db.transaction([this._tableName], "readwrite"); - const objectStore = transaction.objectStore(this._tableName); - - const dbPut = objectStore.put(payload, key); - dbPut.addEventListener("success", (e: IDBRequestEvent) => resolve(e.target.result)); - dbPut.addEventListener("error", e => reject(e)); - }); - } -} diff --git a/lib/msal-browser/src/crypto/BrowserCrypto.ts b/lib/msal-browser/src/crypto/BrowserCrypto.ts index 7946e2d409..6bddea2b48 100644 --- a/lib/msal-browser/src/crypto/BrowserCrypto.ts +++ b/lib/msal-browser/src/crypto/BrowserCrypto.ts @@ -49,6 +49,7 @@ export class BrowserCrypto { modulusLength: keygenConfigModulusLength, publicExponent: keygenConfigPublicExponent }; + console.log(this._keygenAlgorithmOptions); } /** @@ -95,33 +96,6 @@ export class BrowserCrypto { return this.hasIECrypto() ? this.msCryptoExportKey(key, format) : window.crypto.subtle.exportKey(format, key); } - /** - * Imports key as given KeyFormat, can set extractable and usages. - * @param key - * @param format - * @param extractable - * @param usages - */ - async importKey(key: JsonWebKey, format: KeyFormat, extractable: boolean, usages: Array): Promise { - const keyString = BrowserCrypto.getJwkString(key); - const keyBuffer = BrowserStringUtils.stringToArrayBuffer(keyString); - - return this.hasIECrypto() ? - this.msCryptoImportKey(keyBuffer, format, extractable, usages) - : window.crypto.subtle.importKey(format, key, this._keygenAlgorithmOptions, extractable, usages); - } - - /** - * Signs given data with given key - * @param key - * @param data - */ - async sign(key: CryptoKey, data: ArrayBuffer): Promise { - return this.hasIECrypto() ? - this.msCryptoSign(key, data) - : window.crypto.subtle.sign(this._keygenAlgorithmOptions, key, data); - } - /** * Check whether IE crypto or other browser cryptography is available. */ @@ -169,11 +143,6 @@ export class BrowserCrypto { }); } - /** - * IE Helper function for generating a keypair - * @param extractable - * @param usages - */ private async msCryptoGenerateKey(extractable: boolean, usages: Array): Promise { return new Promise((resolve: any, reject: any) => { const msGenerateKey = window["msCrypto"].subtle.generateKey(this._keygenAlgorithmOptions, extractable, usages); @@ -188,7 +157,7 @@ export class BrowserCrypto { } /** - * IE Helper function for exportKey + * IE Helper function for exporting keys * @param key * @param format */ @@ -218,44 +187,6 @@ export class BrowserCrypto { }); } - /** - * IE Helper function for importKey - * @param key - * @param format - * @param extractable - * @param usages - */ - private async msCryptoImportKey(keyBuffer: ArrayBuffer, format: KeyFormat, extractable: boolean, usages: Array): Promise { - return new Promise((resolve: any, reject: any) => { - const msImportKey = window["msCrypto"].subtle.importKey(format, keyBuffer, this._keygenAlgorithmOptions, extractable, usages); - msImportKey.addEventListener("complete", (e: { target: { result: CryptoKey | PromiseLike; }; }) => { - resolve(e.target.result); - }); - - msImportKey.addEventListener("error", (error: any) => { - reject(error); - }); - }); - } - - /** - * IE Helper function for sign JWT - * @param key - * @param data - */ - private async msCryptoSign(key: CryptoKey, data: ArrayBuffer): Promise { - return new Promise((resolve: any, reject: any) => { - const msSign = window["msCrypto"].subtle.sign(this._keygenAlgorithmOptions, key, data); - msSign.addEventListener("complete", (e: { target: { result: ArrayBuffer | PromiseLike; }; }) => { - resolve(e.target.result); - }); - - msSign.addEventListener("error", (error: any) => { - reject(error); - }); - }); - } - /** * Returns stringified jwk. * @param jwk diff --git a/lib/msal-browser/src/crypto/CryptoOps.ts b/lib/msal-browser/src/crypto/CryptoOps.ts index d3e638e5fc..99113c9118 100644 --- a/lib/msal-browser/src/crypto/CryptoOps.ts +++ b/lib/msal-browser/src/crypto/CryptoOps.ts @@ -2,22 +2,14 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ -import { ICrypto, PkceCodes, SignedHttpRequest } from "@azure/msal-common"; +import { ICrypto, PkceCodes } from "@azure/msal-common"; import { GuidGenerator } from "./GuidGenerator"; import { Base64Encode } from "../encode/Base64Encode"; import { Base64Decode } from "../encode/Base64Decode"; import { PkceGenerator } from "./PkceGenerator"; import { BrowserCrypto, KeyFormat } from "./BrowserCrypto"; -import { DatabaseStorage } from "../cache/DatabaseStorage"; import { BrowserStringUtils } from "../utils/BrowserStringUtils"; -type CachedKeyPair = { - publicKey: CryptoKey, - privateKey: CryptoKey, - requestMethod: string, - requestUri: string -}; - /** * This class implements MSAL's crypto interface, which allows it to perform base64 encoding and decoding, generating cryptographically random GUIDs and * implementing Proof Key for Code Exchange specs for the OAuth Authorization Code Flow using PKCE (rfc here: https://tools.ietf.org/html/rfc7636). @@ -34,11 +26,6 @@ export class CryptoOps implements ICrypto { private static EXTRACTABLE: boolean = true; private static POP_HASH_LENGTH = 43; // 256 bit digest / 6 bits per char = 43 - private static DB_VERSION = 1; - private static DB_NAME = "msal.db"; - private static TABLE_NAME =`${CryptoOps.DB_NAME}.keys`; - private _cache: DatabaseStorage; - constructor() { // Browser crypto needs to be validated first before any other classes can be set. this.browserCrypto = new BrowserCrypto(); @@ -46,8 +33,16 @@ export class CryptoOps implements ICrypto { this.b64Decode = new Base64Decode(); this.guidGenerator = new GuidGenerator(this.browserCrypto); this.pkceGenerator = new PkceGenerator(this.browserCrypto); - this._cache = new DatabaseStorage(CryptoOps.DB_NAME, CryptoOps.TABLE_NAME, CryptoOps.DB_VERSION); - this._cache.open(); + } + + async getPublicKeyThumprint(): Promise { + const keyPair = await this.browserCrypto.generateKeyPair(CryptoOps.EXTRACTABLE, CryptoOps.POP_KEY_USAGES); + // TODO: Store keypair + const publicKeyJwk: JsonWebKey = await this.browserCrypto.exportKey(keyPair.publicKey, KeyFormat.jwk); + const publicJwkString: string = BrowserCrypto.getJwkString(publicKeyJwk); + const publicJwkBuffer: ArrayBuffer = await this.browserCrypto.sha256Digest(publicJwkString); + const publicJwkDigest: string = this.b64Encode.urlEncodeArr(new Uint8Array(publicJwkBuffer)); + return this.base64Encode(publicJwkDigest).substr(0, CryptoOps.POP_HASH_LENGTH); } /** @@ -80,53 +75,4 @@ export class CryptoOps implements ICrypto { async generatePkceCodes(): Promise { return this.pkceGenerator.generateCodes(); } - - /** - * Generates a keypair, stores it and returns a thumbprint - * @param resourceRequestMethod - * @param resourceRequestUri - */ - async getPublicKeyThumprint(resourceRequestMethod: string, resourceRequestUri: string): Promise { - const keyPair = await this.browserCrypto.generateKeyPair(CryptoOps.EXTRACTABLE, CryptoOps.POP_KEY_USAGES); - const publicKeyJwk: JsonWebKey = await this.browserCrypto.exportKey(keyPair.publicKey, KeyFormat.jwk); - const privateKeyJwk: JsonWebKey = await this.browserCrypto.exportKey(keyPair.privateKey, KeyFormat.jwk); - const publicJwkString: string = BrowserCrypto.getJwkString(publicKeyJwk); - const publicJwkBuffer: ArrayBuffer = await this.browserCrypto.sha256Digest(publicJwkString); - const publicJwkDigest: string = this.b64Encode.urlEncodeArr(new Uint8Array(publicJwkBuffer)); - const unextractablePrivateKey: CryptoKey = await this.browserCrypto.importKey(privateKeyJwk, KeyFormat.jwk, false, ["sign"]); - const publicKeyHash = this.base64Encode(publicJwkDigest).substr(0, CryptoOps.POP_HASH_LENGTH); - this._cache.put(publicKeyHash, { - privateKey: unextractablePrivateKey, - publicKey: keyPair.publicKey, - requestMethod: resourceRequestMethod, - requestUri: resourceRequestUri - }); - return publicKeyHash; - } - - /** - * Signs the given object as a jwt payload with private key retrieved by given kid. - * @param payload - * @param kid - */ - async signJwt(payload: SignedHttpRequest, kid: string): Promise { - const cachedKeyPair: CachedKeyPair = await this._cache.get(kid); - const publicKeyJwk = await this.browserCrypto.exportKey(cachedKeyPair.publicKey, KeyFormat.jwk); - const publicKeyJwkString = BrowserCrypto.getJwkString(publicKeyJwk); - - const header = { - alg: publicKeyJwk.alg, - type: KeyFormat.jwk, - jwk: JSON.parse(publicKeyJwkString) - }; - - const encodedHeader = this.b64Encode.urlEncode(JSON.stringify(header)); - const encodedPayload = this.b64Encode.urlEncode(JSON.stringify(payload)); - const tokenString = `${encodedHeader}.${encodedPayload}`; - const tokenBuffer = BrowserStringUtils.stringToArrayBuffer(tokenString); - const signatureBuffer = await this.browserCrypto.sign(cachedKeyPair.privateKey, tokenBuffer); - const encodedSignature = this.b64Encode.urlEncode(BrowserStringUtils.utf8ArrToString(new Uint8Array(signatureBuffer))); - - return `${tokenString}.${encodedSignature}`; - } } diff --git a/lib/msal-browser/src/utils/BrowserStringUtils.ts b/lib/msal-browser/src/utils/BrowserStringUtils.ts index 6f0d8673ba..b60dcb386e 100644 --- a/lib/msal-browser/src/utils/BrowserStringUtils.ts +++ b/lib/msal-browser/src/utils/BrowserStringUtils.ts @@ -68,19 +68,6 @@ export class BrowserStringUtils { return aBytes; } - /** - * Converst string to ArrayBuffer - * @param dataString - */ - static stringToArrayBuffer(dataString: string): ArrayBuffer { - const data = new ArrayBuffer(dataString.length); - const dataView = new Uint8Array(data); - for (let i: number = 0; i < dataString.length; i++) { - dataView[i] = dataString.charCodeAt(i); - } - return data; - } - /** * Converts Uint8Array to a string * @param aBytes diff --git a/lib/msal-common/src/account/JwtToken.ts b/lib/msal-common/src/account/IdToken.ts similarity index 54% rename from lib/msal-common/src/account/JwtToken.ts rename to lib/msal-common/src/account/IdToken.ts index b1375941e6..36bcb296fe 100644 --- a/lib/msal-common/src/account/JwtToken.ts +++ b/lib/msal-common/src/account/IdToken.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ -import { TokenClaims } from "./TokenClaims"; +import { IdTokenClaims } from "./IdTokenClaims"; import { DecodedJwt } from "./DecodedJwt"; import { ClientAuthError } from "../error/ClientAuthError"; import { StringUtils } from "../utils/StringUtils"; @@ -11,19 +11,19 @@ import { ICrypto } from "../crypto/ICrypto"; /** * Id Token representation class. Parses id token string and generates claims object. */ -export class JwtToken { +export class IdToken { // Raw Id Token string - rawToken: string; + rawIdToken: string; // Claims inside Id Token - claims: TokenClaims; - constructor(rawToken: string, crypto: ICrypto) { - if (StringUtils.isEmpty(rawToken)) { - throw ClientAuthError.createTokenNullOrEmptyError(rawToken); + claims: IdTokenClaims; + constructor(rawIdToken: string, crypto: ICrypto) { + if (StringUtils.isEmpty(rawIdToken)) { + throw ClientAuthError.createIdTokenNullOrEmptyError(rawIdToken); } - this.rawToken = rawToken; - this.claims = JwtToken.extractTokenClaims(rawToken, crypto); + this.rawIdToken = rawIdToken; + this.claims = IdToken.extractIdToken(rawIdToken, crypto); } /** @@ -31,19 +31,19 @@ export class JwtToken { * * @param encodedIdToken */ - static extractTokenClaims(encodedIdToken: string, crypto: ICrypto): TokenClaims { + static extractIdToken(encodedIdToken: string, crypto: ICrypto): IdTokenClaims { // id token will be decoded to get the username const decodedToken: DecodedJwt = StringUtils.decodeJwt(encodedIdToken); if (!decodedToken) { return null; } try { - const base64TokenPayload = decodedToken.JWSPayload; + const base64IdTokenPayload = decodedToken.JWSPayload; // base64Decode() should throw an error if there is an issue - const base64Decoded = crypto.base64Decode(base64TokenPayload); - return JSON.parse(base64Decoded) as TokenClaims; + const base64Decoded = crypto.base64Decode(base64IdTokenPayload); + return JSON.parse(base64Decoded) as IdTokenClaims; } catch (err) { - throw ClientAuthError.createTokenParsingError(err); + throw ClientAuthError.createIdTokenParsingError(err); } } } diff --git a/lib/msal-common/src/account/TokenClaims.ts b/lib/msal-common/src/account/IdTokenClaims.ts similarity index 79% rename from lib/msal-common/src/account/TokenClaims.ts rename to lib/msal-common/src/account/IdTokenClaims.ts index 066f5e48ce..a6eda5709b 100644 --- a/lib/msal-common/src/account/TokenClaims.ts +++ b/lib/msal-common/src/account/IdTokenClaims.ts @@ -6,7 +6,7 @@ /** * Type which describes Id Token claims known by MSAL. */ -export type TokenClaims = { +export type IdTokenClaims = { iss?: string, oid?: string, sub?: string, @@ -19,8 +19,5 @@ export type TokenClaims = { exp?: number, home_oid?: string, sid?: string, - cloud_instance_host_name?: string, - cnf?: { - kid: string; - }; + cloud_instance_host_name?: string }; diff --git a/lib/msal-common/src/cache/entities/AccessTokenEntity.ts b/lib/msal-common/src/cache/entities/AccessTokenEntity.ts index 1bd3296cd3..52ddcfa309 100644 --- a/lib/msal-common/src/cache/entities/AccessTokenEntity.ts +++ b/lib/msal-common/src/cache/entities/AccessTokenEntity.ts @@ -4,9 +4,8 @@ */ import { CredentialEntity } from "./CredentialEntity"; -import { CredentialType, AuthenticationType } from "../../utils/Constants"; +import { CredentialType } from "../../utils/Constants"; import { TimeUtils } from "../../utils/TimeUtils"; -import { StringUtils } from "../../utils/StringUtils"; /** * ACCESS_TOKEN Credential Type @@ -61,8 +60,7 @@ export class AccessTokenEntity extends CredentialEntity { tenantId: string, scopes: string, expiresOn: number, - extExpiresOn: number, - tokenType?: string + extExpiresOn: number ): AccessTokenEntity { const atEntity: AccessTokenEntity = new AccessTokenEntity(); @@ -83,7 +81,6 @@ export class AccessTokenEntity extends CredentialEntity { atEntity.realm = tenantId; atEntity.target = scopes; - atEntity.tokenType = StringUtils.isEmpty(tokenType) ? AuthenticationType.BEARER : tokenType; return atEntity; } } diff --git a/lib/msal-common/src/cache/entities/AccountEntity.ts b/lib/msal-common/src/cache/entities/AccountEntity.ts index a1402bd60d..d2820a8ca5 100644 --- a/lib/msal-common/src/cache/entities/AccountEntity.ts +++ b/lib/msal-common/src/cache/entities/AccountEntity.ts @@ -9,7 +9,7 @@ import { CacheType, } from "../../utils/Constants"; import { Authority } from "../../authority/Authority"; -import { JwtToken } from "../../account/JwtToken"; +import { IdToken } from "../../account/IdToken"; import { ICrypto } from "../../crypto/ICrypto"; import { buildClientInfo } from "../../account/ClientInfo"; import { StringUtils } from "../../utils/StringUtils"; @@ -125,7 +125,7 @@ export class AccountEntity { static createAccount( clientInfo: string, authority: Authority, - idToken: JwtToken, + idToken: IdToken, crypto: ICrypto ): AccountEntity { const account: AccountEntity = new AccountEntity(); @@ -164,7 +164,7 @@ export class AccountEntity { */ static createADFSAccount( authority: Authority, - idToken: JwtToken + idToken: IdToken ): AccountEntity { const account: AccountEntity = new AccountEntity(); diff --git a/lib/msal-common/src/client/AuthorizationCodeClient.ts b/lib/msal-common/src/client/AuthorizationCodeClient.ts index 14de0bca3a..c70715a22c 100644 --- a/lib/msal-common/src/client/AuthorizationCodeClient.ts +++ b/lib/msal-common/src/client/AuthorizationCodeClient.ts @@ -71,7 +71,9 @@ export class AuthorizationCodeClient extends BaseClient { // Validate response. This function throws a server error if an error is returned by the server. responseHandler.validateTokenResponse(response.body); - return await responseHandler.handleServerTokenResponse(response.body, this.authority, cachedNonce, cachedState, request.resourceRequestMethod, request.resourceRequestUri); + const tokenResponse = responseHandler.handleServerTokenResponse(response.body, this.authority, cachedNonce, cachedState); + + return tokenResponse; } /** @@ -174,7 +176,7 @@ export class AuthorizationCodeClient extends BaseClient { if (request.authenticationScheme === AuthenticationType.POP) { const popTokenGenerator = new PopTokenGenerator(this.cryptoUtils); - parameterBuilder.addPopToken(await popTokenGenerator.generateCnf(request.resourceRequestMethod, request.resourceRequestUri)); + parameterBuilder.addPopToken(await popTokenGenerator.generateCnf()); } const correlationId = request.correlationId || this.config.cryptoInterface.createNewGuid(); diff --git a/lib/msal-common/src/client/DeviceCodeClient.ts b/lib/msal-common/src/client/DeviceCodeClient.ts index e63f2d0d2f..cb837743a1 100644 --- a/lib/msal-common/src/client/DeviceCodeClient.ts +++ b/lib/msal-common/src/client/DeviceCodeClient.ts @@ -47,14 +47,12 @@ export class DeviceCodeClient extends BaseClient { // Validate response. This function throws a server error if an error is returned by the server. responseHandler.validateTokenResponse(response); - return await responseHandler.handleServerTokenResponse( + const tokenResponse = responseHandler.handleServerTokenResponse( response, - this.authority, - "", - "", - request.resourceRequestMethod, - request.resourceRequestUri + this.authority ); + + return tokenResponse; } /** diff --git a/lib/msal-common/src/client/RefreshTokenClient.ts b/lib/msal-common/src/client/RefreshTokenClient.ts index f6ffbf6b31..1c2b03379c 100644 --- a/lib/msal-common/src/client/RefreshTokenClient.ts +++ b/lib/msal-common/src/client/RefreshTokenClient.ts @@ -34,10 +34,12 @@ export class RefreshTokenClient extends BaseClient { ); responseHandler.validateTokenResponse(response.body); - return await responseHandler.handleServerTokenResponse( + const tokenResponse = responseHandler.handleServerTokenResponse( response.body, this.authority ); + + return tokenResponse; } private async executeTokenRequest(request: RefreshTokenRequest, authority: Authority) diff --git a/lib/msal-common/src/client/SilentFlowClient.ts b/lib/msal-common/src/client/SilentFlowClient.ts index 584612b67e..c9853864d3 100644 --- a/lib/msal-common/src/client/SilentFlowClient.ts +++ b/lib/msal-common/src/client/SilentFlowClient.ts @@ -12,7 +12,7 @@ import { IdTokenEntity } from "../cache/entities/IdTokenEntity"; import { AccessTokenEntity } from "../cache/entities/AccessTokenEntity"; import { RefreshTokenEntity } from "../cache/entities/RefreshTokenEntity"; import { ScopeSet } from "../request/ScopeSet"; -import { JwtToken } from "../account/JwtToken"; +import { IdToken } from "../account/IdToken"; import { TimeUtils } from "../utils/TimeUtils"; import { RefreshTokenRequest } from "../request/RefreshTokenRequest"; import { RefreshTokenClient } from "./RefreshTokenClient"; @@ -77,22 +77,14 @@ export class SilentFlowClient extends BaseClient { // Return tokens from cache this.config.serverTelemetryManager.incrementCacheHits(); const cachedIdToken = this.readIdTokenFromCache(homeAccountId, environment, cachedAccount.realm); - const idTokenObj = new JwtToken(cachedIdToken.secret, this.config.cryptoInterface); - - return await ResponseHandler.generateAuthenticationResult( - this.cryptoUtils, - { - account: cachedAccount, - accessToken: cachedAccessToken, - idToken: cachedIdToken, - refreshToken: cachedRefreshToken - }, - idTokenObj, - true, - null, - request.resourceRequestMethod, - request.resourceRequestUri - ); + const idTokenObj = new IdToken(cachedIdToken.secret, this.config.cryptoInterface); + + return ResponseHandler.generateAuthenticationResult({ + account: cachedAccount, + accessToken: cachedAccessToken, + idToken: cachedIdToken, + refreshToken: cachedRefreshToken + }, idTokenObj, true); } /** diff --git a/lib/msal-common/src/config/ClientConfiguration.ts b/lib/msal-common/src/config/ClientConfiguration.ts index bed2869206..957322ecd3 100644 --- a/lib/msal-common/src/config/ClientConfiguration.ts +++ b/lib/msal-common/src/config/ClientConfiguration.ts @@ -149,10 +149,6 @@ const DEFAULT_CRYPTO_IMPLEMENTATION: ICrypto = { async getPublicKeyThumprint(): Promise { const notImplErr = "Crypto interface - getPublicKeyThumprint() has not been implemented"; throw AuthError.createUnexpectedError(notImplErr); - }, - async signJwt(): Promise { - const notImplErr = "Crypto interface - signJwt() has not been implemented"; - throw AuthError.createUnexpectedError(notImplErr); } }; diff --git a/lib/msal-common/src/crypto/ICrypto.ts b/lib/msal-common/src/crypto/ICrypto.ts index f32f7eb4b3..c49cf2ce0b 100644 --- a/lib/msal-common/src/crypto/ICrypto.ts +++ b/lib/msal-common/src/crypto/ICrypto.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. */ -import { SignedHttpRequest } from "./SignedHttpRequest"; - /** * The PkceCodes type describes the structure * of objects that contain PKCE code @@ -39,13 +37,6 @@ export interface ICrypto { generatePkceCodes(): Promise; /** * Generates an JWK RSA S256 Thumbprint - * @param resourceRequestMethod - * @param resourceRequestUri - */ - getPublicKeyThumprint(resourceRequestMethod: string, resourceRequestUri: string): Promise; - /** - * Returns a signed proof-of-possession token with a given acces token that contains a cnf claim with the required kid. - * @param accessToken */ - signJwt(payload: SignedHttpRequest, kid: string): Promise; + getPublicKeyThumprint(): Promise; } diff --git a/lib/msal-common/src/crypto/PopTokenGenerator.ts b/lib/msal-common/src/crypto/PopTokenGenerator.ts index a2eb3d217e..628579316a 100644 --- a/lib/msal-common/src/crypto/PopTokenGenerator.ts +++ b/lib/msal-common/src/crypto/PopTokenGenerator.ts @@ -1,9 +1,4 @@ import { ICrypto } from "./ICrypto"; -import { JwtToken } from "../account/JwtToken"; -import { TokenClaims } from "../account/TokenClaims"; -import { TimeUtils } from "../utils/TimeUtils"; -import { UrlString } from "../url/UrlString"; -import { IUri } from "../url/IUri"; /** * See eSTS docs for more info. @@ -30,27 +25,12 @@ export class PopTokenGenerator { this.cryptoUtils = cryptoUtils; } - async generateCnf(resourceRequestMethod: string, resourceRequestUri: string): Promise { + async generateCnf(): Promise { const reqCnf: ReqCnf = { - kid: await this.cryptoUtils.getPublicKeyThumprint(resourceRequestMethod, resourceRequestUri), + kid: await this.cryptoUtils.getPublicKeyThumprint(), xms_ksl: KeyLocation.SW }; return this.cryptoUtils.base64Encode(JSON.stringify(reqCnf)); } - - async signPopToken(accessToken: string, resourceRequestMethod: string, resourceRequestUri: string): Promise { - const tokenClaims: TokenClaims = JwtToken.extractTokenClaims(accessToken, this.cryptoUtils); - const resourceUrlString: UrlString = new UrlString(resourceRequestUri); - const resourceUrlComponents: IUri = resourceUrlString.getUrlComponents(); - return await this.cryptoUtils.signJwt({ - at: accessToken, - ts: `${TimeUtils.nowSeconds()}`, - m: resourceRequestMethod.toUpperCase(), - u: resourceUrlComponents.HostNameAndPort || "", - nonce: this.cryptoUtils.createNewGuid(), - p: resourceUrlComponents.AbsolutePath, - q: [[], resourceUrlComponents.QueryString] - }, tokenClaims.cnf.kid); - } } diff --git a/lib/msal-common/src/crypto/SignedHttpRequest.ts b/lib/msal-common/src/crypto/SignedHttpRequest.ts deleted file mode 100644 index 30110f4143..0000000000 --- a/lib/msal-common/src/crypto/SignedHttpRequest.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. - */ - -export type SignedHttpRequest = { - at?: string; - cnf?: string; - m?: string; - u?: string; - p?: string; - q?: [Array, string]; - ts?: string; - nonce?: string; -}; diff --git a/lib/msal-common/src/error/ClientAuthError.ts b/lib/msal-common/src/error/ClientAuthError.ts index 7ef63afa66..351a8bfdab 100644 --- a/lib/msal-common/src/error/ClientAuthError.ts +++ b/lib/msal-common/src/error/ClientAuthError.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ import { AuthError } from "./AuthError"; -import { JwtToken } from "../account/JwtToken"; +import { IdToken } from "../account/IdToken"; import { ScopeSet } from "../request/ScopeSet"; /** @@ -18,13 +18,13 @@ export const ClientAuthErrorMessage = { code: "client_info_empty_error", desc: "The client info was empty. Please review the trace to determine the root cause." }, - tokenParsingError: { - code: "token_parsing_error", - desc: "Token cannot be parsed. Please review stack trace to determine root cause." + idTokenParsingError: { + code: "id_token_parsing_error", + desc: "ID token cannot be parsed. Please review stack trace to determine root cause." }, - nullOrEmptyToken: { - code: "null_or_empty_token", - desc: "The token is null or empty. Please review the trace to determine the root cause." + nullOrEmptyIdToken: { + code: "null_or_empty_id_token", + desc: "The idToken is null or empty. Please review the trace to determine the root cause." }, endpointResolutionError: { code: "endpoints_resolution_error", @@ -191,18 +191,18 @@ export class ClientAuthError extends AuthError { * Creates an error thrown when the id token extraction errors out. * @param err */ - static createTokenParsingError(caughtExtractionError: string): ClientAuthError { - return new ClientAuthError(ClientAuthErrorMessage.tokenParsingError.code, - `${ClientAuthErrorMessage.tokenParsingError.desc} Failed with error: ${caughtExtractionError}`); + static createIdTokenParsingError(caughtExtractionError: string): ClientAuthError { + return new ClientAuthError(ClientAuthErrorMessage.idTokenParsingError.code, + `${ClientAuthErrorMessage.idTokenParsingError.desc} Failed with error: ${caughtExtractionError}`); } /** * Creates an error thrown when the id token string is null or empty. * @param invalidRawTokenString */ - static createTokenNullOrEmptyError(invalidRawTokenString: string) : ClientAuthError { - return new ClientAuthError(ClientAuthErrorMessage.nullOrEmptyToken.code, - `${ClientAuthErrorMessage.nullOrEmptyToken.desc} Raw ID Token Value: ${invalidRawTokenString}`); + static createIdTokenNullOrEmptyError(invalidRawTokenString: string) : ClientAuthError { + return new ClientAuthError(ClientAuthErrorMessage.nullOrEmptyIdToken.code, + `${ClientAuthErrorMessage.nullOrEmptyIdToken.desc} Raw ID Token Value: ${invalidRawTokenString}`); } /** @@ -268,7 +268,7 @@ export class ClientAuthError extends AuthError { * Throws error if idToken is not correctly formed * @param idToken */ - static createInvalidIdTokenError(idToken: JwtToken) : ClientAuthError { + static createInvalidIdTokenError(idToken: IdToken) : ClientAuthError { return new ClientAuthError(ClientAuthErrorMessage.invalidIdToken.code, `${ClientAuthErrorMessage.invalidIdToken.desc} Given token: ${JSON.stringify(idToken)}`); } diff --git a/lib/msal-common/src/index.ts b/lib/msal-common/src/index.ts index f35a44c82f..f2578cb2dc 100644 --- a/lib/msal-common/src/index.ts +++ b/lib/msal-common/src/index.ts @@ -7,8 +7,8 @@ export { AuthOptions, SystemOptions, LoggerOptions, DEFAULT_SYSTEM_OPTIONS } fro export { ClientConfiguration } from "./config/ClientConfiguration"; // Account export { AccountInfo } from "./account/AccountInfo"; -export { JwtToken as IdToken } from "./account/JwtToken"; -export { TokenClaims as IdTokenClaims } from "./account/TokenClaims"; +export { IdToken } from "./account/IdToken"; +export { IdTokenClaims } from "./account/IdTokenClaims"; // Authority export { Authority } from "./authority/Authority"; export { CloudDiscoveryMetadata } from "./authority/CloudDiscoveryMetadata"; @@ -31,7 +31,7 @@ export { IUri } from "./url/IUri"; export { UrlString } from "./url/UrlString"; // Crypto Interface export { ICrypto, PkceCodes } from "./crypto/ICrypto"; -export { SignedHttpRequest } from "./crypto/SignedHttpRequest"; + // Request and Response export { BaseAuthRequest } from "./request/BaseAuthRequest"; export { AuthorizationUrlRequest } from "./request/AuthorizationUrlRequest"; diff --git a/lib/msal-common/src/request/AuthorizationCodeRequest.ts b/lib/msal-common/src/request/AuthorizationCodeRequest.ts index e290215c12..4601c53f7b 100644 --- a/lib/msal-common/src/request/AuthorizationCodeRequest.ts +++ b/lib/msal-common/src/request/AuthorizationCodeRequest.ts @@ -15,8 +15,6 @@ import { AuthenticationType } from "../utils/Constants"; * - redirectUri - 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. * - code - The authorization_code that the user acquired in the first leg of the flow. * - codeVerifier - 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 - * - resourceRequestMethod - HTTP Request type used to request data from the resource (i.e. "GET", "POST", etc.). Used for proof-of-possession flows. - * - resourceRequestUri - URI that token will be used for. Used for proof-of-possession flows. */ export type AuthorizationCodeRequest = BaseAuthRequest & { redirectUri: string; diff --git a/lib/msal-common/src/request/AuthorizationUrlRequest.ts b/lib/msal-common/src/request/AuthorizationUrlRequest.ts index 5622acb580..44726d5c52 100644 --- a/lib/msal-common/src/request/AuthorizationUrlRequest.ts +++ b/lib/msal-common/src/request/AuthorizationUrlRequest.ts @@ -30,8 +30,6 @@ import { BaseAuthRequest } from "./BaseAuthRequest"; * - extraQueryParameters - String to string map of custom query parameters. * - claims - In cases where Azure AD tenant admin has enabled conditional access policies, and the policy has not been met, exceptions will contain claims that need to be consented to. * - nonce - 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. - * - resourceRequestMethod - HTTP Request type used to request data from the resource (i.e. "GET", "POST", etc.). Used for proof-of-possession flows. - * - resourceRequestUri - URI that token will be used for. Used for proof-of-possession flows. */ export type AuthorizationUrlRequest = BaseAuthRequest & { authenticationScheme?: AuthenticationType, diff --git a/lib/msal-common/src/request/BaseAuthRequest.ts b/lib/msal-common/src/request/BaseAuthRequest.ts index 6b5300eaf0..b6259d9f99 100644 --- a/lib/msal-common/src/request/BaseAuthRequest.ts +++ b/lib/msal-common/src/request/BaseAuthRequest.ts @@ -8,13 +8,9 @@ * - scopes - Array of scopes the application is requesting access to. * - authority - 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. * - correlationId - Unique GUID set per request to trace a request end-to-end for telemetry purposes. - * - resourceRequestMethod - HTTP Request type used to request data from the resource (i.e. "GET", "POST", etc.). Used for proof-of-possession flows. - * - resourceRequestUri - URI that token will be used for. Used for proof-of-possession flows. */ export type BaseAuthRequest = { scopes: Array; authority?: string; correlationId?: string; - resourceRequestMethod?: string; - resourceRequestUri?: string; }; diff --git a/lib/msal-common/src/request/DeviceCodeRequest.ts b/lib/msal-common/src/request/DeviceCodeRequest.ts index 361cae329f..e8ee02bc44 100644 --- a/lib/msal-common/src/request/DeviceCodeRequest.ts +++ b/lib/msal-common/src/request/DeviceCodeRequest.ts @@ -13,8 +13,6 @@ import { BaseAuthRequest } from "./BaseAuthRequest"; * - correlationId - Unique GUID set per request to trace a request end-to-end for telemetry purposes. * - deviceCodeCallback - Callback containing device code response. Message should be shown to end user. End user can then navigate to the verification_uri, input the user_code, and input credentials. * - cancel - Boolean to cancel polling of device code endpoint. While the user authenticates on a separate device, MSAL polls the the token endpoint of security token service for the interval specified in the device code response (usually 15 minutes). To stop polling and cancel the request, set cancel=true. - * - resourceRequestMethod - HTTP Request type used to request data from the resource (i.e. "GET", "POST", etc.). Used for proof-of-possession flows. - * - resourceRequestUri - URI that token will be used for. Used for proof-of-possession flows. */ export type DeviceCodeRequest = BaseAuthRequest & { deviceCodeCallback: (response: DeviceCodeResponse) => void; diff --git a/lib/msal-common/src/request/RefreshTokenRequest.ts b/lib/msal-common/src/request/RefreshTokenRequest.ts index 39346b60b7..1e555f76b5 100644 --- a/lib/msal-common/src/request/RefreshTokenRequest.ts +++ b/lib/msal-common/src/request/RefreshTokenRequest.ts @@ -11,8 +11,6 @@ import { BaseAuthRequest } from "./BaseAuthRequest"; * - authority - URL of the authority, the security token service (STS) from which MSAL will acquire tokens. * - correlationId - Unique GUID set per request to trace a request end-to-end for telemetry purposes. * - refreshToken - A refresh token returned from a previous request to the Identity provider. - * - resourceRequestMethod - HTTP Request type used to request data from the resource (i.e. "GET", "POST", etc.). Used for proof-of-possession flows. - * - resourceRequestUri - URI that token will be used for. Used for proof-of-possession flows. */ export type RefreshTokenRequest = BaseAuthRequest & { refreshToken: string; diff --git a/lib/msal-common/src/request/RequestParameterBuilder.ts b/lib/msal-common/src/request/RequestParameterBuilder.ts index 3b6d91b44d..cf08d13b07 100644 --- a/lib/msal-common/src/request/RequestParameterBuilder.ts +++ b/lib/msal-common/src/request/RequestParameterBuilder.ts @@ -249,7 +249,7 @@ export class RequestParameterBuilder { * add pop_jwk to query params * @param cnfString */ - addPopToken(cnfString: string): void { + addPopToken(cnfString: string) { if (!StringUtils.isEmpty(cnfString)) { this.parameters.set(AADServerParamKeys.TOKEN_TYPE, AuthenticationType.POP); this.parameters.set(AADServerParamKeys.REQ_CNF, encodeURIComponent(cnfString)); diff --git a/lib/msal-common/src/request/SilentFlowRequest.ts b/lib/msal-common/src/request/SilentFlowRequest.ts index 14c7404ae3..76985e9214 100644 --- a/lib/msal-common/src/request/SilentFlowRequest.ts +++ b/lib/msal-common/src/request/SilentFlowRequest.ts @@ -13,8 +13,6 @@ import { BaseAuthRequest } from "./BaseAuthRequest"; * - correlationId - Unique GUID set per request to trace a request end-to-end for telemetry purposes. * - account - Account entity to lookup the credentials. * - forceRefresh - Forces silent requests to make network calls if true. - * - resourceRequestMethod - HTTP Request type used to request data from the resource (i.e. "GET", "POST", etc.). Used for proof-of-possession flows. - * - resourceRequestUri - URI that token will be used for. Used for proof-of-possession flows. */ export type SilentFlowRequest = BaseAuthRequest & { account: AccountInfo; diff --git a/lib/msal-common/src/response/AuthenticationResult.ts b/lib/msal-common/src/response/AuthenticationResult.ts index 66215d7cea..d422bb5c90 100644 --- a/lib/msal-common/src/response/AuthenticationResult.ts +++ b/lib/msal-common/src/response/AuthenticationResult.ts @@ -30,7 +30,6 @@ export type AuthenticationResult = { accessToken: string; fromCache: boolean; expiresOn: Date; - tokenType: string; extExpiresOn?: Date; state?: string; familyId?: string; diff --git a/lib/msal-common/src/response/ResponseHandler.ts b/lib/msal-common/src/response/ResponseHandler.ts index 05b7ddb26b..31969371a6 100644 --- a/lib/msal-common/src/response/ResponseHandler.ts +++ b/lib/msal-common/src/response/ResponseHandler.ts @@ -10,7 +10,7 @@ import { StringUtils } from "../utils/StringUtils"; import { ServerAuthorizationCodeResponse } from "./ServerAuthorizationCodeResponse"; import { Logger } from "../logger/Logger"; import { ServerError } from "../error/ServerError"; -import { JwtToken } from "../account/JwtToken"; +import { IdToken } from "../account/IdToken"; import { ScopeSet } from "../request/ScopeSet"; import { TimeUtils } from "../utils/TimeUtils"; import { AuthenticationResult } from "./AuthenticationResult"; @@ -25,8 +25,6 @@ import { CacheRecord } from "../cache/entities/CacheRecord"; import { TrustedAuthority } from "../authority/TrustedAuthority"; import { CacheManager } from "../cache/CacheManager"; import { ProtocolUtils, LibraryStateObject, RequestStateObject } from "../utils/ProtocolUtils"; -import { AuthenticationType } from "../utils/Constants"; -import { PopTokenGenerator } from "../crypto/PopTokenGenerator"; /** * Class that handles response parsing. @@ -100,15 +98,9 @@ export class ResponseHandler { * @param serverTokenResponse * @param authority */ - async handleServerTokenResponse( - serverTokenResponse: ServerAuthorizationTokenResponse, - authority: Authority, - cachedNonce?: string, - cachedState?: string, - resourceRequestMethod?: string, - resourceRequestUri?: string): Promise { + handleServerTokenResponse(serverTokenResponse: ServerAuthorizationTokenResponse, authority: Authority, cachedNonce?: string, cachedState?: string): AuthenticationResult { // create an idToken object (not entity) - const idTokenObj = new JwtToken(serverTokenResponse.id_token, this.cryptoObj); + const idTokenObj = new IdToken(serverTokenResponse.id_token, this.cryptoObj); // token nonce check (TODO: Add a warning if no nonce is given?) if (!StringUtils.isEmpty(cachedNonce)) { @@ -126,7 +118,7 @@ export class ResponseHandler { const cacheRecord = this.generateCacheRecord(serverTokenResponse, idTokenObj, authority, requestStateObj && requestStateObj.libraryState); this.cacheStorage.saveCacheRecord(cacheRecord); - return await ResponseHandler.generateAuthenticationResult(this.cryptoObj, cacheRecord, idTokenObj, false, requestStateObj, resourceRequestMethod, resourceRequestUri); + return ResponseHandler.generateAuthenticationResult(cacheRecord, idTokenObj, false, requestStateObj); } /** @@ -135,7 +127,7 @@ export class ResponseHandler { * @param idTokenObj * @param authority */ - private generateCacheRecord(serverTokenResponse: ServerAuthorizationTokenResponse, idTokenObj: JwtToken, authority: Authority, libraryState?: LibraryStateObject): CacheRecord { + private generateCacheRecord(serverTokenResponse: ServerAuthorizationTokenResponse, idTokenObj: IdToken, authority: Authority, libraryState?: LibraryStateObject): CacheRecord { // Account const cachedAccount = this.generateAccountEntity( serverTokenResponse, @@ -183,8 +175,7 @@ export class ResponseHandler { idTokenObj.claims.tid, responseScopes.printScopesLowerCase(), tokenExpirationSeconds, - extendedTokenExpirationSeconds, - serverTokenResponse.token_type + extendedTokenExpirationSeconds ); } @@ -209,7 +200,7 @@ export class ResponseHandler { * @param idToken * @param authority */ - private generateAccountEntity(serverTokenResponse: ServerAuthorizationTokenResponse, idToken: JwtToken, authority: Authority): AccountEntity { + private generateAccountEntity(serverTokenResponse: ServerAuthorizationTokenResponse, idToken: IdToken, authority: Authority): AccountEntity { const authorityType = authority.authorityType; if (StringUtils.isEmpty(serverTokenResponse.client_info)) { @@ -231,41 +222,33 @@ export class ResponseHandler { * @param fromTokenCache * @param stateString */ - static async generateAuthenticationResult(cryptoObj: ICrypto, cacheRecord: CacheRecord, idTokenObj: JwtToken, fromTokenCache: boolean, requestState?: RequestStateObject, resourceRequestMethod?: string, resourceRequestUri?: string): Promise { + static generateAuthenticationResult(cacheRecord: CacheRecord, idTokenObj: IdToken, fromTokenCache: boolean, requestState?: RequestStateObject): AuthenticationResult { let accessToken: string = ""; let responseScopes: Array = []; let expiresOn: Date = null; let extExpiresOn: Date = null; let familyId: string = null; if (cacheRecord.accessToken) { - if (cacheRecord.accessToken.tokenType === AuthenticationType.POP) { - const popTokenGenerator: PopTokenGenerator = new PopTokenGenerator(cryptoObj); - accessToken = await popTokenGenerator.signPopToken(cacheRecord.accessToken.secret, resourceRequestMethod, resourceRequestUri); - } else { - accessToken = cacheRecord.accessToken.secret; - } + accessToken = cacheRecord.accessToken.secret; responseScopes = ScopeSet.fromString(cacheRecord.accessToken.target).asArray(); expiresOn = new Date(Number(cacheRecord.accessToken.expiresOn) * 1000); extExpiresOn = new Date(Number(cacheRecord.accessToken.extendedExpiresOn) * 1000); } - if (cacheRecord.refreshToken) { familyId = cacheRecord.refreshToken.familyId || null; } - return { uniqueId: idTokenObj.claims.oid || idTokenObj.claims.sub, tenantId: idTokenObj.claims.tid, scopes: responseScopes, account: cacheRecord.account.getAccountInfo(), - idToken: idTokenObj.rawToken, + idToken: idTokenObj.rawIdToken, idTokenClaims: idTokenObj.claims, accessToken: accessToken, fromCache: fromTokenCache, expiresOn: expiresOn, extExpiresOn: extExpiresOn, familyId: familyId, - tokenType: cacheRecord.accessToken.tokenType, state: requestState ? requestState.userRequestState : "" }; } diff --git a/lib/msal-common/src/url/IUri.ts b/lib/msal-common/src/url/IUri.ts index 2a97323ea7..f31cdec495 100644 --- a/lib/msal-common/src/url/IUri.ts +++ b/lib/msal-common/src/url/IUri.ts @@ -13,5 +13,4 @@ export interface IUri { Search: string; Hash: string; PathSegments: string[]; - QueryString: string; } diff --git a/lib/msal-common/src/url/UrlString.ts b/lib/msal-common/src/url/UrlString.ts index aaf6fb13d1..ebd134758a 100644 --- a/lib/msal-common/src/url/UrlString.ts +++ b/lib/msal-common/src/url/UrlString.ts @@ -131,8 +131,7 @@ export class UrlString { const urlComponents = { Protocol: match[1], HostNameAndPort: match[4], - AbsolutePath: match[5], - QueryString: match[7] + AbsolutePath: match[5] } as IUri; let pathSegments = urlComponents.AbsolutePath.split("/"); diff --git a/lib/msal-common/src/utils/StringUtils.ts b/lib/msal-common/src/utils/StringUtils.ts index 38d0115262..b424b11534 100644 --- a/lib/msal-common/src/utils/StringUtils.ts +++ b/lib/msal-common/src/utils/StringUtils.ts @@ -17,12 +17,12 @@ export class StringUtils { */ static decodeJwt(jwtToken: string): DecodedJwt { if (StringUtils.isEmpty(jwtToken)) { - throw ClientAuthError.createTokenNullOrEmptyError(jwtToken); + throw ClientAuthError.createIdTokenNullOrEmptyError(jwtToken); } const idTokenPartsRegex = /^([^\.\s]*)\.([^\.\s]+)\.([^\.\s]*)$/; const matches = idTokenPartsRegex.exec(jwtToken); if (!matches || matches.length < 4) { - throw ClientAuthError.createTokenParsingError(`Given token is malformed: ${JSON.stringify(jwtToken)}`); + throw ClientAuthError.createIdTokenParsingError(`Given token is malformed: ${JSON.stringify(jwtToken)}`); } const crackedToken: DecodedJwt = { header: matches[1], diff --git a/samples/msal-browser-samples/VanillaJSTestApp2.0/app/default/authConfig.js b/samples/msal-browser-samples/VanillaJSTestApp2.0/app/default/authConfig.js index 7913deb82d..cfcb622a20 100644 --- a/samples/msal-browser-samples/VanillaJSTestApp2.0/app/default/authConfig.js +++ b/samples/msal-browser-samples/VanillaJSTestApp2.0/app/default/authConfig.js @@ -33,20 +33,18 @@ const msalConfig = { } }; +// Add here scopes for id token to be used at MS Identity Platform endpoints. +const loginRequest = { + scopes: ["User.Read"], + authenticationType: "pop" +}; + // Add here the endpoints for MS Graph API services you would like to use. const graphConfig = { graphMeEndpoint: "https://graph.microsoft-ppe.com/v1.0/me", graphMailEndpoint: "https://graph.microsoft-ppe.com/v1.0/me/messages" }; -// Add here scopes for id token to be used at MS Identity Platform endpoints. -const loginRequest = { - scopes: ["User.Read"], - authenticationScheme: "pop", - resourceRequestMethod: "POST", - resourceRequestUri: graphConfig.graphMeEndpoint -}; - // Add here scopes for access token to be used at MS Graph API endpoints. const tokenRequest = { scopes: ["Mail.Read"], From 03e051deea95cd8543c4c87c05821b4bf7f7e334 Mon Sep 17 00:00:00 2001 From: Prithvi Kanherkar Date: Fri, 14 Aug 2020 18:29:44 -0700 Subject: [PATCH 03/17] msal-common changes only This reverts commit efe1dea288a1667f50282329c39c8e05297c2909. --- .../src/account/{IdToken.ts => JwtToken.ts} | 28 +++++++------- .../{IdTokenClaims.ts => TokenClaims.ts} | 7 +++- .../src/cache/entities/AccessTokenEntity.ts | 7 +++- .../src/cache/entities/AccountEntity.ts | 6 +-- .../src/client/AuthorizationCodeClient.ts | 6 +-- .../src/client/DeviceCodeClient.ts | 10 +++-- .../src/client/RefreshTokenClient.ts | 4 +- .../src/client/SilentFlowClient.ts | 26 ++++++++----- .../src/config/ClientConfiguration.ts | 4 ++ lib/msal-common/src/crypto/ICrypto.ts | 11 +++++- .../src/crypto/PopTokenGenerator.ts | 24 +++++++++++- .../src/crypto/SignedHttpRequest.ts | 15 ++++++++ lib/msal-common/src/error/ClientAuthError.ts | 28 +++++++------- lib/msal-common/src/index.ts | 6 +-- .../src/request/AuthorizationCodeRequest.ts | 2 + .../src/request/AuthorizationUrlRequest.ts | 2 + .../src/request/BaseAuthRequest.ts | 4 ++ .../src/request/DeviceCodeRequest.ts | 2 + .../src/request/RefreshTokenRequest.ts | 2 + .../src/request/RequestParameterBuilder.ts | 2 +- .../src/request/SilentFlowRequest.ts | 2 + .../src/response/AuthenticationResult.ts | 1 + .../src/response/ResponseHandler.ts | 37 ++++++++++++++----- lib/msal-common/src/url/IUri.ts | 1 + lib/msal-common/src/url/UrlString.ts | 3 +- lib/msal-common/src/utils/StringUtils.ts | 4 +- .../app/default/authConfig.js | 14 ++++--- 27 files changed, 177 insertions(+), 81 deletions(-) rename lib/msal-common/src/account/{IdToken.ts => JwtToken.ts} (54%) rename lib/msal-common/src/account/{IdTokenClaims.ts => TokenClaims.ts} (79%) create mode 100644 lib/msal-common/src/crypto/SignedHttpRequest.ts diff --git a/lib/msal-common/src/account/IdToken.ts b/lib/msal-common/src/account/JwtToken.ts similarity index 54% rename from lib/msal-common/src/account/IdToken.ts rename to lib/msal-common/src/account/JwtToken.ts index 36bcb296fe..b1375941e6 100644 --- a/lib/msal-common/src/account/IdToken.ts +++ b/lib/msal-common/src/account/JwtToken.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ -import { IdTokenClaims } from "./IdTokenClaims"; +import { TokenClaims } from "./TokenClaims"; import { DecodedJwt } from "./DecodedJwt"; import { ClientAuthError } from "../error/ClientAuthError"; import { StringUtils } from "../utils/StringUtils"; @@ -11,19 +11,19 @@ import { ICrypto } from "../crypto/ICrypto"; /** * Id Token representation class. Parses id token string and generates claims object. */ -export class IdToken { +export class JwtToken { // Raw Id Token string - rawIdToken: string; + rawToken: string; // Claims inside Id Token - claims: IdTokenClaims; - constructor(rawIdToken: string, crypto: ICrypto) { - if (StringUtils.isEmpty(rawIdToken)) { - throw ClientAuthError.createIdTokenNullOrEmptyError(rawIdToken); + claims: TokenClaims; + constructor(rawToken: string, crypto: ICrypto) { + if (StringUtils.isEmpty(rawToken)) { + throw ClientAuthError.createTokenNullOrEmptyError(rawToken); } - this.rawIdToken = rawIdToken; - this.claims = IdToken.extractIdToken(rawIdToken, crypto); + this.rawToken = rawToken; + this.claims = JwtToken.extractTokenClaims(rawToken, crypto); } /** @@ -31,19 +31,19 @@ export class IdToken { * * @param encodedIdToken */ - static extractIdToken(encodedIdToken: string, crypto: ICrypto): IdTokenClaims { + static extractTokenClaims(encodedIdToken: string, crypto: ICrypto): TokenClaims { // id token will be decoded to get the username const decodedToken: DecodedJwt = StringUtils.decodeJwt(encodedIdToken); if (!decodedToken) { return null; } try { - const base64IdTokenPayload = decodedToken.JWSPayload; + const base64TokenPayload = decodedToken.JWSPayload; // base64Decode() should throw an error if there is an issue - const base64Decoded = crypto.base64Decode(base64IdTokenPayload); - return JSON.parse(base64Decoded) as IdTokenClaims; + const base64Decoded = crypto.base64Decode(base64TokenPayload); + return JSON.parse(base64Decoded) as TokenClaims; } catch (err) { - throw ClientAuthError.createIdTokenParsingError(err); + throw ClientAuthError.createTokenParsingError(err); } } } diff --git a/lib/msal-common/src/account/IdTokenClaims.ts b/lib/msal-common/src/account/TokenClaims.ts similarity index 79% rename from lib/msal-common/src/account/IdTokenClaims.ts rename to lib/msal-common/src/account/TokenClaims.ts index a6eda5709b..066f5e48ce 100644 --- a/lib/msal-common/src/account/IdTokenClaims.ts +++ b/lib/msal-common/src/account/TokenClaims.ts @@ -6,7 +6,7 @@ /** * Type which describes Id Token claims known by MSAL. */ -export type IdTokenClaims = { +export type TokenClaims = { iss?: string, oid?: string, sub?: string, @@ -19,5 +19,8 @@ export type IdTokenClaims = { exp?: number, home_oid?: string, sid?: string, - cloud_instance_host_name?: string + cloud_instance_host_name?: string, + cnf?: { + kid: string; + }; }; diff --git a/lib/msal-common/src/cache/entities/AccessTokenEntity.ts b/lib/msal-common/src/cache/entities/AccessTokenEntity.ts index 52ddcfa309..1bd3296cd3 100644 --- a/lib/msal-common/src/cache/entities/AccessTokenEntity.ts +++ b/lib/msal-common/src/cache/entities/AccessTokenEntity.ts @@ -4,8 +4,9 @@ */ import { CredentialEntity } from "./CredentialEntity"; -import { CredentialType } from "../../utils/Constants"; +import { CredentialType, AuthenticationType } from "../../utils/Constants"; import { TimeUtils } from "../../utils/TimeUtils"; +import { StringUtils } from "../../utils/StringUtils"; /** * ACCESS_TOKEN Credential Type @@ -60,7 +61,8 @@ export class AccessTokenEntity extends CredentialEntity { tenantId: string, scopes: string, expiresOn: number, - extExpiresOn: number + extExpiresOn: number, + tokenType?: string ): AccessTokenEntity { const atEntity: AccessTokenEntity = new AccessTokenEntity(); @@ -81,6 +83,7 @@ export class AccessTokenEntity extends CredentialEntity { atEntity.realm = tenantId; atEntity.target = scopes; + atEntity.tokenType = StringUtils.isEmpty(tokenType) ? AuthenticationType.BEARER : tokenType; return atEntity; } } diff --git a/lib/msal-common/src/cache/entities/AccountEntity.ts b/lib/msal-common/src/cache/entities/AccountEntity.ts index d2820a8ca5..a1402bd60d 100644 --- a/lib/msal-common/src/cache/entities/AccountEntity.ts +++ b/lib/msal-common/src/cache/entities/AccountEntity.ts @@ -9,7 +9,7 @@ import { CacheType, } from "../../utils/Constants"; import { Authority } from "../../authority/Authority"; -import { IdToken } from "../../account/IdToken"; +import { JwtToken } from "../../account/JwtToken"; import { ICrypto } from "../../crypto/ICrypto"; import { buildClientInfo } from "../../account/ClientInfo"; import { StringUtils } from "../../utils/StringUtils"; @@ -125,7 +125,7 @@ export class AccountEntity { static createAccount( clientInfo: string, authority: Authority, - idToken: IdToken, + idToken: JwtToken, crypto: ICrypto ): AccountEntity { const account: AccountEntity = new AccountEntity(); @@ -164,7 +164,7 @@ export class AccountEntity { */ static createADFSAccount( authority: Authority, - idToken: IdToken + idToken: JwtToken ): AccountEntity { const account: AccountEntity = new AccountEntity(); diff --git a/lib/msal-common/src/client/AuthorizationCodeClient.ts b/lib/msal-common/src/client/AuthorizationCodeClient.ts index c70715a22c..14de0bca3a 100644 --- a/lib/msal-common/src/client/AuthorizationCodeClient.ts +++ b/lib/msal-common/src/client/AuthorizationCodeClient.ts @@ -71,9 +71,7 @@ export class AuthorizationCodeClient extends BaseClient { // Validate response. This function throws a server error if an error is returned by the server. responseHandler.validateTokenResponse(response.body); - const tokenResponse = responseHandler.handleServerTokenResponse(response.body, this.authority, cachedNonce, cachedState); - - return tokenResponse; + return await responseHandler.handleServerTokenResponse(response.body, this.authority, cachedNonce, cachedState, request.resourceRequestMethod, request.resourceRequestUri); } /** @@ -176,7 +174,7 @@ export class AuthorizationCodeClient extends BaseClient { if (request.authenticationScheme === AuthenticationType.POP) { const popTokenGenerator = new PopTokenGenerator(this.cryptoUtils); - parameterBuilder.addPopToken(await popTokenGenerator.generateCnf()); + parameterBuilder.addPopToken(await popTokenGenerator.generateCnf(request.resourceRequestMethod, request.resourceRequestUri)); } const correlationId = request.correlationId || this.config.cryptoInterface.createNewGuid(); diff --git a/lib/msal-common/src/client/DeviceCodeClient.ts b/lib/msal-common/src/client/DeviceCodeClient.ts index cb837743a1..e63f2d0d2f 100644 --- a/lib/msal-common/src/client/DeviceCodeClient.ts +++ b/lib/msal-common/src/client/DeviceCodeClient.ts @@ -47,12 +47,14 @@ export class DeviceCodeClient extends BaseClient { // Validate response. This function throws a server error if an error is returned by the server. responseHandler.validateTokenResponse(response); - const tokenResponse = responseHandler.handleServerTokenResponse( + return await responseHandler.handleServerTokenResponse( response, - this.authority + this.authority, + "", + "", + request.resourceRequestMethod, + request.resourceRequestUri ); - - return tokenResponse; } /** diff --git a/lib/msal-common/src/client/RefreshTokenClient.ts b/lib/msal-common/src/client/RefreshTokenClient.ts index 1c2b03379c..f6ffbf6b31 100644 --- a/lib/msal-common/src/client/RefreshTokenClient.ts +++ b/lib/msal-common/src/client/RefreshTokenClient.ts @@ -34,12 +34,10 @@ export class RefreshTokenClient extends BaseClient { ); responseHandler.validateTokenResponse(response.body); - const tokenResponse = responseHandler.handleServerTokenResponse( + return await responseHandler.handleServerTokenResponse( response.body, this.authority ); - - return tokenResponse; } private async executeTokenRequest(request: RefreshTokenRequest, authority: Authority) diff --git a/lib/msal-common/src/client/SilentFlowClient.ts b/lib/msal-common/src/client/SilentFlowClient.ts index c9853864d3..584612b67e 100644 --- a/lib/msal-common/src/client/SilentFlowClient.ts +++ b/lib/msal-common/src/client/SilentFlowClient.ts @@ -12,7 +12,7 @@ import { IdTokenEntity } from "../cache/entities/IdTokenEntity"; import { AccessTokenEntity } from "../cache/entities/AccessTokenEntity"; import { RefreshTokenEntity } from "../cache/entities/RefreshTokenEntity"; import { ScopeSet } from "../request/ScopeSet"; -import { IdToken } from "../account/IdToken"; +import { JwtToken } from "../account/JwtToken"; import { TimeUtils } from "../utils/TimeUtils"; import { RefreshTokenRequest } from "../request/RefreshTokenRequest"; import { RefreshTokenClient } from "./RefreshTokenClient"; @@ -77,14 +77,22 @@ export class SilentFlowClient extends BaseClient { // Return tokens from cache this.config.serverTelemetryManager.incrementCacheHits(); const cachedIdToken = this.readIdTokenFromCache(homeAccountId, environment, cachedAccount.realm); - const idTokenObj = new IdToken(cachedIdToken.secret, this.config.cryptoInterface); - - return ResponseHandler.generateAuthenticationResult({ - account: cachedAccount, - accessToken: cachedAccessToken, - idToken: cachedIdToken, - refreshToken: cachedRefreshToken - }, idTokenObj, true); + const idTokenObj = new JwtToken(cachedIdToken.secret, this.config.cryptoInterface); + + return await ResponseHandler.generateAuthenticationResult( + this.cryptoUtils, + { + account: cachedAccount, + accessToken: cachedAccessToken, + idToken: cachedIdToken, + refreshToken: cachedRefreshToken + }, + idTokenObj, + true, + null, + request.resourceRequestMethod, + request.resourceRequestUri + ); } /** diff --git a/lib/msal-common/src/config/ClientConfiguration.ts b/lib/msal-common/src/config/ClientConfiguration.ts index 957322ecd3..bed2869206 100644 --- a/lib/msal-common/src/config/ClientConfiguration.ts +++ b/lib/msal-common/src/config/ClientConfiguration.ts @@ -149,6 +149,10 @@ const DEFAULT_CRYPTO_IMPLEMENTATION: ICrypto = { async getPublicKeyThumprint(): Promise { const notImplErr = "Crypto interface - getPublicKeyThumprint() has not been implemented"; throw AuthError.createUnexpectedError(notImplErr); + }, + async signJwt(): Promise { + const notImplErr = "Crypto interface - signJwt() has not been implemented"; + throw AuthError.createUnexpectedError(notImplErr); } }; diff --git a/lib/msal-common/src/crypto/ICrypto.ts b/lib/msal-common/src/crypto/ICrypto.ts index c49cf2ce0b..f32f7eb4b3 100644 --- a/lib/msal-common/src/crypto/ICrypto.ts +++ b/lib/msal-common/src/crypto/ICrypto.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. */ +import { SignedHttpRequest } from "./SignedHttpRequest"; + /** * The PkceCodes type describes the structure * of objects that contain PKCE code @@ -37,6 +39,13 @@ export interface ICrypto { generatePkceCodes(): Promise; /** * Generates an JWK RSA S256 Thumbprint + * @param resourceRequestMethod + * @param resourceRequestUri + */ + getPublicKeyThumprint(resourceRequestMethod: string, resourceRequestUri: string): Promise; + /** + * Returns a signed proof-of-possession token with a given acces token that contains a cnf claim with the required kid. + * @param accessToken */ - getPublicKeyThumprint(): Promise; + signJwt(payload: SignedHttpRequest, kid: string): Promise; } diff --git a/lib/msal-common/src/crypto/PopTokenGenerator.ts b/lib/msal-common/src/crypto/PopTokenGenerator.ts index 628579316a..a2eb3d217e 100644 --- a/lib/msal-common/src/crypto/PopTokenGenerator.ts +++ b/lib/msal-common/src/crypto/PopTokenGenerator.ts @@ -1,4 +1,9 @@ import { ICrypto } from "./ICrypto"; +import { JwtToken } from "../account/JwtToken"; +import { TokenClaims } from "../account/TokenClaims"; +import { TimeUtils } from "../utils/TimeUtils"; +import { UrlString } from "../url/UrlString"; +import { IUri } from "../url/IUri"; /** * See eSTS docs for more info. @@ -25,12 +30,27 @@ export class PopTokenGenerator { this.cryptoUtils = cryptoUtils; } - async generateCnf(): Promise { + async generateCnf(resourceRequestMethod: string, resourceRequestUri: string): Promise { const reqCnf: ReqCnf = { - kid: await this.cryptoUtils.getPublicKeyThumprint(), + kid: await this.cryptoUtils.getPublicKeyThumprint(resourceRequestMethod, resourceRequestUri), xms_ksl: KeyLocation.SW }; return this.cryptoUtils.base64Encode(JSON.stringify(reqCnf)); } + + async signPopToken(accessToken: string, resourceRequestMethod: string, resourceRequestUri: string): Promise { + const tokenClaims: TokenClaims = JwtToken.extractTokenClaims(accessToken, this.cryptoUtils); + const resourceUrlString: UrlString = new UrlString(resourceRequestUri); + const resourceUrlComponents: IUri = resourceUrlString.getUrlComponents(); + return await this.cryptoUtils.signJwt({ + at: accessToken, + ts: `${TimeUtils.nowSeconds()}`, + m: resourceRequestMethod.toUpperCase(), + u: resourceUrlComponents.HostNameAndPort || "", + nonce: this.cryptoUtils.createNewGuid(), + p: resourceUrlComponents.AbsolutePath, + q: [[], resourceUrlComponents.QueryString] + }, tokenClaims.cnf.kid); + } } diff --git a/lib/msal-common/src/crypto/SignedHttpRequest.ts b/lib/msal-common/src/crypto/SignedHttpRequest.ts new file mode 100644 index 0000000000..30110f4143 --- /dev/null +++ b/lib/msal-common/src/crypto/SignedHttpRequest.ts @@ -0,0 +1,15 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +export type SignedHttpRequest = { + at?: string; + cnf?: string; + m?: string; + u?: string; + p?: string; + q?: [Array, string]; + ts?: string; + nonce?: string; +}; diff --git a/lib/msal-common/src/error/ClientAuthError.ts b/lib/msal-common/src/error/ClientAuthError.ts index 351a8bfdab..7ef63afa66 100644 --- a/lib/msal-common/src/error/ClientAuthError.ts +++ b/lib/msal-common/src/error/ClientAuthError.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ import { AuthError } from "./AuthError"; -import { IdToken } from "../account/IdToken"; +import { JwtToken } from "../account/JwtToken"; import { ScopeSet } from "../request/ScopeSet"; /** @@ -18,13 +18,13 @@ export const ClientAuthErrorMessage = { code: "client_info_empty_error", desc: "The client info was empty. Please review the trace to determine the root cause." }, - idTokenParsingError: { - code: "id_token_parsing_error", - desc: "ID token cannot be parsed. Please review stack trace to determine root cause." + tokenParsingError: { + code: "token_parsing_error", + desc: "Token cannot be parsed. Please review stack trace to determine root cause." }, - nullOrEmptyIdToken: { - code: "null_or_empty_id_token", - desc: "The idToken is null or empty. Please review the trace to determine the root cause." + nullOrEmptyToken: { + code: "null_or_empty_token", + desc: "The token is null or empty. Please review the trace to determine the root cause." }, endpointResolutionError: { code: "endpoints_resolution_error", @@ -191,18 +191,18 @@ export class ClientAuthError extends AuthError { * Creates an error thrown when the id token extraction errors out. * @param err */ - static createIdTokenParsingError(caughtExtractionError: string): ClientAuthError { - return new ClientAuthError(ClientAuthErrorMessage.idTokenParsingError.code, - `${ClientAuthErrorMessage.idTokenParsingError.desc} Failed with error: ${caughtExtractionError}`); + static createTokenParsingError(caughtExtractionError: string): ClientAuthError { + return new ClientAuthError(ClientAuthErrorMessage.tokenParsingError.code, + `${ClientAuthErrorMessage.tokenParsingError.desc} Failed with error: ${caughtExtractionError}`); } /** * Creates an error thrown when the id token string is null or empty. * @param invalidRawTokenString */ - static createIdTokenNullOrEmptyError(invalidRawTokenString: string) : ClientAuthError { - return new ClientAuthError(ClientAuthErrorMessage.nullOrEmptyIdToken.code, - `${ClientAuthErrorMessage.nullOrEmptyIdToken.desc} Raw ID Token Value: ${invalidRawTokenString}`); + static createTokenNullOrEmptyError(invalidRawTokenString: string) : ClientAuthError { + return new ClientAuthError(ClientAuthErrorMessage.nullOrEmptyToken.code, + `${ClientAuthErrorMessage.nullOrEmptyToken.desc} Raw ID Token Value: ${invalidRawTokenString}`); } /** @@ -268,7 +268,7 @@ export class ClientAuthError extends AuthError { * Throws error if idToken is not correctly formed * @param idToken */ - static createInvalidIdTokenError(idToken: IdToken) : ClientAuthError { + static createInvalidIdTokenError(idToken: JwtToken) : ClientAuthError { return new ClientAuthError(ClientAuthErrorMessage.invalidIdToken.code, `${ClientAuthErrorMessage.invalidIdToken.desc} Given token: ${JSON.stringify(idToken)}`); } diff --git a/lib/msal-common/src/index.ts b/lib/msal-common/src/index.ts index f2578cb2dc..f35a44c82f 100644 --- a/lib/msal-common/src/index.ts +++ b/lib/msal-common/src/index.ts @@ -7,8 +7,8 @@ export { AuthOptions, SystemOptions, LoggerOptions, DEFAULT_SYSTEM_OPTIONS } fro export { ClientConfiguration } from "./config/ClientConfiguration"; // Account export { AccountInfo } from "./account/AccountInfo"; -export { IdToken } from "./account/IdToken"; -export { IdTokenClaims } from "./account/IdTokenClaims"; +export { JwtToken as IdToken } from "./account/JwtToken"; +export { TokenClaims as IdTokenClaims } from "./account/TokenClaims"; // Authority export { Authority } from "./authority/Authority"; export { CloudDiscoveryMetadata } from "./authority/CloudDiscoveryMetadata"; @@ -31,7 +31,7 @@ export { IUri } from "./url/IUri"; export { UrlString } from "./url/UrlString"; // Crypto Interface export { ICrypto, PkceCodes } from "./crypto/ICrypto"; - +export { SignedHttpRequest } from "./crypto/SignedHttpRequest"; // Request and Response export { BaseAuthRequest } from "./request/BaseAuthRequest"; export { AuthorizationUrlRequest } from "./request/AuthorizationUrlRequest"; diff --git a/lib/msal-common/src/request/AuthorizationCodeRequest.ts b/lib/msal-common/src/request/AuthorizationCodeRequest.ts index 4601c53f7b..e290215c12 100644 --- a/lib/msal-common/src/request/AuthorizationCodeRequest.ts +++ b/lib/msal-common/src/request/AuthorizationCodeRequest.ts @@ -15,6 +15,8 @@ import { AuthenticationType } from "../utils/Constants"; * - redirectUri - 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. * - code - The authorization_code that the user acquired in the first leg of the flow. * - codeVerifier - 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 + * - resourceRequestMethod - HTTP Request type used to request data from the resource (i.e. "GET", "POST", etc.). Used for proof-of-possession flows. + * - resourceRequestUri - URI that token will be used for. Used for proof-of-possession flows. */ export type AuthorizationCodeRequest = BaseAuthRequest & { redirectUri: string; diff --git a/lib/msal-common/src/request/AuthorizationUrlRequest.ts b/lib/msal-common/src/request/AuthorizationUrlRequest.ts index 44726d5c52..5622acb580 100644 --- a/lib/msal-common/src/request/AuthorizationUrlRequest.ts +++ b/lib/msal-common/src/request/AuthorizationUrlRequest.ts @@ -30,6 +30,8 @@ import { BaseAuthRequest } from "./BaseAuthRequest"; * - extraQueryParameters - String to string map of custom query parameters. * - claims - In cases where Azure AD tenant admin has enabled conditional access policies, and the policy has not been met, exceptions will contain claims that need to be consented to. * - nonce - 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. + * - resourceRequestMethod - HTTP Request type used to request data from the resource (i.e. "GET", "POST", etc.). Used for proof-of-possession flows. + * - resourceRequestUri - URI that token will be used for. Used for proof-of-possession flows. */ export type AuthorizationUrlRequest = BaseAuthRequest & { authenticationScheme?: AuthenticationType, diff --git a/lib/msal-common/src/request/BaseAuthRequest.ts b/lib/msal-common/src/request/BaseAuthRequest.ts index b6259d9f99..6b5300eaf0 100644 --- a/lib/msal-common/src/request/BaseAuthRequest.ts +++ b/lib/msal-common/src/request/BaseAuthRequest.ts @@ -8,9 +8,13 @@ * - scopes - Array of scopes the application is requesting access to. * - authority - 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. * - correlationId - Unique GUID set per request to trace a request end-to-end for telemetry purposes. + * - resourceRequestMethod - HTTP Request type used to request data from the resource (i.e. "GET", "POST", etc.). Used for proof-of-possession flows. + * - resourceRequestUri - URI that token will be used for. Used for proof-of-possession flows. */ export type BaseAuthRequest = { scopes: Array; authority?: string; correlationId?: string; + resourceRequestMethod?: string; + resourceRequestUri?: string; }; diff --git a/lib/msal-common/src/request/DeviceCodeRequest.ts b/lib/msal-common/src/request/DeviceCodeRequest.ts index e8ee02bc44..361cae329f 100644 --- a/lib/msal-common/src/request/DeviceCodeRequest.ts +++ b/lib/msal-common/src/request/DeviceCodeRequest.ts @@ -13,6 +13,8 @@ import { BaseAuthRequest } from "./BaseAuthRequest"; * - correlationId - Unique GUID set per request to trace a request end-to-end for telemetry purposes. * - deviceCodeCallback - Callback containing device code response. Message should be shown to end user. End user can then navigate to the verification_uri, input the user_code, and input credentials. * - cancel - Boolean to cancel polling of device code endpoint. While the user authenticates on a separate device, MSAL polls the the token endpoint of security token service for the interval specified in the device code response (usually 15 minutes). To stop polling and cancel the request, set cancel=true. + * - resourceRequestMethod - HTTP Request type used to request data from the resource (i.e. "GET", "POST", etc.). Used for proof-of-possession flows. + * - resourceRequestUri - URI that token will be used for. Used for proof-of-possession flows. */ export type DeviceCodeRequest = BaseAuthRequest & { deviceCodeCallback: (response: DeviceCodeResponse) => void; diff --git a/lib/msal-common/src/request/RefreshTokenRequest.ts b/lib/msal-common/src/request/RefreshTokenRequest.ts index 1e555f76b5..39346b60b7 100644 --- a/lib/msal-common/src/request/RefreshTokenRequest.ts +++ b/lib/msal-common/src/request/RefreshTokenRequest.ts @@ -11,6 +11,8 @@ import { BaseAuthRequest } from "./BaseAuthRequest"; * - authority - URL of the authority, the security token service (STS) from which MSAL will acquire tokens. * - correlationId - Unique GUID set per request to trace a request end-to-end for telemetry purposes. * - refreshToken - A refresh token returned from a previous request to the Identity provider. + * - resourceRequestMethod - HTTP Request type used to request data from the resource (i.e. "GET", "POST", etc.). Used for proof-of-possession flows. + * - resourceRequestUri - URI that token will be used for. Used for proof-of-possession flows. */ export type RefreshTokenRequest = BaseAuthRequest & { refreshToken: string; diff --git a/lib/msal-common/src/request/RequestParameterBuilder.ts b/lib/msal-common/src/request/RequestParameterBuilder.ts index cf08d13b07..3b6d91b44d 100644 --- a/lib/msal-common/src/request/RequestParameterBuilder.ts +++ b/lib/msal-common/src/request/RequestParameterBuilder.ts @@ -249,7 +249,7 @@ export class RequestParameterBuilder { * add pop_jwk to query params * @param cnfString */ - addPopToken(cnfString: string) { + addPopToken(cnfString: string): void { if (!StringUtils.isEmpty(cnfString)) { this.parameters.set(AADServerParamKeys.TOKEN_TYPE, AuthenticationType.POP); this.parameters.set(AADServerParamKeys.REQ_CNF, encodeURIComponent(cnfString)); diff --git a/lib/msal-common/src/request/SilentFlowRequest.ts b/lib/msal-common/src/request/SilentFlowRequest.ts index 76985e9214..14c7404ae3 100644 --- a/lib/msal-common/src/request/SilentFlowRequest.ts +++ b/lib/msal-common/src/request/SilentFlowRequest.ts @@ -13,6 +13,8 @@ import { BaseAuthRequest } from "./BaseAuthRequest"; * - correlationId - Unique GUID set per request to trace a request end-to-end for telemetry purposes. * - account - Account entity to lookup the credentials. * - forceRefresh - Forces silent requests to make network calls if true. + * - resourceRequestMethod - HTTP Request type used to request data from the resource (i.e. "GET", "POST", etc.). Used for proof-of-possession flows. + * - resourceRequestUri - URI that token will be used for. Used for proof-of-possession flows. */ export type SilentFlowRequest = BaseAuthRequest & { account: AccountInfo; diff --git a/lib/msal-common/src/response/AuthenticationResult.ts b/lib/msal-common/src/response/AuthenticationResult.ts index d422bb5c90..66215d7cea 100644 --- a/lib/msal-common/src/response/AuthenticationResult.ts +++ b/lib/msal-common/src/response/AuthenticationResult.ts @@ -30,6 +30,7 @@ export type AuthenticationResult = { accessToken: string; fromCache: boolean; expiresOn: Date; + tokenType: string; extExpiresOn?: Date; state?: string; familyId?: string; diff --git a/lib/msal-common/src/response/ResponseHandler.ts b/lib/msal-common/src/response/ResponseHandler.ts index 31969371a6..05b7ddb26b 100644 --- a/lib/msal-common/src/response/ResponseHandler.ts +++ b/lib/msal-common/src/response/ResponseHandler.ts @@ -10,7 +10,7 @@ import { StringUtils } from "../utils/StringUtils"; import { ServerAuthorizationCodeResponse } from "./ServerAuthorizationCodeResponse"; import { Logger } from "../logger/Logger"; import { ServerError } from "../error/ServerError"; -import { IdToken } from "../account/IdToken"; +import { JwtToken } from "../account/JwtToken"; import { ScopeSet } from "../request/ScopeSet"; import { TimeUtils } from "../utils/TimeUtils"; import { AuthenticationResult } from "./AuthenticationResult"; @@ -25,6 +25,8 @@ import { CacheRecord } from "../cache/entities/CacheRecord"; import { TrustedAuthority } from "../authority/TrustedAuthority"; import { CacheManager } from "../cache/CacheManager"; import { ProtocolUtils, LibraryStateObject, RequestStateObject } from "../utils/ProtocolUtils"; +import { AuthenticationType } from "../utils/Constants"; +import { PopTokenGenerator } from "../crypto/PopTokenGenerator"; /** * Class that handles response parsing. @@ -98,9 +100,15 @@ export class ResponseHandler { * @param serverTokenResponse * @param authority */ - handleServerTokenResponse(serverTokenResponse: ServerAuthorizationTokenResponse, authority: Authority, cachedNonce?: string, cachedState?: string): AuthenticationResult { + async handleServerTokenResponse( + serverTokenResponse: ServerAuthorizationTokenResponse, + authority: Authority, + cachedNonce?: string, + cachedState?: string, + resourceRequestMethod?: string, + resourceRequestUri?: string): Promise { // create an idToken object (not entity) - const idTokenObj = new IdToken(serverTokenResponse.id_token, this.cryptoObj); + const idTokenObj = new JwtToken(serverTokenResponse.id_token, this.cryptoObj); // token nonce check (TODO: Add a warning if no nonce is given?) if (!StringUtils.isEmpty(cachedNonce)) { @@ -118,7 +126,7 @@ export class ResponseHandler { const cacheRecord = this.generateCacheRecord(serverTokenResponse, idTokenObj, authority, requestStateObj && requestStateObj.libraryState); this.cacheStorage.saveCacheRecord(cacheRecord); - return ResponseHandler.generateAuthenticationResult(cacheRecord, idTokenObj, false, requestStateObj); + return await ResponseHandler.generateAuthenticationResult(this.cryptoObj, cacheRecord, idTokenObj, false, requestStateObj, resourceRequestMethod, resourceRequestUri); } /** @@ -127,7 +135,7 @@ export class ResponseHandler { * @param idTokenObj * @param authority */ - private generateCacheRecord(serverTokenResponse: ServerAuthorizationTokenResponse, idTokenObj: IdToken, authority: Authority, libraryState?: LibraryStateObject): CacheRecord { + private generateCacheRecord(serverTokenResponse: ServerAuthorizationTokenResponse, idTokenObj: JwtToken, authority: Authority, libraryState?: LibraryStateObject): CacheRecord { // Account const cachedAccount = this.generateAccountEntity( serverTokenResponse, @@ -175,7 +183,8 @@ export class ResponseHandler { idTokenObj.claims.tid, responseScopes.printScopesLowerCase(), tokenExpirationSeconds, - extendedTokenExpirationSeconds + extendedTokenExpirationSeconds, + serverTokenResponse.token_type ); } @@ -200,7 +209,7 @@ export class ResponseHandler { * @param idToken * @param authority */ - private generateAccountEntity(serverTokenResponse: ServerAuthorizationTokenResponse, idToken: IdToken, authority: Authority): AccountEntity { + private generateAccountEntity(serverTokenResponse: ServerAuthorizationTokenResponse, idToken: JwtToken, authority: Authority): AccountEntity { const authorityType = authority.authorityType; if (StringUtils.isEmpty(serverTokenResponse.client_info)) { @@ -222,33 +231,41 @@ export class ResponseHandler { * @param fromTokenCache * @param stateString */ - static generateAuthenticationResult(cacheRecord: CacheRecord, idTokenObj: IdToken, fromTokenCache: boolean, requestState?: RequestStateObject): AuthenticationResult { + static async generateAuthenticationResult(cryptoObj: ICrypto, cacheRecord: CacheRecord, idTokenObj: JwtToken, fromTokenCache: boolean, requestState?: RequestStateObject, resourceRequestMethod?: string, resourceRequestUri?: string): Promise { let accessToken: string = ""; let responseScopes: Array = []; let expiresOn: Date = null; let extExpiresOn: Date = null; let familyId: string = null; if (cacheRecord.accessToken) { - accessToken = cacheRecord.accessToken.secret; + if (cacheRecord.accessToken.tokenType === AuthenticationType.POP) { + const popTokenGenerator: PopTokenGenerator = new PopTokenGenerator(cryptoObj); + accessToken = await popTokenGenerator.signPopToken(cacheRecord.accessToken.secret, resourceRequestMethod, resourceRequestUri); + } else { + accessToken = cacheRecord.accessToken.secret; + } responseScopes = ScopeSet.fromString(cacheRecord.accessToken.target).asArray(); expiresOn = new Date(Number(cacheRecord.accessToken.expiresOn) * 1000); extExpiresOn = new Date(Number(cacheRecord.accessToken.extendedExpiresOn) * 1000); } + if (cacheRecord.refreshToken) { familyId = cacheRecord.refreshToken.familyId || null; } + return { uniqueId: idTokenObj.claims.oid || idTokenObj.claims.sub, tenantId: idTokenObj.claims.tid, scopes: responseScopes, account: cacheRecord.account.getAccountInfo(), - idToken: idTokenObj.rawIdToken, + idToken: idTokenObj.rawToken, idTokenClaims: idTokenObj.claims, accessToken: accessToken, fromCache: fromTokenCache, expiresOn: expiresOn, extExpiresOn: extExpiresOn, familyId: familyId, + tokenType: cacheRecord.accessToken.tokenType, state: requestState ? requestState.userRequestState : "" }; } diff --git a/lib/msal-common/src/url/IUri.ts b/lib/msal-common/src/url/IUri.ts index f31cdec495..2a97323ea7 100644 --- a/lib/msal-common/src/url/IUri.ts +++ b/lib/msal-common/src/url/IUri.ts @@ -13,4 +13,5 @@ export interface IUri { Search: string; Hash: string; PathSegments: string[]; + QueryString: string; } diff --git a/lib/msal-common/src/url/UrlString.ts b/lib/msal-common/src/url/UrlString.ts index ebd134758a..aaf6fb13d1 100644 --- a/lib/msal-common/src/url/UrlString.ts +++ b/lib/msal-common/src/url/UrlString.ts @@ -131,7 +131,8 @@ export class UrlString { const urlComponents = { Protocol: match[1], HostNameAndPort: match[4], - AbsolutePath: match[5] + AbsolutePath: match[5], + QueryString: match[7] } as IUri; let pathSegments = urlComponents.AbsolutePath.split("/"); diff --git a/lib/msal-common/src/utils/StringUtils.ts b/lib/msal-common/src/utils/StringUtils.ts index b424b11534..38d0115262 100644 --- a/lib/msal-common/src/utils/StringUtils.ts +++ b/lib/msal-common/src/utils/StringUtils.ts @@ -17,12 +17,12 @@ export class StringUtils { */ static decodeJwt(jwtToken: string): DecodedJwt { if (StringUtils.isEmpty(jwtToken)) { - throw ClientAuthError.createIdTokenNullOrEmptyError(jwtToken); + throw ClientAuthError.createTokenNullOrEmptyError(jwtToken); } const idTokenPartsRegex = /^([^\.\s]*)\.([^\.\s]+)\.([^\.\s]*)$/; const matches = idTokenPartsRegex.exec(jwtToken); if (!matches || matches.length < 4) { - throw ClientAuthError.createIdTokenParsingError(`Given token is malformed: ${JSON.stringify(jwtToken)}`); + throw ClientAuthError.createTokenParsingError(`Given token is malformed: ${JSON.stringify(jwtToken)}`); } const crackedToken: DecodedJwt = { header: matches[1], diff --git a/samples/msal-browser-samples/VanillaJSTestApp2.0/app/default/authConfig.js b/samples/msal-browser-samples/VanillaJSTestApp2.0/app/default/authConfig.js index cfcb622a20..7913deb82d 100644 --- a/samples/msal-browser-samples/VanillaJSTestApp2.0/app/default/authConfig.js +++ b/samples/msal-browser-samples/VanillaJSTestApp2.0/app/default/authConfig.js @@ -33,18 +33,20 @@ const msalConfig = { } }; -// Add here scopes for id token to be used at MS Identity Platform endpoints. -const loginRequest = { - scopes: ["User.Read"], - authenticationType: "pop" -}; - // Add here the endpoints for MS Graph API services you would like to use. const graphConfig = { graphMeEndpoint: "https://graph.microsoft-ppe.com/v1.0/me", graphMailEndpoint: "https://graph.microsoft-ppe.com/v1.0/me/messages" }; +// Add here scopes for id token to be used at MS Identity Platform endpoints. +const loginRequest = { + scopes: ["User.Read"], + authenticationScheme: "pop", + resourceRequestMethod: "POST", + resourceRequestUri: graphConfig.graphMeEndpoint +}; + // Add here scopes for access token to be used at MS Graph API endpoints. const tokenRequest = { scopes: ["Mail.Read"], From 4d8104cde8f3b237f6359061d1178cf4a30767ab Mon Sep 17 00:00:00 2001 From: Prithvi Kanherkar Date: Tue, 18 Aug 2020 10:11:29 -0700 Subject: [PATCH 04/17] Update authConfig.js --- .../VanillaJSTestApp2.0/app/pop/authConfig.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/samples/msal-browser-samples/VanillaJSTestApp2.0/app/pop/authConfig.js b/samples/msal-browser-samples/VanillaJSTestApp2.0/app/pop/authConfig.js index 7deb921cb8..b7388f4ef1 100644 --- a/samples/msal-browser-samples/VanillaJSTestApp2.0/app/pop/authConfig.js +++ b/samples/msal-browser-samples/VanillaJSTestApp2.0/app/pop/authConfig.js @@ -33,18 +33,20 @@ const msalConfig = { } }; -// Add here scopes for id token to be used at MS Identity Platform endpoints. -const loginRequest = { - scopes: ["User.Read"], - authenticationScheme: msal.AuthenticationScheme.POP -}; - // Add here the endpoints for MS Graph API services you would like to use. const graphConfig = { graphMeEndpoint: "https://graph.microsoft-ppe.com/v1.0/me", graphMailEndpoint: "https://graph.microsoft-ppe.com/v1.0/me/messages" }; +// Add here scopes for id token to be used at MS Identity Platform endpoints. +const loginRequest = { + scopes: ["User.Read"], + authenticationScheme: msal.AuthenticationScheme.POP, + resourceRequestMethod: "POST", + resourceRequestUri: graphConfig.graphMeEndpoint +}; + // Add here scopes for access token to be used at MS Graph API endpoints. const tokenRequest = { scopes: ["Mail.Read"], From 9ee748b24e3d6ea98737f15b7b0a77f2a59d4935 Mon Sep 17 00:00:00 2001 From: Prithvi Kanherkar Date: Tue, 18 Aug 2020 10:18:17 -0700 Subject: [PATCH 05/17] change to authenticationScheme --- lib/msal-common/src/cache/entities/AccessTokenEntity.ts | 4 ++-- lib/msal-common/src/response/ResponseHandler.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/msal-common/src/cache/entities/AccessTokenEntity.ts b/lib/msal-common/src/cache/entities/AccessTokenEntity.ts index 1bd3296cd3..f0c3f36269 100644 --- a/lib/msal-common/src/cache/entities/AccessTokenEntity.ts +++ b/lib/msal-common/src/cache/entities/AccessTokenEntity.ts @@ -4,7 +4,7 @@ */ import { CredentialEntity } from "./CredentialEntity"; -import { CredentialType, AuthenticationType } from "../../utils/Constants"; +import { CredentialType, AuthenticationScheme } from "../../utils/Constants"; import { TimeUtils } from "../../utils/TimeUtils"; import { StringUtils } from "../../utils/StringUtils"; @@ -83,7 +83,7 @@ export class AccessTokenEntity extends CredentialEntity { atEntity.realm = tenantId; atEntity.target = scopes; - atEntity.tokenType = StringUtils.isEmpty(tokenType) ? AuthenticationType.BEARER : tokenType; + atEntity.tokenType = StringUtils.isEmpty(tokenType) ? AuthenticationScheme.BEARER : tokenType; return atEntity; } } diff --git a/lib/msal-common/src/response/ResponseHandler.ts b/lib/msal-common/src/response/ResponseHandler.ts index 05b7ddb26b..85fb1d0797 100644 --- a/lib/msal-common/src/response/ResponseHandler.ts +++ b/lib/msal-common/src/response/ResponseHandler.ts @@ -25,7 +25,7 @@ import { CacheRecord } from "../cache/entities/CacheRecord"; import { TrustedAuthority } from "../authority/TrustedAuthority"; import { CacheManager } from "../cache/CacheManager"; import { ProtocolUtils, LibraryStateObject, RequestStateObject } from "../utils/ProtocolUtils"; -import { AuthenticationType } from "../utils/Constants"; +import { AuthenticationScheme } from "../utils/Constants"; import { PopTokenGenerator } from "../crypto/PopTokenGenerator"; /** @@ -238,7 +238,7 @@ export class ResponseHandler { let extExpiresOn: Date = null; let familyId: string = null; if (cacheRecord.accessToken) { - if (cacheRecord.accessToken.tokenType === AuthenticationType.POP) { + if (cacheRecord.accessToken.tokenType === AuthenticationScheme.POP) { const popTokenGenerator: PopTokenGenerator = new PopTokenGenerator(cryptoObj); accessToken = await popTokenGenerator.signPopToken(cacheRecord.accessToken.secret, resourceRequestMethod, resourceRequestUri); } else { From 224f9a73452a85fa348ccb4f7370a8123264cfb0 Mon Sep 17 00:00:00 2001 From: Prithvi Kanherkar Date: Tue, 18 Aug 2020 10:20:45 -0700 Subject: [PATCH 06/17] Update CryptoOps.ts --- lib/msal-browser/src/crypto/CryptoOps.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/msal-browser/src/crypto/CryptoOps.ts b/lib/msal-browser/src/crypto/CryptoOps.ts index b833f08243..e08e219cd6 100644 --- a/lib/msal-browser/src/crypto/CryptoOps.ts +++ b/lib/msal-browser/src/crypto/CryptoOps.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ -import { ICrypto, PkceCodes } from "@azure/msal-common"; +import { ICrypto, PkceCodes, SignedHttpRequest } from "@azure/msal-common"; import { GuidGenerator } from "./GuidGenerator"; import { Base64Encode } from "../encode/Base64Encode"; import { Base64Decode } from "../encode/Base64Decode"; @@ -74,4 +74,8 @@ export class CryptoOps implements ICrypto { async generatePkceCodes(): Promise { return this.pkceGenerator.generateCodes(); } + + signJwt(payload: SignedHttpRequest, kid: string): Promise { + throw new Error("Method not implemented."); + } } From 536987e54ed708cb12b0e629c77161a79a872ba2 Mon Sep 17 00:00:00 2001 From: Prithvi Kanherkar Date: Tue, 18 Aug 2020 10:32:22 -0700 Subject: [PATCH 07/17] removing id token references from JwtToken class --- lib/msal-common/src/account/JwtToken.ts | 16 ++++++++-------- lib/msal-common/src/error/ClientAuthError.ts | 15 +-------------- lib/msal-common/src/index.ts | 4 ++-- lib/msal-common/src/utils/Constants.ts | 18 ------------------ lib/msal-common/src/utils/StringUtils.ts | 4 ++-- 5 files changed, 13 insertions(+), 44 deletions(-) diff --git a/lib/msal-common/src/account/JwtToken.ts b/lib/msal-common/src/account/JwtToken.ts index b1375941e6..777add5990 100644 --- a/lib/msal-common/src/account/JwtToken.ts +++ b/lib/msal-common/src/account/JwtToken.ts @@ -9,13 +9,13 @@ import { StringUtils } from "../utils/StringUtils"; import { ICrypto } from "../crypto/ICrypto"; /** - * Id Token representation class. Parses id token string and generates claims object. + * JWT Token representation class. Parses token string and generates claims object. */ export class JwtToken { - // Raw Id Token string + // Raw Token string rawToken: string; - // Claims inside Id Token + // Claims inside token claims: TokenClaims; constructor(rawToken: string, crypto: ICrypto) { if (StringUtils.isEmpty(rawToken)) { @@ -27,13 +27,13 @@ export class JwtToken { } /** - * Extract IdToken by decoding the RAWIdToken + * Extract token by decoding the rawToken * - * @param encodedIdToken + * @param encodedToken */ - static extractTokenClaims(encodedIdToken: string, crypto: ICrypto): TokenClaims { - // id token will be decoded to get the username - const decodedToken: DecodedJwt = StringUtils.decodeJwt(encodedIdToken); + static extractTokenClaims(encodedToken: string, crypto: ICrypto): TokenClaims { + // token will be decoded to get the username + const decodedToken: DecodedJwt = StringUtils.decodeJwt(encodedToken); if (!decodedToken) { return null; } diff --git a/lib/msal-common/src/error/ClientAuthError.ts b/lib/msal-common/src/error/ClientAuthError.ts index 7ef63afa66..1ae05584a7 100644 --- a/lib/msal-common/src/error/ClientAuthError.ts +++ b/lib/msal-common/src/error/ClientAuthError.ts @@ -58,10 +58,6 @@ export const ClientAuthErrorMessage = { code: "account_mismatch", desc: "The cached account and account which made the token request do not match." }, - invalidIdToken: { - code: "invalid_id_token", - desc: "Invalid ID token format." - }, noTokensFoundError: { code: "no_tokens_found", desc: "No tokens were found for the given scopes, and no authorization code was passed to acquireToken. You must retrieve an authorization code before making a call to acquireToken()." @@ -202,7 +198,7 @@ export class ClientAuthError extends AuthError { */ static createTokenNullOrEmptyError(invalidRawTokenString: string) : ClientAuthError { return new ClientAuthError(ClientAuthErrorMessage.nullOrEmptyToken.code, - `${ClientAuthErrorMessage.nullOrEmptyToken.desc} Raw ID Token Value: ${invalidRawTokenString}`); + `${ClientAuthErrorMessage.nullOrEmptyToken.desc} Raw Token Value: ${invalidRawTokenString}`); } /** @@ -264,15 +260,6 @@ export class ClientAuthError extends AuthError { ClientAuthErrorMessage.accountMismatchError.desc); } - /** - * Throws error if idToken is not correctly formed - * @param idToken - */ - static createInvalidIdTokenError(idToken: JwtToken) : ClientAuthError { - return new ClientAuthError(ClientAuthErrorMessage.invalidIdToken.code, - `${ClientAuthErrorMessage.invalidIdToken.desc} Given token: ${JSON.stringify(idToken)}`); - } - /** * Creates an error thrown when the authorization code required for a token request is null or empty. */ diff --git a/lib/msal-common/src/index.ts b/lib/msal-common/src/index.ts index 387fedd5bc..5c117c8032 100644 --- a/lib/msal-common/src/index.ts +++ b/lib/msal-common/src/index.ts @@ -7,8 +7,8 @@ export { AuthOptions, SystemOptions, LoggerOptions, DEFAULT_SYSTEM_OPTIONS } fro export { ClientConfiguration } from "./config/ClientConfiguration"; // Account export { AccountInfo } from "./account/AccountInfo"; -export { JwtToken as IdToken } from "./account/JwtToken"; -export { TokenClaims as IdTokenClaims } from "./account/TokenClaims"; +export { JwtToken } from "./account/JwtToken"; +export { TokenClaims } from "./account/TokenClaims"; // Authority export { Authority } from "./authority/Authority"; export { CloudDiscoveryMetadata } from "./authority/CloudDiscoveryMetadata"; diff --git a/lib/msal-common/src/utils/Constants.ts b/lib/msal-common/src/utils/Constants.ts index 192af20983..7d6203235b 100644 --- a/lib/msal-common/src/utils/Constants.ts +++ b/lib/msal-common/src/utils/Constants.ts @@ -106,24 +106,6 @@ export enum AADServerParamKeys { REQ_CNF = "req_cnf" } -/** - * IdToken claim string constants - */ -export enum IdTokenClaimName { - ISSUER = "iss", - OBJID = "oid", - SUBJECT = "sub", - TENANTID = "tid", - VERSION = "ver", - PREF_USERNAME = "preferred_username", - NAME = "name", - NONCE = "nonce", - EXPIRATION = "exp", - HOME_OBJID = "home_oid", - SESSIONID = "sid", - CLOUD_INSTANCE_HOSTNAME = "cloud_instance_host_name" -} - /** * we considered making this "enum" in the request instead of string, however it looks like the allowed list of * prompt values kept changing over past couple of years. There are some undocumented prompt values for some diff --git a/lib/msal-common/src/utils/StringUtils.ts b/lib/msal-common/src/utils/StringUtils.ts index 38d0115262..a5af72758f 100644 --- a/lib/msal-common/src/utils/StringUtils.ts +++ b/lib/msal-common/src/utils/StringUtils.ts @@ -19,8 +19,8 @@ export class StringUtils { if (StringUtils.isEmpty(jwtToken)) { throw ClientAuthError.createTokenNullOrEmptyError(jwtToken); } - const idTokenPartsRegex = /^([^\.\s]*)\.([^\.\s]+)\.([^\.\s]*)$/; - const matches = idTokenPartsRegex.exec(jwtToken); + const tokenPartsRegex = /^([^\.\s]*)\.([^\.\s]+)\.([^\.\s]*)$/; + const matches = tokenPartsRegex.exec(jwtToken); if (!matches || matches.length < 4) { throw ClientAuthError.createTokenParsingError(`Given token is malformed: ${JSON.stringify(jwtToken)}`); } From ab4d4185f2d6b2cf150d98db0bbde03eec58e43f Mon Sep 17 00:00:00 2001 From: Prithvi Kanherkar Date: Tue, 18 Aug 2020 12:13:01 -0700 Subject: [PATCH 08/17] Updating token exports in browser --- lib/msal-browser/src/app/PublicClientApplication.ts | 4 ++-- lib/msal-common/src/error/ClientAuthError.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/msal-browser/src/app/PublicClientApplication.ts b/lib/msal-browser/src/app/PublicClientApplication.ts index 82266e4ab1..7791b2d126 100644 --- a/lib/msal-browser/src/app/PublicClientApplication.ts +++ b/lib/msal-browser/src/app/PublicClientApplication.ts @@ -15,7 +15,7 @@ import { TrustedAuthority, AuthorizationUrlRequest, PersistentCacheKeys, - IdToken, + JwtToken, ProtocolUtils, AuthorizationCodeRequest, Constants, @@ -681,7 +681,7 @@ export class PublicClientApplication implements IPublicClientApplication { // Only check for adal token if no SSO params are being used const adalIdTokenString = this.browserStorage.getItem(PersistentCacheKeys.ADAL_ID_TOKEN, CacheSchemaType.TEMPORARY) as string; if (!StringUtils.isEmpty(adalIdTokenString)) { - const adalIdToken = new IdToken(adalIdTokenString, this.browserCrypto); + const adalIdToken = new JwtToken(adalIdTokenString, this.browserCrypto); this.browserStorage.removeItem(PersistentCacheKeys.ADAL_ID_TOKEN); if (adalIdToken.claims && adalIdToken.claims.upn) { validatedRequest.loginHint = adalIdToken.claims.upn; diff --git a/lib/msal-common/src/error/ClientAuthError.ts b/lib/msal-common/src/error/ClientAuthError.ts index 1ae05584a7..dae3d61f22 100644 --- a/lib/msal-common/src/error/ClientAuthError.ts +++ b/lib/msal-common/src/error/ClientAuthError.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ import { AuthError } from "./AuthError"; -import { JwtToken } from "../account/JwtToken"; import { ScopeSet } from "../request/ScopeSet"; /** From 7719ee40684c9248518ff2dcbe7017cab87aae1d Mon Sep 17 00:00:00 2001 From: Prithvi Kanherkar Date: Thu, 27 Aug 2020 14:19:01 -0700 Subject: [PATCH 09/17] rename JwtToken to AuthToken --- .../src/account/{JwtToken.ts => AuthToken.ts} | 8 ++++---- .../{DecodedJwt.ts => DecodedAuthToken.ts} | 2 +- .../src/cache/entities/AccountEntity.ts | 6 +++--- lib/msal-common/src/client/SilentFlowClient.ts | 4 ++-- lib/msal-common/src/crypto/PopTokenGenerator.ts | 4 ++-- lib/msal-common/src/index.ts | 2 +- lib/msal-common/src/response/ResponseHandler.ts | 10 +++++----- lib/msal-common/src/utils/StringUtils.ts | 16 ++++++++-------- 8 files changed, 26 insertions(+), 26 deletions(-) rename lib/msal-common/src/account/{JwtToken.ts => AuthToken.ts} (85%) rename lib/msal-common/src/account/{DecodedJwt.ts => DecodedAuthToken.ts} (85%) diff --git a/lib/msal-common/src/account/JwtToken.ts b/lib/msal-common/src/account/AuthToken.ts similarity index 85% rename from lib/msal-common/src/account/JwtToken.ts rename to lib/msal-common/src/account/AuthToken.ts index 777add5990..fa41c271c9 100644 --- a/lib/msal-common/src/account/JwtToken.ts +++ b/lib/msal-common/src/account/AuthToken.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ import { TokenClaims } from "./TokenClaims"; -import { DecodedJwt } from "./DecodedJwt"; +import { DecodedAuthToken } from "./DecodedAuthToken"; import { ClientAuthError } from "../error/ClientAuthError"; import { StringUtils } from "../utils/StringUtils"; import { ICrypto } from "../crypto/ICrypto"; @@ -11,7 +11,7 @@ import { ICrypto } from "../crypto/ICrypto"; /** * JWT Token representation class. Parses token string and generates claims object. */ -export class JwtToken { +export class AuthToken { // Raw Token string rawToken: string; @@ -23,7 +23,7 @@ export class JwtToken { } this.rawToken = rawToken; - this.claims = JwtToken.extractTokenClaims(rawToken, crypto); + this.claims = AuthToken.extractTokenClaims(rawToken, crypto); } /** @@ -33,7 +33,7 @@ export class JwtToken { */ static extractTokenClaims(encodedToken: string, crypto: ICrypto): TokenClaims { // token will be decoded to get the username - const decodedToken: DecodedJwt = StringUtils.decodeJwt(encodedToken); + const decodedToken: DecodedAuthToken = StringUtils.decodeAuthToken(encodedToken); if (!decodedToken) { return null; } diff --git a/lib/msal-common/src/account/DecodedJwt.ts b/lib/msal-common/src/account/DecodedAuthToken.ts similarity index 85% rename from lib/msal-common/src/account/DecodedJwt.ts rename to lib/msal-common/src/account/DecodedAuthToken.ts index 7ec63f6bca..ddbb21bed6 100644 --- a/lib/msal-common/src/account/DecodedJwt.ts +++ b/lib/msal-common/src/account/DecodedAuthToken.ts @@ -6,7 +6,7 @@ /** * Interface for Decoded JWT tokens. */ -export interface DecodedJwt { +export interface DecodedAuthToken { header: string, JWSPayload: string, JWSSig: string diff --git a/lib/msal-common/src/cache/entities/AccountEntity.ts b/lib/msal-common/src/cache/entities/AccountEntity.ts index a1402bd60d..41e69e6b2e 100644 --- a/lib/msal-common/src/cache/entities/AccountEntity.ts +++ b/lib/msal-common/src/cache/entities/AccountEntity.ts @@ -9,7 +9,7 @@ import { CacheType, } from "../../utils/Constants"; import { Authority } from "../../authority/Authority"; -import { JwtToken } from "../../account/JwtToken"; +import { AuthToken } from "../../account/AuthToken"; import { ICrypto } from "../../crypto/ICrypto"; import { buildClientInfo } from "../../account/ClientInfo"; import { StringUtils } from "../../utils/StringUtils"; @@ -125,7 +125,7 @@ export class AccountEntity { static createAccount( clientInfo: string, authority: Authority, - idToken: JwtToken, + idToken: AuthToken, crypto: ICrypto ): AccountEntity { const account: AccountEntity = new AccountEntity(); @@ -164,7 +164,7 @@ export class AccountEntity { */ static createADFSAccount( authority: Authority, - idToken: JwtToken + idToken: AuthToken ): AccountEntity { const account: AccountEntity = new AccountEntity(); diff --git a/lib/msal-common/src/client/SilentFlowClient.ts b/lib/msal-common/src/client/SilentFlowClient.ts index 584612b67e..cfb6f72409 100644 --- a/lib/msal-common/src/client/SilentFlowClient.ts +++ b/lib/msal-common/src/client/SilentFlowClient.ts @@ -12,7 +12,7 @@ import { IdTokenEntity } from "../cache/entities/IdTokenEntity"; import { AccessTokenEntity } from "../cache/entities/AccessTokenEntity"; import { RefreshTokenEntity } from "../cache/entities/RefreshTokenEntity"; import { ScopeSet } from "../request/ScopeSet"; -import { JwtToken } from "../account/JwtToken"; +import { AuthToken } from "../account/AuthToken"; import { TimeUtils } from "../utils/TimeUtils"; import { RefreshTokenRequest } from "../request/RefreshTokenRequest"; import { RefreshTokenClient } from "./RefreshTokenClient"; @@ -77,7 +77,7 @@ export class SilentFlowClient extends BaseClient { // Return tokens from cache this.config.serverTelemetryManager.incrementCacheHits(); const cachedIdToken = this.readIdTokenFromCache(homeAccountId, environment, cachedAccount.realm); - const idTokenObj = new JwtToken(cachedIdToken.secret, this.config.cryptoInterface); + const idTokenObj = new AuthToken(cachedIdToken.secret, this.config.cryptoInterface); return await ResponseHandler.generateAuthenticationResult( this.cryptoUtils, diff --git a/lib/msal-common/src/crypto/PopTokenGenerator.ts b/lib/msal-common/src/crypto/PopTokenGenerator.ts index 56a7aca779..b6ff4ac062 100644 --- a/lib/msal-common/src/crypto/PopTokenGenerator.ts +++ b/lib/msal-common/src/crypto/PopTokenGenerator.ts @@ -1,5 +1,5 @@ import { ICrypto } from "./ICrypto"; -import { JwtToken } from "../account/JwtToken"; +import { AuthToken } from "../account/AuthToken"; import { TokenClaims } from "../account/TokenClaims"; import { TimeUtils } from "../utils/TimeUtils"; import { UrlString } from "../url/UrlString"; @@ -40,7 +40,7 @@ export class PopTokenGenerator { } async signPopToken(accessToken: string, resourceRequestMethod: string, resourceRequestUri: string): Promise { - const tokenClaims: TokenClaims = JwtToken.extractTokenClaims(accessToken, this.cryptoUtils); + const tokenClaims: TokenClaims = AuthToken.extractTokenClaims(accessToken, this.cryptoUtils); const resourceUrlString: UrlString = new UrlString(resourceRequestUri); const resourceUrlComponents: IUri = resourceUrlString.getUrlComponents(); return await this.cryptoUtils.signJwt({ diff --git a/lib/msal-common/src/index.ts b/lib/msal-common/src/index.ts index 5c117c8032..8354c3d097 100644 --- a/lib/msal-common/src/index.ts +++ b/lib/msal-common/src/index.ts @@ -7,7 +7,7 @@ export { AuthOptions, SystemOptions, LoggerOptions, DEFAULT_SYSTEM_OPTIONS } fro export { ClientConfiguration } from "./config/ClientConfiguration"; // Account export { AccountInfo } from "./account/AccountInfo"; -export { JwtToken } from "./account/JwtToken"; +export { AuthToken as JwtToken } from "./account/AuthToken"; export { TokenClaims } from "./account/TokenClaims"; // Authority export { Authority } from "./authority/Authority"; diff --git a/lib/msal-common/src/response/ResponseHandler.ts b/lib/msal-common/src/response/ResponseHandler.ts index 85fb1d0797..ca01757a0b 100644 --- a/lib/msal-common/src/response/ResponseHandler.ts +++ b/lib/msal-common/src/response/ResponseHandler.ts @@ -10,7 +10,7 @@ import { StringUtils } from "../utils/StringUtils"; import { ServerAuthorizationCodeResponse } from "./ServerAuthorizationCodeResponse"; import { Logger } from "../logger/Logger"; import { ServerError } from "../error/ServerError"; -import { JwtToken } from "../account/JwtToken"; +import { AuthToken } from "../account/AuthToken"; import { ScopeSet } from "../request/ScopeSet"; import { TimeUtils } from "../utils/TimeUtils"; import { AuthenticationResult } from "./AuthenticationResult"; @@ -108,7 +108,7 @@ export class ResponseHandler { resourceRequestMethod?: string, resourceRequestUri?: string): Promise { // create an idToken object (not entity) - const idTokenObj = new JwtToken(serverTokenResponse.id_token, this.cryptoObj); + const idTokenObj = new AuthToken(serverTokenResponse.id_token, this.cryptoObj); // token nonce check (TODO: Add a warning if no nonce is given?) if (!StringUtils.isEmpty(cachedNonce)) { @@ -135,7 +135,7 @@ export class ResponseHandler { * @param idTokenObj * @param authority */ - private generateCacheRecord(serverTokenResponse: ServerAuthorizationTokenResponse, idTokenObj: JwtToken, authority: Authority, libraryState?: LibraryStateObject): CacheRecord { + private generateCacheRecord(serverTokenResponse: ServerAuthorizationTokenResponse, idTokenObj: AuthToken, authority: Authority, libraryState?: LibraryStateObject): CacheRecord { // Account const cachedAccount = this.generateAccountEntity( serverTokenResponse, @@ -209,7 +209,7 @@ export class ResponseHandler { * @param idToken * @param authority */ - private generateAccountEntity(serverTokenResponse: ServerAuthorizationTokenResponse, idToken: JwtToken, authority: Authority): AccountEntity { + private generateAccountEntity(serverTokenResponse: ServerAuthorizationTokenResponse, idToken: AuthToken, authority: Authority): AccountEntity { const authorityType = authority.authorityType; if (StringUtils.isEmpty(serverTokenResponse.client_info)) { @@ -231,7 +231,7 @@ export class ResponseHandler { * @param fromTokenCache * @param stateString */ - static async generateAuthenticationResult(cryptoObj: ICrypto, cacheRecord: CacheRecord, idTokenObj: JwtToken, fromTokenCache: boolean, requestState?: RequestStateObject, resourceRequestMethod?: string, resourceRequestUri?: string): Promise { + static async generateAuthenticationResult(cryptoObj: ICrypto, cacheRecord: CacheRecord, idTokenObj: AuthToken, fromTokenCache: boolean, requestState?: RequestStateObject, resourceRequestMethod?: string, resourceRequestUri?: string): Promise { let accessToken: string = ""; let responseScopes: Array = []; let expiresOn: Date = null; diff --git a/lib/msal-common/src/utils/StringUtils.ts b/lib/msal-common/src/utils/StringUtils.ts index a5af72758f..729dbc6b6f 100644 --- a/lib/msal-common/src/utils/StringUtils.ts +++ b/lib/msal-common/src/utils/StringUtils.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ -import { DecodedJwt } from "../account/DecodedJwt"; +import { DecodedAuthToken } from "../account/DecodedAuthToken"; import { ClientAuthError } from "../error/ClientAuthError"; /** @@ -13,18 +13,18 @@ export class StringUtils { /** * decode a JWT * - * @param jwtToken + * @param authToken */ - static decodeJwt(jwtToken: string): DecodedJwt { - if (StringUtils.isEmpty(jwtToken)) { - throw ClientAuthError.createTokenNullOrEmptyError(jwtToken); + static decodeAuthToken(authToken: string): DecodedAuthToken { + if (StringUtils.isEmpty(authToken)) { + throw ClientAuthError.createTokenNullOrEmptyError(authToken); } const tokenPartsRegex = /^([^\.\s]*)\.([^\.\s]+)\.([^\.\s]*)$/; - const matches = tokenPartsRegex.exec(jwtToken); + const matches = tokenPartsRegex.exec(authToken); if (!matches || matches.length < 4) { - throw ClientAuthError.createTokenParsingError(`Given token is malformed: ${JSON.stringify(jwtToken)}`); + throw ClientAuthError.createTokenParsingError(`Given token is malformed: ${JSON.stringify(authToken)}`); } - const crackedToken: DecodedJwt = { + const crackedToken: DecodedAuthToken = { header: matches[1], JWSPayload: matches[2], JWSSig: matches[3] From 19f9780902ea42fdd0b04f93fe2e806f2ee0274e Mon Sep 17 00:00:00 2001 From: Prithvi Kanherkar Date: Thu, 27 Aug 2020 14:23:38 -0700 Subject: [PATCH 10/17] Adding changes from PR feedback --- lib/msal-common/src/client/AuthorizationCodeClient.ts | 2 +- lib/msal-common/src/client/DeviceCodeClient.ts | 2 -- lib/msal-common/src/index.ts | 4 +++- lib/msal-common/src/response/ResponseHandler.ts | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/msal-common/src/client/AuthorizationCodeClient.ts b/lib/msal-common/src/client/AuthorizationCodeClient.ts index 294af86930..1aafc4c737 100644 --- a/lib/msal-common/src/client/AuthorizationCodeClient.ts +++ b/lib/msal-common/src/client/AuthorizationCodeClient.ts @@ -71,7 +71,7 @@ export class AuthorizationCodeClient extends BaseClient { // Validate response. This function throws a server error if an error is returned by the server. responseHandler.validateTokenResponse(response.body); - return await responseHandler.handleServerTokenResponse(response.body, this.authority, cachedNonce, cachedState, request.resourceRequestMethod, request.resourceRequestUri); + return await responseHandler.handleServerTokenResponse(response.body, this.authority, request.resourceRequestMethod, request.resourceRequestUri, cachedNonce, cachedState); } /** diff --git a/lib/msal-common/src/client/DeviceCodeClient.ts b/lib/msal-common/src/client/DeviceCodeClient.ts index e63f2d0d2f..038bbcfb8d 100644 --- a/lib/msal-common/src/client/DeviceCodeClient.ts +++ b/lib/msal-common/src/client/DeviceCodeClient.ts @@ -50,8 +50,6 @@ export class DeviceCodeClient extends BaseClient { return await responseHandler.handleServerTokenResponse( response, this.authority, - "", - "", request.resourceRequestMethod, request.resourceRequestUri ); diff --git a/lib/msal-common/src/index.ts b/lib/msal-common/src/index.ts index 8354c3d097..e3c91425fe 100644 --- a/lib/msal-common/src/index.ts +++ b/lib/msal-common/src/index.ts @@ -7,8 +7,10 @@ export { AuthOptions, SystemOptions, LoggerOptions, DEFAULT_SYSTEM_OPTIONS } fro export { ClientConfiguration } from "./config/ClientConfiguration"; // Account export { AccountInfo } from "./account/AccountInfo"; -export { AuthToken as JwtToken } from "./account/AuthToken"; +export { AuthToken } from "./account/AuthToken"; +export { AuthToken as IdToken } from "./account/AuthToken"; export { TokenClaims } from "./account/TokenClaims"; +export { TokenClaims as IdTokenClaims } from "./account/TokenClaims"; // Authority export { Authority } from "./authority/Authority"; export { CloudDiscoveryMetadata } from "./authority/CloudDiscoveryMetadata"; diff --git a/lib/msal-common/src/response/ResponseHandler.ts b/lib/msal-common/src/response/ResponseHandler.ts index ca01757a0b..6941c5277b 100644 --- a/lib/msal-common/src/response/ResponseHandler.ts +++ b/lib/msal-common/src/response/ResponseHandler.ts @@ -102,11 +102,11 @@ export class ResponseHandler { */ async handleServerTokenResponse( serverTokenResponse: ServerAuthorizationTokenResponse, - authority: Authority, - cachedNonce?: string, - cachedState?: string, + authority: Authority, resourceRequestMethod?: string, - resourceRequestUri?: string): Promise { + resourceRequestUri?: string, + cachedNonce?: string, + cachedState?: string): Promise { // create an idToken object (not entity) const idTokenObj = new AuthToken(serverTokenResponse.id_token, this.cryptoObj); From 56070a9be2f37b249887c2d71843a3b5f94f0074 Mon Sep 17 00:00:00 2001 From: Prithvi Kanherkar Date: Thu, 27 Aug 2020 15:24:42 -0700 Subject: [PATCH 11/17] Fixing msal-common tests --- .../src/response/ResponseHandler.ts | 2 +- lib/msal-common/src/url/UrlString.ts | 4 ++ .../test/account/ClientInfo.spec.ts | 3 ++ lib/msal-common/test/account/IdToken.spec.ts | 47 +++++++++--------- .../test/cache/entities/AccountEntity.spec.ts | 9 ++-- .../client/AuthorizationCodeClient.spec.ts | 4 +- .../test/client/BaseClient.spec.ts | 2 +- .../test/client/ClientTestUtils.ts | 3 ++ .../test/client/DeviceCodeClient.spec.ts | 4 +- .../test/client/RefreshTokenClient.spec.ts | 9 ++-- .../test/client/SilentFlowClient.spec.ts | 9 ++-- .../test/config/ClientConfiguration.spec.ts | 3 ++ .../test/crypto/PopTokenGenerator.spec.ts | 7 ++- .../test/error/ClientAuthError.spec.ts | 29 ++++------- .../test/response/ResponseHandler.spec.ts | 48 ++++++++++--------- lib/msal-common/test/url/UrlString.spec.ts | 6 +-- .../test/utils/ProtocolUtils.spec.ts | 3 ++ .../test/utils/StringUtils.spec.ts | 26 +++++----- 18 files changed, 116 insertions(+), 102 deletions(-) diff --git a/lib/msal-common/src/response/ResponseHandler.ts b/lib/msal-common/src/response/ResponseHandler.ts index 6941c5277b..bd1c4b8d76 100644 --- a/lib/msal-common/src/response/ResponseHandler.ts +++ b/lib/msal-common/src/response/ResponseHandler.ts @@ -265,7 +265,7 @@ export class ResponseHandler { expiresOn: expiresOn, extExpiresOn: extExpiresOn, familyId: familyId, - tokenType: cacheRecord.accessToken.tokenType, + tokenType: cacheRecord.accessToken ? cacheRecord.accessToken.tokenType : "", state: requestState ? requestState.userRequestState : "" }; } diff --git a/lib/msal-common/src/url/UrlString.ts b/lib/msal-common/src/url/UrlString.ts index aaf6fb13d1..64cbdce483 100644 --- a/lib/msal-common/src/url/UrlString.ts +++ b/lib/msal-common/src/url/UrlString.ts @@ -138,6 +138,10 @@ export class UrlString { let pathSegments = urlComponents.AbsolutePath.split("/"); pathSegments = pathSegments.filter((val) => val && val.length > 0); // remove empty elements urlComponents.PathSegments = pathSegments; + + if (!StringUtils.isEmpty(urlComponents.QueryString) && urlComponents.QueryString.endsWith("/")) { + urlComponents.QueryString = urlComponents.QueryString.substring(0, urlComponents.QueryString.length-1); + } return urlComponents; } diff --git a/lib/msal-common/test/account/ClientInfo.spec.ts b/lib/msal-common/test/account/ClientInfo.spec.ts index 827cd98111..c4227eb76e 100644 --- a/lib/msal-common/test/account/ClientInfo.spec.ts +++ b/lib/msal-common/test/account/ClientInfo.spec.ts @@ -44,6 +44,9 @@ describe("ClientInfo.ts Class Unit Tests", () => { }, async getPublicKeyThumbprint(): Promise { return TEST_POP_VALUES.KID; + }, + async signJwt(): Promise { + return ""; } }; }); diff --git a/lib/msal-common/test/account/IdToken.spec.ts b/lib/msal-common/test/account/IdToken.spec.ts index 08537f698d..3be53535f9 100644 --- a/lib/msal-common/test/account/IdToken.spec.ts +++ b/lib/msal-common/test/account/IdToken.spec.ts @@ -1,10 +1,10 @@ import { expect } from "chai"; -import { IdToken } from "../../src/account/IdToken"; +import { AuthToken } from "../../src/account/AuthToken"; import { TEST_CONFIG, TEST_DATA_CLIENT_INFO, RANDOM_TEST_GUID, TEST_TOKENS, TEST_URIS, TEST_POP_VALUES } from "../utils/StringConstants"; import { PkceCodes, ICrypto } from "../../src/crypto/ICrypto"; import sinon from "sinon"; import { ClientAuthErrorMessage, ClientAuthError, StringUtils } from "../../src"; -import { DecodedJwt } from "../../src/account/DecodedJwt"; +import { DecodedAuthToken } from "../../src/account/DecodedAuthToken"; // Set up stubs const idTokenClaims = { @@ -61,6 +61,9 @@ describe("IdToken.ts Class Unit Tests", () => { }, async getPublicKeyThumbprint(): Promise { return TEST_POP_VALUES.KID; + }, + async signJwt(): Promise { + return ""; } }; }); @@ -72,18 +75,18 @@ describe("IdToken.ts Class Unit Tests", () => { describe("Constructor", () => { it("Throws error if rawIdToken is null or empty", () => { - expect(() => new IdToken("", cryptoInterface)).to.throw(ClientAuthErrorMessage.nullOrEmptyIdToken.desc); - expect(() => new IdToken("", cryptoInterface)).to.throw(ClientAuthError); + expect(() => new AuthToken("", cryptoInterface)).to.throw(ClientAuthErrorMessage.nullOrEmptyToken.desc); + expect(() => new AuthToken("", cryptoInterface)).to.throw(ClientAuthError); - expect(() => new IdToken(null, cryptoInterface)).to.throw(ClientAuthErrorMessage.nullOrEmptyIdToken.desc); - expect(() => new IdToken(null, cryptoInterface)).to.throw(ClientAuthError); + expect(() => new AuthToken(null, cryptoInterface)).to.throw(ClientAuthErrorMessage.nullOrEmptyToken.desc); + expect(() => new AuthToken(null, cryptoInterface)).to.throw(ClientAuthError); }); it("Successfully sets the rawidToken and claims fields", () => { - sinon.stub(IdToken, "extractIdToken").returns(idTokenClaims); + sinon.stub(AuthToken, "extractTokenClaims").returns(idTokenClaims); - const idToken = new IdToken(TEST_TOKENS.IDTOKEN_V2, cryptoInterface); - expect(idToken.rawIdToken).to.be.eq(TEST_TOKENS.IDTOKEN_V2); + const idToken = new AuthToken(TEST_TOKENS.IDTOKEN_V2, cryptoInterface); + expect(idToken.rawToken).to.be.eq(TEST_TOKENS.IDTOKEN_V2); expect(idToken.claims).to.be.deep.eq(idTokenClaims); }); }); @@ -91,32 +94,32 @@ describe("IdToken.ts Class Unit Tests", () => { describe("extractIdToken()", () => { it("Throws error if idToken is null or empty", () => { - expect(() => IdToken.extractIdToken("", cryptoInterface)).to.throw(ClientAuthErrorMessage.nullOrEmptyIdToken.desc); - expect(() => IdToken.extractIdToken("", cryptoInterface)).to.throw(ClientAuthError); + expect(() => AuthToken.extractTokenClaims("", cryptoInterface)).to.throw(ClientAuthErrorMessage.nullOrEmptyToken.desc); + expect(() => AuthToken.extractTokenClaims("", cryptoInterface)).to.throw(ClientAuthError); - expect(() => IdToken.extractIdToken(null, cryptoInterface)).to.throw(ClientAuthErrorMessage.nullOrEmptyIdToken.desc); - expect(() => IdToken.extractIdToken(null, cryptoInterface)).to.throw(ClientAuthError); + expect(() => AuthToken.extractTokenClaims(null, cryptoInterface)).to.throw(ClientAuthErrorMessage.nullOrEmptyToken.desc); + expect(() => AuthToken.extractTokenClaims(null, cryptoInterface)).to.throw(ClientAuthError); }); it("returns null if decodeJwt returns null", () => { - sinon.stub(StringUtils, "decodeJwt").returns(null); - expect(IdToken.extractIdToken(TEST_TOKENS.IDTOKEN_V2, cryptoInterface)).to.be.null; + sinon.stub(StringUtils, "decodeAuthToken").returns(null); + expect(AuthToken.extractTokenClaims(TEST_TOKENS.IDTOKEN_V2, cryptoInterface)).to.be.null; }); it("Throws error if payload cannot be parsed", () => { - const decodedJwt: DecodedJwt = { + const decodedJwt: DecodedAuthToken = { header: "jwt header", JWSPayload: "jws payload", JWSSig: "signature" }; - sinon.stub(StringUtils, "decodeJwt").returns(decodedJwt); + sinon.stub(StringUtils, "decodeAuthToken").returns(decodedJwt); - expect(() => IdToken.extractIdToken(TEST_TOKENS.IDTOKEN_V2, cryptoInterface)).to.throw(ClientAuthErrorMessage.idTokenParsingError.desc); - expect(() => IdToken.extractIdToken(TEST_TOKENS.IDTOKEN_V2, cryptoInterface)).to.throw(ClientAuthError); + expect(() => AuthToken.extractTokenClaims(TEST_TOKENS.IDTOKEN_V2, cryptoInterface)).to.throw(ClientAuthErrorMessage.tokenParsingError.desc); + expect(() => AuthToken.extractTokenClaims(TEST_TOKENS.IDTOKEN_V2, cryptoInterface)).to.throw(ClientAuthError); }); it("Successfully extracts the idTokenClaims from the decodedJwt", () => { - const decodedJwt: DecodedJwt = { + const decodedJwt: DecodedAuthToken = { header: JSON.stringify({ "typ": "JWT", "alg": "RS256", @@ -125,8 +128,8 @@ describe("IdToken.ts Class Unit Tests", () => { JWSPayload: testTokenPayload, JWSSig: "signature" }; - sinon.stub(StringUtils, "decodeJwt").returns(decodedJwt); - expect(IdToken.extractIdToken(decodedJwt.JWSPayload, cryptoInterface)).to.be.deep.eq(idTokenClaims); + sinon.stub(StringUtils, "decodeAuthToken").returns(decodedJwt); + expect(AuthToken.extractTokenClaims(decodedJwt.JWSPayload, cryptoInterface)).to.be.deep.eq(idTokenClaims); }); }); }); diff --git a/lib/msal-common/test/cache/entities/AccountEntity.spec.ts b/lib/msal-common/test/cache/entities/AccountEntity.spec.ts index c31b5f0fa7..24391bd0b8 100644 --- a/lib/msal-common/test/cache/entities/AccountEntity.spec.ts +++ b/lib/msal-common/test/cache/entities/AccountEntity.spec.ts @@ -1,7 +1,7 @@ import { expect } from "chai"; import { AccountEntity } from "../../../src/cache/entities/AccountEntity"; import { mockAccountEntity } from "./cacheConstants"; -import { IdToken } from "../../../src/account/IdToken"; +import { AuthToken } from "../../../src/account/AuthToken"; import { AuthorityFactory } from "../../../src/authority/AuthorityFactory"; import { Constants } from "../../../src/utils/Constants"; import { NetworkRequestOptions, INetworkModule } from "../../../src/network/INetworkModule"; @@ -80,6 +80,9 @@ describe("AccountEntity.ts Unit Tests", () => { }, async getPublicKeyThumbprint(): Promise { return TEST_POP_VALUES.KID; + }, + async signJwt(): Promise { + return ""; } }; @@ -114,8 +117,8 @@ describe("AccountEntity.ts Unit Tests", () => { "tid": "3338040d-6c67-4c5b-b112-36a304b66dad", "nonce": "123523", }; - sinon.stub(IdToken, "extractIdToken").returns(idTokenClaims); - const idToken = new IdToken(TEST_TOKENS.IDTOKEN_V2, cryptoInterface); + sinon.stub(AuthToken, "extractTokenClaims").returns(idTokenClaims); + const idToken = new AuthToken(TEST_TOKENS.IDTOKEN_V2, cryptoInterface); const acc = AccountEntity.createAccount( TEST_DATA_CLIENT_INFO.TEST_CACHE_RAW_CLIENT_INFO, diff --git a/lib/msal-common/test/client/AuthorizationCodeClient.spec.ts b/lib/msal-common/test/client/AuthorizationCodeClient.spec.ts index 609f825db6..751c997004 100644 --- a/lib/msal-common/test/client/AuthorizationCodeClient.spec.ts +++ b/lib/msal-common/test/client/AuthorizationCodeClient.spec.ts @@ -345,7 +345,7 @@ describe("AuthorizationCodeClient unit tests", () => { "tid": "3338040d-6c67-4c5b-b112-36a304b66dad", "nonce": "123523", }; - sinon.stub(IdToken, "extractIdToken").returns(idTokenClaims); + sinon.stub(IdToken, "extractTokenClaims").returns(idTokenClaims); const client = new AuthorizationCodeClient(config); const authCodeRequest: AuthorizationCodeRequest = { authority: Constants.DEFAULT_AUTHORITY, @@ -418,7 +418,7 @@ describe("AuthorizationCodeClient unit tests", () => { "tid": "3338040d-6c67-4c5b-b112-36a304b66dad", "nonce": "123523", }; - sinon.stub(IdToken, "extractIdToken").returns(idTokenClaims); + sinon.stub(IdToken, "extractTokenClaims").returns(idTokenClaims); const client = new AuthorizationCodeClient(config); const authCodeRequest: AuthorizationCodeRequest = { authenticationScheme: AuthenticationScheme.POP, diff --git a/lib/msal-common/test/client/BaseClient.spec.ts b/lib/msal-common/test/client/BaseClient.spec.ts index de5a770270..5d6736c27b 100644 --- a/lib/msal-common/test/client/BaseClient.spec.ts +++ b/lib/msal-common/test/client/BaseClient.spec.ts @@ -6,7 +6,7 @@ import { ClientTestUtils } from "./ClientTestUtils"; import { ClientConfiguration } from "../../src/config/ClientConfiguration"; import sinon from "sinon"; import { DEFAULT_OPENID_CONFIG_RESPONSE, TEST_CONFIG } from "../utils/StringConstants"; -import { IdToken, ServerTelemetryRequest } from "../../dist/src"; +import { ServerTelemetryRequest } from "../../dist/src"; class TestClient extends BaseClient { diff --git a/lib/msal-common/test/client/ClientTestUtils.ts b/lib/msal-common/test/client/ClientTestUtils.ts index 1983a6e178..68d06865ac 100644 --- a/lib/msal-common/test/client/ClientTestUtils.ts +++ b/lib/msal-common/test/client/ClientTestUtils.ts @@ -104,6 +104,9 @@ export class ClientTestUtils { }, async getPublicKeyThumbprint(): Promise { return TEST_POP_VALUES.KID; + }, + async signJwt(): Promise { + return ""; } }, loggerOptions: { diff --git a/lib/msal-common/test/client/DeviceCodeClient.spec.ts b/lib/msal-common/test/client/DeviceCodeClient.spec.ts index cdb717632b..7510709e07 100644 --- a/lib/msal-common/test/client/DeviceCodeClient.spec.ts +++ b/lib/msal-common/test/client/DeviceCodeClient.spec.ts @@ -6,8 +6,8 @@ import { Constants, DeviceCodeClient, DeviceCodeRequest, - IdToken, ClientConfiguration, + AuthToken, } from "../../src"; import { AUTHENTICATION_RESULT, AUTHORIZATION_PENDING_RESPONSE, @@ -77,7 +77,7 @@ describe("DeviceCodeClient unit tests", async () => { tid: "3338040d-6c67-4c5b-b112-36a304b66dad", nonce: "123523", }; - sinon.stub(IdToken, "extractIdToken").returns(idTokenClaims); + sinon.stub(AuthToken, "extractTokenClaims").returns(idTokenClaims); }); afterEach(() => { diff --git a/lib/msal-common/test/client/RefreshTokenClient.spec.ts b/lib/msal-common/test/client/RefreshTokenClient.spec.ts index 06cad2ff92..ef4a4517f2 100644 --- a/lib/msal-common/test/client/RefreshTokenClient.spec.ts +++ b/lib/msal-common/test/client/RefreshTokenClient.spec.ts @@ -13,10 +13,9 @@ import {AADServerParamKeys, GrantType, Constants} from "../../src/utils/Constant import {ClientTestUtils} from "./ClientTestUtils"; import { Authority } from "../../src/authority/Authority"; import { RefreshTokenClient } from "../../src/client/RefreshTokenClient"; -import { IdTokenClaims } from "../../src/account/IdTokenClaims"; -import { IdToken } from "../../src/account/IdToken"; import { RefreshTokenRequest } from "../../src/request/RefreshTokenRequest"; -import { AccountInfo, AuthenticationResult } from "../../src"; +import { AccountInfo, AuthenticationResult, AuthToken } from "../../src"; +import { TokenClaims } from "../../dist/src/account/TokenClaims"; describe("RefreshTokenClient unit tests", () => { beforeEach(() => { @@ -40,7 +39,7 @@ describe("RefreshTokenClient unit tests", () => { }); it("acquires a token", async () => { - const idTokenClaims: IdTokenClaims = { + const idTokenClaims: TokenClaims = { "ver": "2.0", "iss": "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0", "sub": "AAAAAAAAAAAAAAAAAAAAAIkzqFVrSaSaFHy782bbtaQ", @@ -54,7 +53,7 @@ describe("RefreshTokenClient unit tests", () => { sinon.stub(Authority.prototype, "discoverEndpoints").resolves(DEFAULT_OPENID_CONFIG_RESPONSE); AUTHENTICATION_RESULT.body.client_info = TEST_DATA_CLIENT_INFO.TEST_DECODED_CLIENT_INFO; sinon.stub(RefreshTokenClient.prototype, "executePostToTokenEndpoint").resolves(AUTHENTICATION_RESULT); - sinon.stub(IdToken, "extractIdToken").returns(idTokenClaims); + sinon.stub(AuthToken, "extractTokenClaims").returns(idTokenClaims); const createTokenRequestBodySpy = sinon.spy(RefreshTokenClient.prototype, "createTokenRequestBody"); diff --git a/lib/msal-common/test/client/SilentFlowClient.spec.ts b/lib/msal-common/test/client/SilentFlowClient.spec.ts index 7137ed7927..67dbf5d6fc 100644 --- a/lib/msal-common/test/client/SilentFlowClient.spec.ts +++ b/lib/msal-common/test/client/SilentFlowClient.spec.ts @@ -14,12 +14,11 @@ import { AADServerParamKeys, GrantType, Constants, CredentialType } from "../../ import { ClientTestUtils, MockStorageClass } from "./ClientTestUtils"; import { Authority } from "../../src/authority/Authority"; import { SilentFlowClient } from "../../src/client/SilentFlowClient"; -import { IdTokenClaims } from "../../src/account/IdTokenClaims"; import { RefreshTokenClient } from "../../src/client/RefreshTokenClient"; -import { IdToken } from "../../src/account/IdToken"; import { AuthenticationResult } from "../../src/response/AuthenticationResult"; import { AccountInfo } from "../../src/account/AccountInfo"; -import { SilentFlowRequest, AccountEntity, IdTokenEntity, AccessTokenEntity, RefreshTokenEntity, CacheManager, ClientConfigurationErrorMessage, ClientAuthErrorMessage } from "../../src"; +import { SilentFlowRequest, AccountEntity, IdTokenEntity, AccessTokenEntity, RefreshTokenEntity, CacheManager, ClientConfigurationErrorMessage, ClientAuthErrorMessage, AuthToken } from "../../src"; +import { TokenClaims } from "../../dist/src/account/TokenClaims"; describe("SilentFlowClient unit tests", () => { beforeEach(() => { @@ -100,7 +99,7 @@ describe("SilentFlowClient unit tests", () => { }); it("acquires a token", async () => { - const idTokenClaims: IdTokenClaims = { + const idTokenClaims: TokenClaims = { "ver": "2.0", "iss": "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0", "sub": "AAAAAAAAAAAAAAAAAAAAAIkzqFVrSaSaFHy782bbtaQ", @@ -147,7 +146,7 @@ describe("SilentFlowClient unit tests", () => { sinon.stub(Authority.prototype, "discoverEndpoints").resolves(DEFAULT_OPENID_CONFIG_RESPONSE); AUTHENTICATION_RESULT.body.client_info = TEST_DATA_CLIENT_INFO.TEST_DECODED_CLIENT_INFO; sinon.stub(RefreshTokenClient.prototype, "executePostToTokenEndpoint").resolves(AUTHENTICATION_RESULT); - sinon.stub(IdToken, "extractIdToken").returns(idTokenClaims); + sinon.stub(AuthToken, "extractTokenClaims").returns(idTokenClaims); sinon.stub(CacheManager.prototype, "getAccount").returns(testAccountEntity); const createTokenRequestBodySpy = sinon.spy(RefreshTokenClient.prototype, "createTokenRequestBody"); diff --git a/lib/msal-common/test/config/ClientConfiguration.spec.ts b/lib/msal-common/test/config/ClientConfiguration.spec.ts index 5c10ba42bd..a21b375e48 100644 --- a/lib/msal-common/test/config/ClientConfiguration.spec.ts +++ b/lib/msal-common/test/config/ClientConfiguration.spec.ts @@ -99,6 +99,9 @@ describe("ClientConfiguration.ts Class Unit Tests", () => { }, async getPublicKeyThumbprint(): Promise { return TEST_POP_VALUES.KID; + }, + async signJwt(): Promise { + return "signedJwt"; } }, storageInterface: cacheStorageMock, diff --git a/lib/msal-common/test/crypto/PopTokenGenerator.spec.ts b/lib/msal-common/test/crypto/PopTokenGenerator.spec.ts index eee12d824c..a6362cbdf0 100644 --- a/lib/msal-common/test/crypto/PopTokenGenerator.spec.ts +++ b/lib/msal-common/test/crypto/PopTokenGenerator.spec.ts @@ -5,7 +5,7 @@ import chaiAsPromised from "chai-as-promised"; const expect = chai.expect; chai.use(chaiAsPromised); import { ICrypto, PkceCodes } from "../../src"; -import { RANDOM_TEST_GUID, TEST_POP_VALUES, TEST_DATA_CLIENT_INFO, TEST_CONFIG } from "../utils/StringConstants"; +import { RANDOM_TEST_GUID, TEST_POP_VALUES, TEST_DATA_CLIENT_INFO, TEST_CONFIG, TEST_URIS } from "../utils/StringConstants"; import { PopTokenGenerator } from "../../src/crypto/PopTokenGenerator"; describe("PopTokenGenerator Unit Tests", () => { @@ -44,12 +44,15 @@ describe("PopTokenGenerator Unit Tests", () => { }, async getPublicKeyThumbprint(): Promise { return TEST_POP_VALUES.KID; + }, + async signJwt(): Promise { + return ""; } }; it("Generates the req_cnf correctly", async () => { const popTokenGenerator = new PopTokenGenerator(cryptoInterface); - const req_cnf = await popTokenGenerator.generateCnf(); + const req_cnf = await popTokenGenerator.generateCnf("POST", TEST_URIS.TEST_REDIR_URI); expect(req_cnf).to.be.eq(TEST_POP_VALUES.ENCODED_REQ_CNF); }) }); diff --git a/lib/msal-common/test/error/ClientAuthError.spec.ts b/lib/msal-common/test/error/ClientAuthError.spec.ts index 96360ff605..2bde9b348a 100644 --- a/lib/msal-common/test/error/ClientAuthError.spec.ts +++ b/lib/msal-common/test/error/ClientAuthError.spec.ts @@ -46,27 +46,27 @@ describe("ClientAuthError.ts Class Unit Tests", () => { }); it("createIdTokenParsingError creates a ClientAuthError object", () => { - const err: ClientAuthError = ClientAuthError.createIdTokenParsingError("Raw client info."); + const err: ClientAuthError = ClientAuthError.createTokenParsingError("Raw client info."); expect(err instanceof ClientAuthError).to.be.true; expect(err instanceof AuthError).to.be.true; expect(err instanceof Error).to.be.true; - expect(err.errorCode).to.equal(ClientAuthErrorMessage.idTokenParsingError.code); - expect(err.errorMessage).to.include(ClientAuthErrorMessage.idTokenParsingError.desc); - expect(err.message).to.include(ClientAuthErrorMessage.idTokenParsingError.desc); + expect(err.errorCode).to.equal(ClientAuthErrorMessage.tokenParsingError.code); + expect(err.errorMessage).to.include(ClientAuthErrorMessage.tokenParsingError.desc); + expect(err.message).to.include(ClientAuthErrorMessage.tokenParsingError.desc); expect(err.name).to.equal("ClientAuthError"); expect(err.stack).to.include("ClientAuthError.spec.ts"); }); it("createIdTokenNullOrEmptyError creates a ClientAuthError object", () => { - const err: ClientAuthError = ClientAuthError.createIdTokenNullOrEmptyError("Invalid Raw IdToken string."); + const err: ClientAuthError = ClientAuthError.createTokenNullOrEmptyError("Invalid Raw IdToken string."); expect(err instanceof ClientAuthError).to.be.true; expect(err instanceof AuthError).to.be.true; expect(err instanceof Error).to.be.true; - expect(err.errorCode).to.equal(ClientAuthErrorMessage.nullOrEmptyIdToken.code); - expect(err.errorMessage).to.include(ClientAuthErrorMessage.nullOrEmptyIdToken.desc); - expect(err.message).to.include(ClientAuthErrorMessage.nullOrEmptyIdToken.desc); + expect(err.errorCode).to.equal(ClientAuthErrorMessage.nullOrEmptyToken.code); + expect(err.errorMessage).to.include(ClientAuthErrorMessage.nullOrEmptyToken.desc); + expect(err.message).to.include(ClientAuthErrorMessage.nullOrEmptyToken.desc); expect(err.name).to.equal("ClientAuthError"); expect(err.stack).to.include("ClientAuthError.spec.ts"); }); @@ -149,19 +149,6 @@ describe("ClientAuthError.ts Class Unit Tests", () => { expect(err.stack).to.include("ClientAuthError.spec.ts"); }); - it("createInvalidIdTokenError creates a ClientAuthError object", () => { - const err: ClientAuthError = ClientAuthError.createInvalidIdTokenError(null); - - expect(err instanceof ClientAuthError).to.be.true; - expect(err instanceof AuthError).to.be.true; - expect(err instanceof Error).to.be.true; - expect(err.errorCode).to.equal(ClientAuthErrorMessage.invalidIdToken.code); - expect(err.errorMessage).to.include(ClientAuthErrorMessage.invalidIdToken.desc); - expect(err.message).to.include(ClientAuthErrorMessage.invalidIdToken.desc); - expect(err.name).to.equal("ClientAuthError"); - expect(err.stack).to.include("ClientAuthError.spec.ts"); - }); - it("createNoTokensFoundError creates a ClientAuthError object", () => { const err: ClientAuthError = ClientAuthError.createNoTokensFoundError(); diff --git a/lib/msal-common/test/response/ResponseHandler.spec.ts b/lib/msal-common/test/response/ResponseHandler.spec.ts index 3fa1cb8b7b..66b5b0ebc9 100644 --- a/lib/msal-common/test/response/ResponseHandler.spec.ts +++ b/lib/msal-common/test/response/ResponseHandler.spec.ts @@ -8,12 +8,10 @@ import { Authority } from "../../src/authority/Authority"; import { INetworkModule, NetworkRequestOptions } from "../../src/network/INetworkModule"; import { CacheManager } from "../../src/cache/CacheManager"; import { ICrypto, PkceCodes } from "../../src/crypto/ICrypto"; -import { IdToken } from "../../src/account/IdToken"; -import { IdTokenClaims } from "../../src/account/IdTokenClaims"; import { ClientTestUtils } from "../client/ClientTestUtils"; -import { AccountEntity, TrustedAuthority, ClientAuthError, ClientAuthErrorMessage, InteractionRequiredAuthError, ServerError } from "../../src"; +import { AccountEntity, TrustedAuthority, ClientAuthError, ClientAuthErrorMessage, InteractionRequiredAuthError, ServerError, AuthToken, AuthenticationResult, AuthError } from "../../src"; import { ServerAuthorizationCodeResponse } from "../../src/response/ServerAuthorizationCodeResponse"; -import { buildClientInfo } from "../../src/account/ClientInfo"; +import { TokenClaims } from "../../dist/src/account/TokenClaims"; const networkInterface: INetworkModule = { sendGetRequestAsync(url: string, options?: NetworkRequestOptions): T { @@ -56,6 +54,9 @@ const cryptoInterface: ICrypto = { }, async getPublicKeyThumbprint(): Promise { return TEST_POP_VALUES.KID; + }, + async signJwt(): Promise { + return ""; } } @@ -92,8 +93,8 @@ let authority = new Authority("https://login.microsoftonline.com/common", networ describe("ResponseHandler.ts", () => { beforeEach(() => { - sinon.stub(IdToken, "extractIdToken").callsFake((encodedIdToken, crypto) => { - return ID_TOKEN_CLAIMS as IdTokenClaims; + sinon.stub(AuthToken, "extractTokenClaims").callsFake((encodedIdToken, crypto) => { + return ID_TOKEN_CLAIMS as TokenClaims; }); sinon.stub(ResponseHandler.prototype, "generateAccountEntity").returns(new AccountEntity()); sinon.stub(AccountEntity.prototype, "getAccountInfo").returns({ @@ -110,10 +111,10 @@ describe("ResponseHandler.ts", () => { }) describe("generateCacheRecord", () => { - it("throws invalid cache environment error", (done) => { + it("throws invalid cache environment error", async () => { sinon.restore(); - sinon.stub(IdToken, "extractIdToken").callsFake((encodedIdToken, crypto) => { - return ID_TOKEN_CLAIMS as IdTokenClaims; + sinon.stub(AuthToken, "extractTokenClaims").callsFake((encodedIdToken, crypto) => { + return ID_TOKEN_CLAIMS as TokenClaims; }); sinon.stub(ResponseHandler.prototype, "generateAccountEntity").returns(new AccountEntity()); sinon.stub(AccountEntity.prototype, "getAccountInfo").returns({ @@ -126,14 +127,17 @@ describe("ResponseHandler.ts", () => { const testResponse: ServerAuthorizationTokenResponse = {...AUTHENTICATION_RESULT.body}; const responseHandler = new ResponseHandler("this-is-a-client-id", testCacheManager, cryptoInterface, null); - try { - responseHandler.handleServerTokenResponse(testResponse, authority); + const tokenResp = await responseHandler.handleServerTokenResponse(testResponse, authority); + expect(tokenResp).to.be.undefined; } catch(e) { - expect(e).to.be.instanceOf(ClientAuthError); - expect(e.errorCode).to.be.eq(ClientAuthErrorMessage.invalidCacheEnvironment.code); - expect(e.errorMessage).to.be.eq(ClientAuthErrorMessage.invalidCacheEnvironment.desc); - done(); + if (e instanceof AuthError) { + expect(e).to.be.instanceOf(ClientAuthError); + expect(e.errorCode).to.be.eq(ClientAuthErrorMessage.invalidCacheEnvironment.code); + expect(e.errorMessage).to.be.eq(ClientAuthErrorMessage.invalidCacheEnvironment.desc); + } else { + throw e; + } } }); @@ -143,7 +147,7 @@ describe("ResponseHandler.ts", () => { const responseHandler = new ResponseHandler("this-is-a-client-id", testCacheManager, cryptoInterface, null); - sinon.stub(ResponseHandler, "generateAuthenticationResult").callsFake((cacheRecord, idTokenObj, fromTokenCache, stateString) => { + sinon.stub(ResponseHandler, "generateAuthenticationResult").callsFake((cryptoObj, cacheRecord, idTokenObj, fromTokenCache, stateString, resourceReqMethod, resourceReqUri) => { expect(cacheRecord.idToken).to.not.be.null; expect(cacheRecord.accessToken).to.be.null; expect(cacheRecord.refreshToken).to.not.be.null; @@ -160,7 +164,7 @@ describe("ResponseHandler.ts", () => { const responseHandler = new ResponseHandler("this-is-a-client-id", testCacheManager, cryptoInterface, null); - sinon.stub(ResponseHandler, "generateAuthenticationResult").callsFake((cacheRecord, idTokenObj, fromTokenCache, stateString) => { + sinon.stub(ResponseHandler, "generateAuthenticationResult").callsFake((cryptoObj, cacheRecord, idTokenObj, fromTokenCache, stateString, resourceReqMethod, resourceReqUri) => { expect(cacheRecord.idToken).to.not.be.null; expect(cacheRecord.accessToken).to.not.be.null; expect(cacheRecord.refreshToken).to.be.null; @@ -176,7 +180,7 @@ describe("ResponseHandler.ts", () => { const responseHandler = new ResponseHandler("this-is-a-client-id", testCacheManager, cryptoInterface, null); - sinon.stub(ResponseHandler, "generateAuthenticationResult").callsFake((cacheRecord, idTokenObj, fromTokenCache, stateString) => { + sinon.stub(ResponseHandler, "generateAuthenticationResult").callsFake((cryptoObj, cacheRecord, idTokenObj, fromTokenCache, stateString, resourceReqMethod, resourceReqUri) => { expect(cacheRecord.idToken).to.not.be.null; expect(cacheRecord.accessToken).to.not.be.null; expect(cacheRecord.refreshToken).to.not.be.null; @@ -189,12 +193,12 @@ describe("ResponseHandler.ts", () => { }); describe("generateAuthenticationResult", () => { - it("sets default values if access_token not in cacheRecord", () => { + it("sets default values if access_token not in cacheRecord", async () => { const testResponse: ServerAuthorizationTokenResponse = {...AUTHENTICATION_RESULT.body}; testResponse.access_token = null; const responseHandler = new ResponseHandler("this-is-a-client-id", testCacheManager, cryptoInterface, null); - const result = responseHandler.handleServerTokenResponse(testResponse, authority); + const result = await responseHandler.handleServerTokenResponse(testResponse, authority); expect(result.accessToken).to.be.eq(""); expect(result.scopes).to.be.length(0); @@ -202,12 +206,12 @@ describe("ResponseHandler.ts", () => { expect(result.extExpiresOn).to.be.null; }); - it("sets default values if refresh_token not in cacheRecord", () => { + it("sets default values if refresh_token not in cacheRecord", async () => { const testResponse: ServerAuthorizationTokenResponse = {...AUTHENTICATION_RESULT.body}; testResponse.refresh_token = null; const responseHandler = new ResponseHandler("this-is-a-client-id", testCacheManager, cryptoInterface, null); - const result = responseHandler.handleServerTokenResponse(testResponse, authority); + const result = await responseHandler.handleServerTokenResponse(testResponse, authority); expect(result.familyId).to.be.null; }); diff --git a/lib/msal-common/test/url/UrlString.spec.ts b/lib/msal-common/test/url/UrlString.spec.ts index 8b9a44a350..49920cdc7c 100644 --- a/lib/msal-common/test/url/UrlString.spec.ts +++ b/lib/msal-common/test/url/UrlString.spec.ts @@ -4,7 +4,6 @@ import { UrlString } from "../../src/url/UrlString"; import { ClientConfigurationError, ClientConfigurationErrorMessage } from "../../src/error/ClientConfigurationError"; import { IUri } from "../../src/url/IUri"; import sinon from "sinon"; -import { IdToken } from "../../src/account/IdToken"; describe("UrlString.ts Class Unit Tests", () => { @@ -126,12 +125,13 @@ describe("UrlString.ts Class Unit Tests", () => { }); it("getUrlComponents returns all path components", () => { - const urlObj = new UrlString(TEST_URIS.TEST_AUTH_ENDPT_WITH_PARAMS1); + const urlObj = new UrlString(TEST_URIS.TEST_AUTH_ENDPT_WITH_PARAMS2); expect(urlObj.getUrlComponents()).to.be.deep.eq({ Protocol: "https:", HostNameAndPort: "login.microsoftonline.com", AbsolutePath: "/common/oauth2/v2.0/authorize", - PathSegments: ["common", "oauth2", "v2.0", "authorize"] + PathSegments: ["common", "oauth2", "v2.0", "authorize"], + QueryString: "param1=value1¶m2=value2" } as IUri); }); diff --git a/lib/msal-common/test/utils/ProtocolUtils.spec.ts b/lib/msal-common/test/utils/ProtocolUtils.spec.ts index 82d2f9c110..2793ae7263 100644 --- a/lib/msal-common/test/utils/ProtocolUtils.spec.ts +++ b/lib/msal-common/test/utils/ProtocolUtils.spec.ts @@ -49,6 +49,9 @@ describe("ProtocolUtils.ts Class Unit Tests", () => { }, async getPublicKeyThumbprint(): Promise { return TEST_POP_VALUES.KID; + }, + async signJwt(): Promise { + return ""; } }; }); diff --git a/lib/msal-common/test/utils/StringUtils.spec.ts b/lib/msal-common/test/utils/StringUtils.spec.ts index ea6232ceab..46e2fe3372 100644 --- a/lib/msal-common/test/utils/StringUtils.spec.ts +++ b/lib/msal-common/test/utils/StringUtils.spec.ts @@ -9,7 +9,7 @@ describe("StringUtils.ts Class Unit Tests", () => { it("decodeJwt returns a correctly crackedToken.", () => { const sampleJwt = `${TEST_TOKENS.SAMPLE_JWT_HEADER}.${TEST_TOKENS.SAMPLE_JWT_PAYLOAD}.${TEST_TOKENS.SAMPLE_JWT_SIG}`; - const decodedJwt = StringUtils.decodeJwt(sampleJwt); + const decodedJwt = StringUtils.decodeAuthToken(sampleJwt); expect(decodedJwt).to.be.deep.eq({ header: TEST_TOKENS.SAMPLE_JWT_HEADER, @@ -22,7 +22,7 @@ describe("StringUtils.ts Class Unit Tests", () => { let err: ClientAuthError; try { - let decodedJwt = StringUtils.decodeJwt(null); + let decodedJwt = StringUtils.decodeAuthToken(null); } catch (e) { err = e; } @@ -30,9 +30,9 @@ describe("StringUtils.ts Class Unit Tests", () => { expect(err instanceof ClientAuthError).to.be.true; expect(err instanceof AuthError).to.be.true; expect(err instanceof Error).to.be.true; - expect(err.errorCode).to.equal(ClientAuthErrorMessage.nullOrEmptyIdToken.code); - expect(err.errorMessage).to.include(ClientAuthErrorMessage.nullOrEmptyIdToken.desc); - expect(err.message).to.include(ClientAuthErrorMessage.nullOrEmptyIdToken.desc); + expect(err.errorCode).to.equal(ClientAuthErrorMessage.nullOrEmptyToken.code); + expect(err.errorMessage).to.include(ClientAuthErrorMessage.nullOrEmptyToken.desc); + expect(err.message).to.include(ClientAuthErrorMessage.nullOrEmptyToken.desc); expect(err.name).to.equal("ClientAuthError"); expect(err.stack).to.include("StringUtils.spec.ts"); }); @@ -41,7 +41,7 @@ describe("StringUtils.ts Class Unit Tests", () => { let err: ClientAuthError; try { - let decodedJwt = StringUtils.decodeJwt(""); + let decodedJwt = StringUtils.decodeAuthToken(""); } catch (e) { err = e; } @@ -49,9 +49,9 @@ describe("StringUtils.ts Class Unit Tests", () => { expect(err instanceof ClientAuthError).to.be.true; expect(err instanceof AuthError).to.be.true; expect(err instanceof Error).to.be.true; - expect(err.errorCode).to.equal(ClientAuthErrorMessage.nullOrEmptyIdToken.code); - expect(err.errorMessage).to.include(ClientAuthErrorMessage.nullOrEmptyIdToken.desc); - expect(err.message).to.include(ClientAuthErrorMessage.nullOrEmptyIdToken.desc); + expect(err.errorCode).to.equal(ClientAuthErrorMessage.nullOrEmptyToken.code); + expect(err.errorMessage).to.include(ClientAuthErrorMessage.nullOrEmptyToken.desc); + expect(err.message).to.include(ClientAuthErrorMessage.nullOrEmptyToken.desc); expect(err.name).to.equal("ClientAuthError"); expect(err.stack).to.include("StringUtils.spec.ts"); }); @@ -60,7 +60,7 @@ describe("StringUtils.ts Class Unit Tests", () => { let err: ClientAuthError; try { - let decodedJwt = StringUtils.decodeJwt(TEST_TOKENS.SAMPLE_MALFORMED_JWT); + let decodedJwt = StringUtils.decodeAuthToken(TEST_TOKENS.SAMPLE_MALFORMED_JWT); } catch (e) { err = e; } @@ -68,9 +68,9 @@ describe("StringUtils.ts Class Unit Tests", () => { expect(err instanceof ClientAuthError).to.be.true; expect(err instanceof AuthError).to.be.true; expect(err instanceof Error).to.be.true; - expect(err.errorCode).to.equal(ClientAuthErrorMessage.idTokenParsingError.code); - expect(err.errorMessage).to.include(ClientAuthErrorMessage.idTokenParsingError.desc); - expect(err.message).to.include(ClientAuthErrorMessage.idTokenParsingError.desc); + expect(err.errorCode).to.equal(ClientAuthErrorMessage.tokenParsingError.code); + expect(err.errorMessage).to.include(ClientAuthErrorMessage.tokenParsingError.desc); + expect(err.message).to.include(ClientAuthErrorMessage.tokenParsingError.desc); expect(err.name).to.equal("ClientAuthError"); expect(err.stack).to.include("StringUtils.spec.ts"); }); From 21add177d7607d8bdf82bf3b138a51cb7906e826 Mon Sep 17 00:00:00 2001 From: Prithvi Kanherkar Date: Thu, 27 Aug 2020 15:58:00 -0700 Subject: [PATCH 12/17] update browser tests --- .../src/app/PublicClientApplication.ts | 6 +- .../test/app/PublicClientApplication.spec.ts | 63 +++++++++++-------- .../InteractionHandler.spec.ts | 8 ++- .../interaction_handler/PopupHandler.spec.ts | 3 + .../RedirectHandler.spec.ts | 8 ++- .../interaction_handler/SilentHandler.spec.ts | 3 + 6 files changed, 57 insertions(+), 34 deletions(-) diff --git a/lib/msal-browser/src/app/PublicClientApplication.ts b/lib/msal-browser/src/app/PublicClientApplication.ts index 7791b2d126..a45a7fe677 100644 --- a/lib/msal-browser/src/app/PublicClientApplication.ts +++ b/lib/msal-browser/src/app/PublicClientApplication.ts @@ -15,7 +15,6 @@ import { TrustedAuthority, AuthorizationUrlRequest, PersistentCacheKeys, - JwtToken, ProtocolUtils, AuthorizationCodeRequest, Constants, @@ -31,7 +30,8 @@ import { Logger, ServerTelemetryManager, ServerTelemetryRequest, - ServerAuthorizationCodeResponse + ServerAuthorizationCodeResponse, + AuthToken } from "@azure/msal-common"; import { buildConfiguration, Configuration } from "../config/Configuration"; import { BrowserStorage } from "../cache/BrowserStorage"; @@ -681,7 +681,7 @@ export class PublicClientApplication implements IPublicClientApplication { // Only check for adal token if no SSO params are being used const adalIdTokenString = this.browserStorage.getItem(PersistentCacheKeys.ADAL_ID_TOKEN, CacheSchemaType.TEMPORARY) as string; if (!StringUtils.isEmpty(adalIdTokenString)) { - const adalIdToken = new JwtToken(adalIdTokenString, this.browserCrypto); + const adalIdToken = new AuthToken(adalIdTokenString, this.browserCrypto); this.browserStorage.removeItem(PersistentCacheKeys.ADAL_ID_TOKEN); if (adalIdToken.claims && adalIdToken.claims.upn) { validatedRequest.loginHint = adalIdToken.claims.upn; diff --git a/lib/msal-browser/test/app/PublicClientApplication.spec.ts b/lib/msal-browser/test/app/PublicClientApplication.spec.ts index 5bddf06e6f..d501e8c421 100644 --- a/lib/msal-browser/test/app/PublicClientApplication.spec.ts +++ b/lib/msal-browser/test/app/PublicClientApplication.spec.ts @@ -6,7 +6,7 @@ const expect = chai.expect; import sinon from "sinon"; import { PublicClientApplication } from "../../src/app/PublicClientApplication"; import { TEST_CONFIG, TEST_URIS, TEST_HASHES, TEST_TOKENS, TEST_DATA_CLIENT_INFO, TEST_TOKEN_LIFETIMES, RANDOM_TEST_GUID, DEFAULT_OPENID_CONFIG_RESPONSE, testNavUrl, testLogoutUrl, TEST_STATE_VALUES, testNavUrlNoRequest } from "../utils/StringConstants"; -import { ServerError, Constants, AccountInfo, IdTokenClaims, PromptValue, AuthenticationResult, AuthorizationCodeRequest, AuthorizationUrlRequest, IdToken, PersistentCacheKeys, SilentFlowRequest, CacheSchemaType, TimeUtils, AuthorizationCodeClient, ResponseMode, SilentFlowClient, TrustedAuthority, EndSessionRequest, CloudDiscoveryMetadata, AccountEntity, ProtocolUtils, ServerTelemetryCacheValue } from "@azure/msal-common"; +import { ServerError, Constants, AccountInfo, TokenClaims, PromptValue, AuthenticationResult, AuthorizationCodeRequest, AuthorizationUrlRequest, AuthToken, PersistentCacheKeys, SilentFlowRequest, CacheSchemaType, TimeUtils, AuthorizationCodeClient, ResponseMode, SilentFlowClient, TrustedAuthority, EndSessionRequest, CloudDiscoveryMetadata, AccountEntity, ProtocolUtils, ServerTelemetryCacheValue, AuthenticationScheme } from "@azure/msal-common"; import { BrowserUtils } from "../../src/utils/BrowserUtils"; import { BrowserConstants, TemporaryCacheKeys, ApiId } from "../../src/utils/BrowserConstants"; import { Base64Encode } from "../../src/encode/Base64Encode"; @@ -203,7 +203,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { client_info: TEST_DATA_CLIENT_INFO.TEST_RAW_CLIENT_INFO } }; - const testIdTokenClaims: IdTokenClaims = { + const testIdTokenClaims: TokenClaims = { "ver": "2.0", "iss": "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0", "sub": "AAAAAAAAAAAAAAAAAAAAAIkzqFVrSaSaFHy782bbtaQ", @@ -228,7 +228,8 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { accessToken: testServerTokenResponse.body.access_token, fromCache: false, expiresOn: new Date(Date.now() + (testServerTokenResponse.body.expires_in * 1000)), - account: testAccount + account: testAccount, + tokenType: AuthenticationScheme.BEARER }; sinon.stub(XhrClient.prototype, "sendGetRequestAsync").resolves(DEFAULT_OPENID_CONFIG_RESPONSE); sinon.stub(XhrClient.prototype, "sendPostRequestAsync").resolves(testServerTokenResponse); @@ -307,7 +308,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { } }; - const testIdTokenClaims: IdTokenClaims = { + const testIdTokenClaims: TokenClaims = { "ver": "2.0", "iss": "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0", "sub": "AAAAAAAAAAAAAAAAAAAAAIkzqFVrSaSaFHy782bbtaQ", @@ -334,7 +335,8 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { accessToken: testServerTokenResponse.body.access_token, fromCache: false, expiresOn: new Date(Date.now() + (testServerTokenResponse.body.expires_in * 1000)), - account: testAccount + account: testAccount, + tokenType: AuthenticationScheme.BEARER }; sinon.stub(XhrClient.prototype, "sendGetRequestAsync").resolves(DEFAULT_OPENID_CONFIG_RESPONSE); @@ -391,7 +393,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { } }; - const testIdTokenClaims: IdTokenClaims = { + const testIdTokenClaims: TokenClaims = { "ver": "2.0", "iss": "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0", "sub": "AAAAAAAAAAAAAAAAAAAAAIkzqFVrSaSaFHy782bbtaQ", @@ -418,7 +420,8 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { accessToken: testServerTokenResponse.body.access_token, fromCache: false, expiresOn: new Date(Date.now() + (testServerTokenResponse.body.expires_in * 1000)), - account: testAccount + account: testAccount, + tokenType: AuthenticationScheme.BEARER }; sinon.stub(XhrClient.prototype, "sendGetRequestAsync").resolves(DEFAULT_OPENID_CONFIG_RESPONSE); @@ -587,7 +590,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { }); it("Uses adal token from cache if it is present.", async () => { - const idTokenClaims: IdTokenClaims = { + const idTokenClaims: TokenClaims = { "iss": "https://sts.windows.net/fa15d692-e9c7-4460-a743-29f2956fd429/", "exp": 1536279024, "name": "abeli", @@ -598,7 +601,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { "ver": "1.0", "upn": "AbeLincoln@contoso.com" }; - sinon.stub(IdToken, "extractIdToken").returns(idTokenClaims); + sinon.stub(AuthToken, "extractTokenClaims").returns(idTokenClaims); const browserStorage: BrowserStorage = new BrowserStorage(TEST_CONFIG.MSAL_CLIENT_ID, cacheConfig); browserStorage.setItem(PersistentCacheKeys.ADAL_ID_TOKEN, TEST_TOKENS.IDTOKEN_V1, CacheSchemaType.TEMPORARY); const loginUrlSpy = sinon.spy(AuthorizationCodeClient.prototype, "getAuthCodeUrl"); @@ -634,7 +637,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { }); it("Does not use adal token from cache if it is present and SSO params have been given.", async () => { - const idTokenClaims: IdTokenClaims = { + const idTokenClaims: TokenClaims = { "iss": "https://sts.windows.net/fa15d692-e9c7-4460-a743-29f2956fd429/", "exp": 1536279024, "name": "abeli", @@ -645,7 +648,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { "ver": "1.0", "upn": "AbeLincoln@contoso.com" }; - sinon.stub(IdToken, "extractIdToken").returns(idTokenClaims); + sinon.stub(AuthToken, "extractTokenClaims").returns(idTokenClaims); const browserStorage: BrowserStorage = new BrowserStorage(TEST_CONFIG.MSAL_CLIENT_ID, cacheConfig); browserStorage.setItem(PersistentCacheKeys.ADAL_ID_TOKEN, TEST_TOKENS.IDTOKEN_V1, CacheSchemaType.TEMPORARY); const loginUrlSpy = sinon.spy(AuthorizationCodeClient.prototype, "getAuthCodeUrl"); @@ -793,7 +796,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { it("Uses adal token from cache if it is present.", async () => { const testScope = "testscope"; - const idTokenClaims: IdTokenClaims = { + const idTokenClaims: TokenClaims = { "iss": "https://sts.windows.net/fa15d692-e9c7-4460-a743-29f2956fd429/", "exp": 1536279024, "name": "abeli", @@ -804,7 +807,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { "ver": "1.0", "upn": "AbeLincoln@contoso.com" }; - sinon.stub(IdToken, "extractIdToken").returns(idTokenClaims); + sinon.stub(AuthToken, "extractTokenClaims").returns(idTokenClaims); const browserStorage: BrowserStorage = new BrowserStorage(TEST_CONFIG.MSAL_CLIENT_ID, cacheConfig); browserStorage.setItem(PersistentCacheKeys.ADAL_ID_TOKEN, TEST_TOKENS.IDTOKEN_V1, CacheSchemaType.TEMPORARY); const acquireTokenUrlSpy = sinon.spy(AuthorizationCodeClient.prototype, "getAuthCodeUrl"); @@ -840,7 +843,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { }); it("Does not use adal token from cache if it is present and SSO params have been given.", async () => { - const idTokenClaims: IdTokenClaims = { + const idTokenClaims: TokenClaims = { "iss": "https://sts.windows.net/fa15d692-e9c7-4460-a743-29f2956fd429/", "exp": 1536279024, "name": "abeli", @@ -851,7 +854,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { "ver": "1.0", "upn": "AbeLincoln@contoso.com" }; - sinon.stub(IdToken, "extractIdToken").returns(idTokenClaims); + sinon.stub(AuthToken, "extractTokenClaims").returns(idTokenClaims); const browserStorage: BrowserStorage = new BrowserStorage(TEST_CONFIG.MSAL_CLIENT_ID, cacheConfig); browserStorage.setItem(PersistentCacheKeys.ADAL_ID_TOKEN, TEST_TOKENS.IDTOKEN_V1, CacheSchemaType.TEMPORARY); const acquireTokenUrlSpy = sinon.spy(AuthorizationCodeClient.prototype, "getAuthCodeUrl"); @@ -920,7 +923,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { refresh_token: TEST_TOKENS.REFRESH_TOKEN, id_token: TEST_TOKENS.IDTOKEN_V2 }; - const testIdTokenClaims: IdTokenClaims = { + const testIdTokenClaims: TokenClaims = { "ver": "2.0", "iss": "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0", "sub": "AAAAAAAAAAAAAAAAAAAAAIkzqFVrSaSaFHy782bbtaQ", @@ -945,7 +948,8 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { accessToken: testServerTokenResponse.access_token, fromCache: false, expiresOn: new Date(Date.now() + (testServerTokenResponse.expires_in * 1000)), - account: testAccount + account: testAccount, + tokenType: AuthenticationScheme.BEARER }; sinon.stub(AuthorizationCodeClient.prototype, "getAuthCodeUrl").resolves(testNavUrl); sinon.stub(PopupHandler.prototype, "initiateAuthRequest").callsFake((requestUrl: string): Window => { @@ -1014,7 +1018,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { refresh_token: TEST_TOKENS.REFRESH_TOKEN, id_token: TEST_TOKENS.IDTOKEN_V2 }; - const testIdTokenClaims: IdTokenClaims = { + const testIdTokenClaims: TokenClaims = { "ver": "2.0", "iss": "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0", "sub": "AAAAAAAAAAAAAAAAAAAAAIkzqFVrSaSaFHy782bbtaQ", @@ -1039,7 +1043,8 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { accessToken: testServerTokenResponse.access_token, fromCache: false, expiresOn: new Date(Date.now() + (testServerTokenResponse.expires_in * 1000)), - account: testAccount + account: testAccount, + tokenType: AuthenticationScheme.BEARER }; sinon.stub(AuthorizationCodeClient.prototype, "getAuthCodeUrl").resolves(testNavUrl); sinon.stub(PopupHandler.prototype, "initiateAuthRequest").callsFake((requestUrl: string): Window => { @@ -1126,7 +1131,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { refresh_token: TEST_TOKENS.REFRESH_TOKEN, id_token: TEST_TOKENS.IDTOKEN_V2 }; - const testIdTokenClaims: IdTokenClaims = { + const testIdTokenClaims: TokenClaims = { "ver": "2.0", "iss": "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0", "sub": "AAAAAAAAAAAAAAAAAAAAAIkzqFVrSaSaFHy782bbtaQ", @@ -1151,7 +1156,8 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { accessToken: testServerTokenResponse.access_token, fromCache: false, expiresOn: new Date(Date.now() + (testServerTokenResponse.expires_in * 1000)), - account: testAccount + account: testAccount, + tokenType: AuthenticationScheme.BEARER }; sinon.stub(AuthorizationCodeClient.prototype, "getAuthCodeUrl").resolves(testNavUrl); const loadFrameSyncSpy = sinon.spy(SilentHandler.prototype, "loadFrameSync"); @@ -1181,7 +1187,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { refresh_token: TEST_TOKENS.REFRESH_TOKEN, id_token: TEST_TOKENS.IDTOKEN_V2 }; - const testIdTokenClaims: IdTokenClaims = { + const testIdTokenClaims: TokenClaims = { "ver": "2.0", "iss": "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0", "sub": "AAAAAAAAAAAAAAAAAAAAAIkzqFVrSaSaFHy782bbtaQ", @@ -1206,7 +1212,8 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { accessToken: testServerTokenResponse.access_token, fromCache: false, expiresOn: new Date(Date.now() + (testServerTokenResponse.expires_in * 1000)), - account: testAccount + account: testAccount, + tokenType: AuthenticationScheme.BEARER }; sinon.stub(AuthorizationCodeClient.prototype, "getAuthCodeUrl").resolves(testNavUrl); const loadFrameSyncSpy = sinon.spy(SilentHandler.prototype, "loadFrameSync"); @@ -1239,7 +1246,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { refresh_token: TEST_TOKENS.REFRESH_TOKEN, id_token: TEST_TOKENS.IDTOKEN_V2 }; - const testIdTokenClaims: IdTokenClaims = { + const testIdTokenClaims: TokenClaims = { "ver": "2.0", "iss": "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0", "sub": "AAAAAAAAAAAAAAAAAAAAAIkzqFVrSaSaFHy782bbtaQ", @@ -1264,7 +1271,8 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { accessToken: testServerTokenResponse.access_token, fromCache: false, expiresOn: new Date(Date.now() + (testServerTokenResponse.expires_in * 1000)), - account: testAccount + account: testAccount, + tokenType: AuthenticationScheme.BEARER }; sinon.stub(CryptoOps.prototype, "createNewGuid").returns(RANDOM_TEST_GUID); const silentATStub = sinon.stub(SilentFlowClient.prototype, "acquireToken").resolves(testTokenResponse); @@ -1326,7 +1334,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { refresh_token: TEST_TOKENS.REFRESH_TOKEN, id_token: TEST_TOKENS.IDTOKEN_V2 }; - const testIdTokenClaims: IdTokenClaims = { + const testIdTokenClaims: TokenClaims = { "ver": "2.0", "iss": "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0", "sub": "AAAAAAAAAAAAAAAAAAAAAIkzqFVrSaSaFHy782bbtaQ", @@ -1351,7 +1359,8 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { accessToken: testServerTokenResponse.access_token, fromCache: false, expiresOn: new Date(Date.now() + (testServerTokenResponse.expires_in * 1000)), - account: testAccount + account: testAccount, + tokenType: AuthenticationScheme.BEARER }; const createAcqTokenStub = sinon.stub(AuthorizationCodeClient.prototype, "getAuthCodeUrl").resolves(testNavUrl); const silentTokenHelperStub = sinon.stub(pca, "silentTokenHelper").resolves(testTokenResponse); diff --git a/lib/msal-browser/test/interaction_handler/InteractionHandler.spec.ts b/lib/msal-browser/test/interaction_handler/InteractionHandler.spec.ts index dca2986b4f..d07097d7be 100644 --- a/lib/msal-browser/test/interaction_handler/InteractionHandler.spec.ts +++ b/lib/msal-browser/test/interaction_handler/InteractionHandler.spec.ts @@ -1,6 +1,6 @@ import { expect } from "chai"; import { InteractionHandler } from "../../src/interaction_handler/InteractionHandler"; -import { PkceCodes, NetworkRequestOptions, LogLevel, AccountInfo, AuthorityFactory, AuthorizationCodeRequest, AuthenticationResult, CacheManager, AuthorizationCodeClient } from "@azure/msal-common"; +import { PkceCodes, NetworkRequestOptions, LogLevel, AccountInfo, AuthorityFactory, AuthorizationCodeRequest, AuthenticationResult, CacheManager, AuthorizationCodeClient, AuthenticationScheme } from "@azure/msal-common"; import { Configuration, buildConfiguration } from "../../src/config/Configuration"; import { TEST_CONFIG, TEST_URIS, TEST_DATA_CLIENT_INFO, TEST_TOKENS, TEST_TOKEN_LIFETIMES, TEST_HASHES, TEST_POP_VALUES } from "../utils/StringConstants"; import { BrowserStorage } from "../../src/cache/BrowserStorage"; @@ -111,6 +111,9 @@ describe("InteractionHandler.ts Unit Tests", () => { }, getPublicKeyThumbprint: async (): Promise => { return TEST_POP_VALUES.ENCODED_REQ_CNF; + }, + signJwt: async (): Promise => { + return "signedJwt"; } }, storageInterface: new TestStorageInterface(), @@ -187,7 +190,8 @@ describe("InteractionHandler.ts Unit Tests", () => { idTokenClaims: idTokenClaims, tenantId: idTokenClaims.tid, uniqueId: idTokenClaims.oid, - state: "testState" + state: "testState", + tokenType: AuthenticationScheme.BEARER }; sinon.stub(AuthorizationCodeClient.prototype, "handleFragmentResponse").returns(testCodeResponse); const acquireTokenSpy = sinon.stub(AuthorizationCodeClient.prototype, "acquireToken").resolves(testTokenResponse); diff --git a/lib/msal-browser/test/interaction_handler/PopupHandler.spec.ts b/lib/msal-browser/test/interaction_handler/PopupHandler.spec.ts index dabefa7a4c..8f347808be 100644 --- a/lib/msal-browser/test/interaction_handler/PopupHandler.spec.ts +++ b/lib/msal-browser/test/interaction_handler/PopupHandler.spec.ts @@ -95,6 +95,9 @@ describe("PopupHandler.ts Unit Tests", () => { }, getPublicKeyThumbprint: async (): Promise => { return TEST_POP_VALUES.ENCODED_REQ_CNF; + }, + signJwt: async (): Promise => { + return "signedJwt"; } }, storageInterface: new TestStorageInterface(), diff --git a/lib/msal-browser/test/interaction_handler/RedirectHandler.spec.ts b/lib/msal-browser/test/interaction_handler/RedirectHandler.spec.ts index 8e2a87288d..032853f256 100644 --- a/lib/msal-browser/test/interaction_handler/RedirectHandler.spec.ts +++ b/lib/msal-browser/test/interaction_handler/RedirectHandler.spec.ts @@ -4,7 +4,7 @@ chai.use(chaiAsPromised); const expect = chai.expect; import sinon from "sinon"; import { Configuration, buildConfiguration } from "../../src/config/Configuration"; -import { PkceCodes, NetworkRequestOptions, LogLevel, AccountInfo, AuthorityFactory, AuthorizationCodeRequest, Constants, AuthenticationResult, CacheSchemaType, CacheManager, AuthorizationCodeClient } from "@azure/msal-common"; +import { PkceCodes, NetworkRequestOptions, LogLevel, AccountInfo, AuthorityFactory, AuthorizationCodeRequest, Constants, AuthenticationResult, CacheSchemaType, CacheManager, AuthorizationCodeClient, AuthenticationScheme } from "@azure/msal-common"; import { TEST_CONFIG, TEST_URIS, TEST_TOKENS, TEST_DATA_CLIENT_INFO, RANDOM_TEST_GUID, TEST_HASHES, TEST_TOKEN_LIFETIMES, TEST_POP_VALUES } from "../utils/StringConstants"; import { BrowserStorage } from "../../src/cache/BrowserStorage"; import { RedirectHandler } from "../../src/interaction_handler/RedirectHandler"; @@ -75,6 +75,9 @@ describe("RedirectHandler.ts Unit Tests", () => { }, getPublicKeyThumbprint: async (): Promise => { return TEST_POP_VALUES.ENCODED_REQ_CNF; + }, + signJwt: async (): Promise => { + return "signedJwt"; } }, storageInterface: browserStorage, @@ -211,7 +214,8 @@ describe("RedirectHandler.ts Unit Tests", () => { expiresOn: new Date(Date.now() + (TEST_TOKEN_LIFETIMES.DEFAULT_EXPIRES_IN * 1000)), idTokenClaims: idTokenClaims, tenantId: idTokenClaims.tid, - uniqueId: idTokenClaims.oid + uniqueId: idTokenClaims.oid, + tokenType: AuthenticationScheme.BEARER }; const browserCrypto = new CryptoOps(); const testAuthCodeRequest: AuthorizationCodeRequest = { diff --git a/lib/msal-browser/test/interaction_handler/SilentHandler.spec.ts b/lib/msal-browser/test/interaction_handler/SilentHandler.spec.ts index 4f6494fbc8..d07dd3c6cb 100644 --- a/lib/msal-browser/test/interaction_handler/SilentHandler.spec.ts +++ b/lib/msal-browser/test/interaction_handler/SilentHandler.spec.ts @@ -96,6 +96,9 @@ describe("SilentHandler.ts Unit Tests", () => { }, getPublicKeyThumbprint: async (): Promise => { return TEST_POP_VALUES.ENCODED_REQ_CNF; + }, + signJwt: async (): Promise => { + return "signedJwt"; } }, storageInterface: new TestStorageInterface(), From ef47cd813015a57a410eca0edac378d4ce1a2fb8 Mon Sep 17 00:00:00 2001 From: Prithvi Kanherkar Date: Thu, 27 Aug 2020 17:27:20 -0700 Subject: [PATCH 13/17] All tests fixed and passing --- .../{IdToken.spec.ts => AuthToken.spec.ts} | 2 +- .../client/AuthorizationCodeClient.spec.ts | 32 +++++++++++-- .../test/client/BaseClient.spec.ts | 3 +- .../test/client/SilentFlowClient.spec.ts | 3 +- .../test/crypto/PopTokenGenerator.spec.ts | 37 +++++++++++++-- .../test/response/ResponseHandler.spec.ts | 46 +++++++++++++++++-- lib/msal-common/test/utils/StringConstants.ts | 10 ++-- 7 files changed, 113 insertions(+), 20 deletions(-) rename lib/msal-common/test/account/{IdToken.spec.ts => AuthToken.spec.ts} (99%) diff --git a/lib/msal-common/test/account/IdToken.spec.ts b/lib/msal-common/test/account/AuthToken.spec.ts similarity index 99% rename from lib/msal-common/test/account/IdToken.spec.ts rename to lib/msal-common/test/account/AuthToken.spec.ts index 3be53535f9..081151cc5d 100644 --- a/lib/msal-common/test/account/IdToken.spec.ts +++ b/lib/msal-common/test/account/AuthToken.spec.ts @@ -21,7 +21,7 @@ const idTokenClaims = { const testTokenPayload = "eyJ2ZXIiOiIyLjAiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vOTE4ODA0MGQtNmM2Ny00YzViLWIxMTItMzZhMzA0YjY2ZGFkL3YyLjAiLCJzdWIiOiJBQUFBQUFBQUFBQUFBQUFBQUFBQUFJa3pxRlZyU2FTYUZIeTc4MmJidGFRIiwiYXVkIjoiNmNiMDQwMTgtYTNmNS00NmE3LWI5OTUtOTQwYzc4ZjVhZWYzIiwiZXhwIjoxNTM2MzYxNDExLCJpYXQiOjE1MzYyNzQ3MTEsIm5iZiI6MTUzNjI3NDcxMSwibmFtZSI6IkFiZSBMaW5jb2xuIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiQWJlTGlAbWljcm9zb2Z0LmNvbSIsIm9pZCI6IjAwMDAwMDAwLTAwMDAtMDAwMC02NmYzLTMzMzJlY2E3ZWE4MSIsInRpZCI6IjMzMzgwNDBkLTZjNjctNGM1Yi1iMTEyLTM2YTMwNGI2NmRhZCIsIm5vbmNlIjoiMTIzNTIzIiwiYWlvIjoiRGYyVVZYTDFpeCFsTUNXTVNPSkJjRmF0emNHZnZGR2hqS3Y4cTVnMHg3MzJkUjVNQjVCaXN2R1FPN1lXQnlqZDhpUURMcSFlR2JJRGFreXA1bW5PcmNkcUhlWVNubHRlcFFtUnA2QUlaOGpZIn0="; -describe("IdToken.ts Class Unit Tests", () => { +describe("AuthToken.ts Class Unit Tests", () => { let cryptoInterface: ICrypto; beforeEach(() => { diff --git a/lib/msal-common/test/client/AuthorizationCodeClient.spec.ts b/lib/msal-common/test/client/AuthorizationCodeClient.spec.ts index 751c997004..7fcddab0b9 100644 --- a/lib/msal-common/test/client/AuthorizationCodeClient.spec.ts +++ b/lib/msal-common/test/client/AuthorizationCodeClient.spec.ts @@ -16,7 +16,11 @@ import { IdToken, CacheManager, AccountInfo, - AccountEntity + AccountEntity, + AuthToken, + ICrypto, + TokenClaims, + SignedHttpRequest } from "../../src"; import { ALTERNATE_OPENID_CONFIG_RESPONSE, @@ -406,6 +410,11 @@ describe("AuthorizationCodeClient unit tests", () => { return input; } }; + const signedJwt = "signedJwt"; + config.cryptoInterface.signJwt = async (payload: SignedHttpRequest, kid: string): Promise => { + expect(payload.at).to.be.eq(POP_AUTHENTICATION_RESULT.body.access_token); + return signedJwt; + }; // Set up stubs const idTokenClaims = { "ver": "2.0", @@ -418,7 +427,20 @@ describe("AuthorizationCodeClient unit tests", () => { "tid": "3338040d-6c67-4c5b-b112-36a304b66dad", "nonce": "123523", }; - sinon.stub(IdToken, "extractTokenClaims").returns(idTokenClaims); + sinon.stub(AuthToken, "extractTokenClaims").callsFake((encodedToken: string, crypto: ICrypto): TokenClaims => { + switch (encodedToken) { + case POP_AUTHENTICATION_RESULT.body.id_token: + return idTokenClaims as TokenClaims; + case POP_AUTHENTICATION_RESULT.body.access_token: + return { + cnf: { + kid: TEST_POP_VALUES.KID + } + }; + default: + return null; + }; + }); const client = new AuthorizationCodeClient(config); const authCodeRequest: AuthorizationCodeRequest = { authenticationScheme: AuthenticationScheme.POP, @@ -426,12 +448,14 @@ describe("AuthorizationCodeClient unit tests", () => { scopes: [...TEST_CONFIG.DEFAULT_GRAPH_SCOPE, ...TEST_CONFIG.DEFAULT_SCOPES], redirectUri: TEST_URIS.TEST_REDIRECT_URI_LOCALHOST, code: TEST_TOKENS.AUTHORIZATION_CODE, - codeVerifier: TEST_CONFIG.TEST_VERIFIER + codeVerifier: TEST_CONFIG.TEST_VERIFIER, + resourceRequestMethod: "POST", + resourceRequestUri: TEST_URIS.TEST_RESOURCE_ENDPT_WITH_PARAMS }; const authenticationResult = await client.acquireToken(authCodeRequest, idTokenClaims.nonce, testState); - expect(authenticationResult.accessToken).to.deep.eq(POP_AUTHENTICATION_RESULT.body.access_token); + expect(authenticationResult.accessToken).to.eq(signedJwt); expect((Date.now() + (POP_AUTHENTICATION_RESULT.body.expires_in * 1000)) >= authenticationResult.expiresOn.getMilliseconds()).to.be.true; expect(createTokenRequestBodySpy.calledWith(authCodeRequest)).to.be.ok; await expect(createTokenRequestBodySpy.returnValues[0]).to.eventually.contain(`${AADServerParamKeys.SCOPE}=${TEST_CONFIG.DEFAULT_GRAPH_SCOPE}%20${Constants.OPENID_SCOPE}%20${Constants.PROFILE_SCOPE}%20${Constants.OFFLINE_ACCESS_SCOPE}`); diff --git a/lib/msal-common/test/client/BaseClient.spec.ts b/lib/msal-common/test/client/BaseClient.spec.ts index 5d6736c27b..c6c0b02a85 100644 --- a/lib/msal-common/test/client/BaseClient.spec.ts +++ b/lib/msal-common/test/client/BaseClient.spec.ts @@ -1,12 +1,11 @@ import { expect } from "chai"; import { BaseClient } from "../../src/client/BaseClient"; -import { Authority, Constants, ServerTelemetryManager } from "../../src"; +import { Authority, Constants, ServerTelemetryManager, ServerTelemetryRequest } from "../../src"; import { AADServerParamKeys, HeaderNames } from "../../src/utils/Constants"; import { ClientTestUtils } from "./ClientTestUtils"; import { ClientConfiguration } from "../../src/config/ClientConfiguration"; import sinon from "sinon"; import { DEFAULT_OPENID_CONFIG_RESPONSE, TEST_CONFIG } from "../utils/StringConstants"; -import { ServerTelemetryRequest } from "../../dist/src"; class TestClient extends BaseClient { diff --git a/lib/msal-common/test/client/SilentFlowClient.spec.ts b/lib/msal-common/test/client/SilentFlowClient.spec.ts index 67dbf5d6fc..49f4263765 100644 --- a/lib/msal-common/test/client/SilentFlowClient.spec.ts +++ b/lib/msal-common/test/client/SilentFlowClient.spec.ts @@ -17,8 +17,7 @@ import { SilentFlowClient } from "../../src/client/SilentFlowClient"; import { RefreshTokenClient } from "../../src/client/RefreshTokenClient"; import { AuthenticationResult } from "../../src/response/AuthenticationResult"; import { AccountInfo } from "../../src/account/AccountInfo"; -import { SilentFlowRequest, AccountEntity, IdTokenEntity, AccessTokenEntity, RefreshTokenEntity, CacheManager, ClientConfigurationErrorMessage, ClientAuthErrorMessage, AuthToken } from "../../src"; -import { TokenClaims } from "../../dist/src/account/TokenClaims"; +import { SilentFlowRequest, AccountEntity, IdTokenEntity, AccessTokenEntity, RefreshTokenEntity, CacheManager, ClientConfigurationErrorMessage, ClientAuthErrorMessage, AuthToken, TokenClaims } from "../../src"; describe("SilentFlowClient unit tests", () => { beforeEach(() => { diff --git a/lib/msal-common/test/crypto/PopTokenGenerator.spec.ts b/lib/msal-common/test/crypto/PopTokenGenerator.spec.ts index a6362cbdf0..8ec8e2ace3 100644 --- a/lib/msal-common/test/crypto/PopTokenGenerator.spec.ts +++ b/lib/msal-common/test/crypto/PopTokenGenerator.spec.ts @@ -4,8 +4,8 @@ import sinon from "sinon"; import chaiAsPromised from "chai-as-promised"; const expect = chai.expect; chai.use(chaiAsPromised); -import { ICrypto, PkceCodes } from "../../src"; -import { RANDOM_TEST_GUID, TEST_POP_VALUES, TEST_DATA_CLIENT_INFO, TEST_CONFIG, TEST_URIS } from "../utils/StringConstants"; +import { ICrypto, PkceCodes, UrlString, SignedHttpRequest, TimeUtils } from "../../src"; +import { RANDOM_TEST_GUID, TEST_POP_VALUES, TEST_DATA_CLIENT_INFO, TEST_CONFIG, TEST_URIS, TEST_TOKENS } from "../utils/StringConstants"; import { PopTokenGenerator } from "../../src/crypto/PopTokenGenerator"; describe("PopTokenGenerator Unit Tests", () => { @@ -20,6 +20,8 @@ describe("PopTokenGenerator Unit Tests", () => { return TEST_POP_VALUES.DECODED_REQ_CNF; case TEST_DATA_CLIENT_INFO.TEST_RAW_CLIENT_INFO: return TEST_DATA_CLIENT_INFO.TEST_DECODED_CLIENT_INFO; + case TEST_POP_VALUES.SAMPLE_POP_AT_PAYLOAD_ENCODED: + return TEST_POP_VALUES.SAMPLE_POP_AT_PAYLOAD_DECODED; default: return input; } @@ -32,6 +34,8 @@ describe("PopTokenGenerator Unit Tests", () => { return "NDU2LXRlc3QtdWlk"; case TEST_POP_VALUES.DECODED_REQ_CNF: return TEST_POP_VALUES.ENCODED_REQ_CNF; + case TEST_POP_VALUES.SAMPLE_POP_AT_PAYLOAD_DECODED: + return TEST_POP_VALUES.SAMPLE_POP_AT_PAYLOAD_ENCODED; default: return input; } @@ -54,5 +58,32 @@ describe("PopTokenGenerator Unit Tests", () => { const popTokenGenerator = new PopTokenGenerator(cryptoInterface); const req_cnf = await popTokenGenerator.generateCnf("POST", TEST_URIS.TEST_REDIR_URI); expect(req_cnf).to.be.eq(TEST_POP_VALUES.ENCODED_REQ_CNF); - }) + }); + + it("Signs the proof-of-possession JWT token", (done) => { + const popTokenGenerator = new PopTokenGenerator(cryptoInterface); + const accessToken = TEST_POP_VALUES.SAMPLE_POP_AT; + const resourceReqMethod = "POST"; + const resourceUrl = TEST_URIS.TEST_RESOURCE_ENDPT_WITH_PARAMS; + const resourceUrlString = new UrlString(resourceUrl); + const resourceUrlComponents = resourceUrlString.getUrlComponents(); + const currTime = TimeUtils.nowSeconds(); + sinon.stub(TimeUtils, "nowSeconds").returns(currTime); + cryptoInterface.signJwt = (payload: SignedHttpRequest, kid: string): Promise => { + expect(kid).to.be.eq(TEST_POP_VALUES.KID); + const expectedPayload = { + at: accessToken, + ts: `${currTime}`, + m: resourceReqMethod, + u: resourceUrlComponents.HostNameAndPort, + nonce: RANDOM_TEST_GUID, + p: resourceUrlComponents.AbsolutePath, + q: [[], resourceUrlComponents.QueryString] + }; + expect(payload).to.be.deep.eq(expectedPayload); + done(); + return null; + }; + popTokenGenerator.signPopToken(accessToken, resourceReqMethod, resourceUrl); + }); }); diff --git a/lib/msal-common/test/response/ResponseHandler.spec.ts b/lib/msal-common/test/response/ResponseHandler.spec.ts index 66b5b0ebc9..c2b8621375 100644 --- a/lib/msal-common/test/response/ResponseHandler.spec.ts +++ b/lib/msal-common/test/response/ResponseHandler.spec.ts @@ -3,15 +3,14 @@ import { expect } from "chai"; import sinon from "sinon"; import { ServerAuthorizationTokenResponse } from "../../src/response/ServerAuthorizationTokenResponse"; import { ResponseHandler } from "../../src/response/ResponseHandler"; -import { AUTHENTICATION_RESULT, RANDOM_TEST_GUID, TEST_CONFIG, ID_TOKEN_CLAIMS, TEST_DATA_CLIENT_INFO, TEST_STATE_VALUES, TEST_POP_VALUES } from "../utils/StringConstants"; +import { AUTHENTICATION_RESULT, RANDOM_TEST_GUID, TEST_CONFIG, ID_TOKEN_CLAIMS, TEST_DATA_CLIENT_INFO, TEST_STATE_VALUES, TEST_POP_VALUES, POP_AUTHENTICATION_RESULT, TEST_URIS } from "../utils/StringConstants"; import { Authority } from "../../src/authority/Authority"; import { INetworkModule, NetworkRequestOptions } from "../../src/network/INetworkModule"; import { CacheManager } from "../../src/cache/CacheManager"; import { ICrypto, PkceCodes } from "../../src/crypto/ICrypto"; import { ClientTestUtils } from "../client/ClientTestUtils"; -import { AccountEntity, TrustedAuthority, ClientAuthError, ClientAuthErrorMessage, InteractionRequiredAuthError, ServerError, AuthToken, AuthenticationResult, AuthError } from "../../src"; +import { AccountEntity, TrustedAuthority, ClientAuthError, ClientAuthErrorMessage, InteractionRequiredAuthError, ServerError, AuthToken, AuthenticationResult, AuthError, TokenClaims, AuthenticationScheme } from "../../src"; import { ServerAuthorizationCodeResponse } from "../../src/response/ServerAuthorizationCodeResponse"; -import { TokenClaims } from "../../dist/src/account/TokenClaims"; const networkInterface: INetworkModule = { sendGetRequestAsync(url: string, options?: NetworkRequestOptions): T { @@ -21,7 +20,7 @@ const networkInterface: INetworkModule = { return null; } }; - +const signedJwt = "SignedJwt"; const cryptoInterface: ICrypto = { createNewGuid(): string { return RANDOM_TEST_GUID; @@ -32,6 +31,8 @@ const cryptoInterface: ICrypto = { TEST_POP_VALUES.DECODED_REQ_CNF; case TEST_DATA_CLIENT_INFO.TEST_RAW_CLIENT_INFO: return TEST_DATA_CLIENT_INFO.TEST_DECODED_CLIENT_INFO; + case TEST_POP_VALUES.SAMPLE_POP_AT_PAYLOAD_ENCODED: + return TEST_POP_VALUES.SAMPLE_POP_AT_PAYLOAD_DECODED; default: return input; } @@ -42,6 +43,8 @@ const cryptoInterface: ICrypto = { TEST_POP_VALUES.ENCODED_REQ_CNF; case TEST_DATA_CLIENT_INFO.TEST_DECODED_CLIENT_INFO: return TEST_DATA_CLIENT_INFO.TEST_RAW_CLIENT_INFO; + case TEST_POP_VALUES.SAMPLE_POP_AT_PAYLOAD_DECODED: + return TEST_POP_VALUES.SAMPLE_POP_AT_PAYLOAD_ENCODED; default: return input; } @@ -56,7 +59,7 @@ const cryptoInterface: ICrypto = { return TEST_POP_VALUES.KID; }, async signJwt(): Promise { - return ""; + return signedJwt; } } @@ -215,6 +218,39 @@ describe("ResponseHandler.ts", () => { expect(result.familyId).to.be.null; }); + + it("sets default values for access token using PoP scheme", async () => { + const testResponse: ServerAuthorizationTokenResponse = { ...POP_AUTHENTICATION_RESULT.body }; + sinon.restore(); + sinon.stub(AuthToken, "extractTokenClaims").callsFake((encodedToken: string, crypto: ICrypto): TokenClaims => { + switch (encodedToken) { + case testResponse.id_token: + return ID_TOKEN_CLAIMS as TokenClaims; + case testResponse.access_token: + return { + cnf: { + kid: TEST_POP_VALUES.KID + } + }; + default: + return null; + }; + }); + sinon.stub(ResponseHandler.prototype, "generateAccountEntity").returns(new AccountEntity()); + sinon.stub(AccountEntity.prototype, "getAccountInfo").returns({ + homeAccountId: TEST_DATA_CLIENT_INFO.TEST_HOME_ACCOUNT_ID, + environment: "login.windows.net", + tenantId: "testTenantId", + username: "test@contoso.com" + }); + ClientTestUtils.setCloudDiscoveryMetadataStubs(); + + const responseHandler = new ResponseHandler("this-is-a-client-id", testCacheManager, cryptoInterface, null); + const result = await responseHandler.handleServerTokenResponse(testResponse, authority, "POST", TEST_URIS.TEST_RESOURCE_ENDPT_WITH_PARAMS); + + expect(result.tokenType).to.be.eq(AuthenticationScheme.POP); + expect(result.accessToken).to.be.eq(signedJwt); + }); }); describe("validateServerAuthorizationCodeResponse", () => { diff --git a/lib/msal-common/test/utils/StringConstants.ts b/lib/msal-common/test/utils/StringConstants.ts index 9e3aa5e557..80437796ee 100644 --- a/lib/msal-common/test/utils/StringConstants.ts +++ b/lib/msal-common/test/utils/StringConstants.ts @@ -83,6 +83,7 @@ export const TEST_URIS = { TEST_AUTH_ENDPT_TENANT_ID: "https://login.microsoftonline.com/sample-tenantID/oauth2/v2.0/authorize", TEST_AUTH_ENDPT_WITH_PARAMS1: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?param1=value1", TEST_AUTH_ENDPT_WITH_PARAMS2: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?param1=value1¶m2=value2", + TEST_RESOURCE_ENDPT_WITH_PARAMS: "https://localhost:8081/endpoint?param1=value1¶m2=value2", TEST_REDIRECT_URI_LOCALHOST: "https://localhost:3000" }; @@ -123,7 +124,10 @@ export const RANDOM_TEST_GUID = "11553a9b-7116-48b1-9d48-f6d4a8ff8371"; export const TEST_POP_VALUES = { KID: "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs", ENCODED_REQ_CNF: "eyJraWQiOiJOemJMc1hoOHVEQ2NkLTZNTndYRjRXXzdub1dYRlpBZkhreFpzUkdDOVhzIiwieG1zX2tzbCI6InN3In0=", - DECODED_REQ_CNF: `{"kid":"NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs","xms_ksl":"sw"}` + DECODED_REQ_CNF: `{"kid":"NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs","xms_ksl":"sw"}`, + SAMPLE_POP_AT: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJjbmYiOnsia2lkIjoiTnpiTHNYaDh1RENjZC02TU53WEY0V183bm9XWEZaQWZIa3hac1JHQzlYcyJ9fQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", + SAMPLE_POP_AT_PAYLOAD_ENCODED: `eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJjbmYiOnsia2lkIjoiTnpiTHNYaDh1RENjZC02TU53WEY0V183bm9XWEZaQWZIa3hac1JHQzlYcyJ9fQ`, + SAMPLE_POP_AT_PAYLOAD_DECODED: `{"sub":"1234567890","name":"John Doe","iat":1516239022,"cnf":{"kid":"NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs"}}` }; export const TEST_STATE_VALUES = { @@ -241,11 +245,11 @@ export const AUTHENTICATION_RESULT = { export const POP_AUTHENTICATION_RESULT = { status: 200, body: { - "token_type": "Bearer", + "token_type": "pop", "scope": "openid profile User.Read email", "expires_in": 3599, "ext_expires_in": 3599, - "access_token": "thisIs.an.POPaccessT0ken", + "access_token": `${TEST_POP_VALUES.SAMPLE_POP_AT}`, "refresh_token": "thisIsARefreshT0ken", "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjFMVE16YWtpaGlSbGFfOHoyQkVKVlhlV01xbyJ9.eyJ2ZXIiOiIyLjAiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vOTE4ODA0MGQtNmM2Ny00YzViLWIxMTItMzZhMzA0YjY2ZGFkL3YyLjAiLCJzdWIiOiJBQUFBQUFBQUFBQUFBQUFBQUFBQUFJa3pxRlZyU2FTYUZIeTc4MmJidGFRIiwiYXVkIjoiNmNiMDQwMTgtYTNmNS00NmE3LWI5OTUtOTQwYzc4ZjVhZWYzIiwiZXhwIjoxNTM2MzYxNDExLCJpYXQiOjE1MzYyNzQ3MTEsIm5iZiI6MTUzNjI3NDcxMSwibmFtZSI6IkFiZSBMaW5jb2xuIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiQWJlTGlAbWljcm9zb2Z0LmNvbSIsIm9pZCI6IjAwMDAwMDAwLTAwMDAtMDAwMC02NmYzLTMzMzJlY2E3ZWE4MSIsInRpZCI6IjMzMzgwNDBkLTZjNjctNGM1Yi1iMTEyLTM2YTMwNGI2NmRhZCIsIm5vbmNlIjoiMTIzNTIzIiwiYWlvIjoiRGYyVVZYTDFpeCFsTUNXTVNPSkJjRmF0emNHZnZGR2hqS3Y4cTVnMHg3MzJkUjVNQjVCaXN2R1FPN1lXQnlqZDhpUURMcSFlR2JJRGFreXA1bW5PcmNkcUhlWVNubHRlcFFtUnA2QUlaOGpZIn0=.1AFWW-Ck5nROwSlltm7GzZvDwUkqvhSQpm55TQsmVo9Y59cLhRXpvB8n-55HCr9Z6G_31_UbeUkoz612I2j_Sm9FFShSDDjoaLQr54CreGIJvjtmS3EkK9a7SJBbcpL1MpUtlfygow39tFjY7EVNW9plWUvRrTgVk7lYLprvfzw-CIqw3gHC-T7IK_m_xkr08INERBtaecwhTeN4chPC4W3jdmw_lIxzC48YoQ0dB1L9-ImX98Egypfrlbm0IBL5spFzL6JDZIRRJOu8vecJvj1mq-IUhGt0MacxX8jdxYLP-KUu2d9MbNKpCKJuZ7p8gwTL5B7NlUdh_dmSviPWrw", "client_info": `${TEST_DATA_CLIENT_INFO.TEST_RAW_CLIENT_INFO}` From 372a3ebc6823bd6c0044ffd3e5f991eee8dda947 Mon Sep 17 00:00:00 2001 From: Prithvi Kanherkar Date: Fri, 28 Aug 2020 09:41:41 -0700 Subject: [PATCH 14/17] Update CryptoProvider.ts --- lib/msal-node/src/crypto/CryptoProvider.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/msal-node/src/crypto/CryptoProvider.ts b/lib/msal-node/src/crypto/CryptoProvider.ts index ebb4f5e0e2..c5c802d372 100644 --- a/lib/msal-node/src/crypto/CryptoProvider.ts +++ b/lib/msal-node/src/crypto/CryptoProvider.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { ICrypto, PkceCodes } from '@azure/msal-common'; +import { ICrypto, PkceCodes, SignedHttpRequest } from '@azure/msal-common'; import { GuidGenerator } from './GuidGenerator'; import { EncodingUtils } from './../utils/EncodingUtils'; import { PkceGenerator } from './PkceGenerator'; @@ -54,4 +54,8 @@ export class CryptoProvider implements ICrypto { getPublicKeyThumbprint(): Promise { throw new Error("Method not implemented."); } + + signJwt(payload: SignedHttpRequest, kid: string): Promise { + throw new Error("Method not implemented."); + } } From 572bbda0f1faa5fc9f30740038f56e4ab5102b10 Mon Sep 17 00:00:00 2001 From: Prithvi Kanherkar Date: Fri, 28 Aug 2020 09:56:28 -0700 Subject: [PATCH 15/17] Update CryptoProvider.ts --- lib/msal-node/src/crypto/CryptoProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msal-node/src/crypto/CryptoProvider.ts b/lib/msal-node/src/crypto/CryptoProvider.ts index c5c802d372..a0c38423d9 100644 --- a/lib/msal-node/src/crypto/CryptoProvider.ts +++ b/lib/msal-node/src/crypto/CryptoProvider.ts @@ -55,7 +55,7 @@ export class CryptoProvider implements ICrypto { throw new Error("Method not implemented."); } - signJwt(payload: SignedHttpRequest, kid: string): Promise { + signJwt(): Promise { throw new Error("Method not implemented."); } } From d9a92c788025d14082da83cab39d6b4100ed90cb Mon Sep 17 00:00:00 2001 From: Prithvi Kanherkar Date: Fri, 28 Aug 2020 10:50:56 -0700 Subject: [PATCH 16/17] Update CryptoProvider.ts --- lib/msal-node/src/crypto/CryptoProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msal-node/src/crypto/CryptoProvider.ts b/lib/msal-node/src/crypto/CryptoProvider.ts index a0c38423d9..e74df82fbc 100644 --- a/lib/msal-node/src/crypto/CryptoProvider.ts +++ b/lib/msal-node/src/crypto/CryptoProvider.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { ICrypto, PkceCodes, SignedHttpRequest } from '@azure/msal-common'; +import { ICrypto, PkceCodes } from '@azure/msal-common'; import { GuidGenerator } from './GuidGenerator'; import { EncodingUtils } from './../utils/EncodingUtils'; import { PkceGenerator } from './PkceGenerator'; From f6aa72480f10a93b93455597365d5164942ee2ce Mon Sep 17 00:00:00 2001 From: Prithvi Kanherkar Date: Fri, 28 Aug 2020 11:59:47 -0700 Subject: [PATCH 17/17] Update CryptoOps.spec.ts --- lib/msal-browser/test/crypto/CryptoOps.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msal-browser/test/crypto/CryptoOps.spec.ts b/lib/msal-browser/test/crypto/CryptoOps.spec.ts index 51c72f013c..8b19fa8f4b 100644 --- a/lib/msal-browser/test/crypto/CryptoOps.spec.ts +++ b/lib/msal-browser/test/crypto/CryptoOps.spec.ts @@ -90,5 +90,5 @@ describe("CryptoOps.ts Unit Tests", () => { expect(generateKeyPairSpy.calledWith(true, ["sign", "verify"])); expect(exportJwkSpy.calledWith((await generateKeyPairSpy.returnValues[0]).publicKey)); expect(regExp.test(pkThumbprint)).to.be.true; - }).timeout(2500); + }).timeout(0); });