Skip to content

Commit 9b1144f

Browse files
author
Prithvi Kanherkar
authored
Merge pull request #2153 from AzureAD/pop-handle-response
AT Proof-Of-Possession #2: Handle proof-of-possession tokens correctly in responses
2 parents 134b0e6 + f6aa724 commit 9b1144f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+504
-302
lines changed

lib/msal-browser/src/app/PublicClientApplication.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
TrustedAuthority,
1616
AuthorizationUrlRequest,
1717
PersistentCacheKeys,
18-
IdToken,
1918
ProtocolUtils,
2019
AuthorizationCodeRequest,
2120
Constants,
@@ -31,7 +30,8 @@ import {
3130
Logger,
3231
ServerTelemetryManager,
3332
ServerTelemetryRequest,
34-
ServerAuthorizationCodeResponse
33+
ServerAuthorizationCodeResponse,
34+
AuthToken
3535
} from "@azure/msal-common";
3636
import { buildConfiguration, Configuration } from "../config/Configuration";
3737
import { BrowserStorage } from "../cache/BrowserStorage";
@@ -681,7 +681,7 @@ export class PublicClientApplication implements IPublicClientApplication {
681681
// Only check for adal token if no SSO params are being used
682682
const adalIdTokenString = this.browserStorage.getItem(PersistentCacheKeys.ADAL_ID_TOKEN, CacheSchemaType.TEMPORARY) as string;
683683
if (!StringUtils.isEmpty(adalIdTokenString)) {
684-
const adalIdToken = new IdToken(adalIdTokenString, this.browserCrypto);
684+
const adalIdToken = new AuthToken(adalIdTokenString, this.browserCrypto);
685685
this.browserStorage.removeItem(PersistentCacheKeys.ADAL_ID_TOKEN);
686686
if (adalIdToken.claims && adalIdToken.claims.upn) {
687687
validatedRequest.loginHint = adalIdToken.claims.upn;

lib/msal-browser/src/crypto/CryptoOps.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Copyright (c) Microsoft Corporation. All rights reserved.
33
* Licensed under the MIT License.
44
*/
5-
import { ICrypto, PkceCodes } from "@azure/msal-common";
5+
import { ICrypto, PkceCodes, SignedHttpRequest } from "@azure/msal-common";
66
import { GuidGenerator } from "./GuidGenerator";
77
import { Base64Encode } from "../encode/Base64Encode";
88
import { Base64Decode } from "../encode/Base64Decode";
@@ -74,4 +74,8 @@ export class CryptoOps implements ICrypto {
7474
async generatePkceCodes(): Promise<PkceCodes> {
7575
return this.pkceGenerator.generateCodes();
7676
}
77+
78+
signJwt(payload: SignedHttpRequest, kid: string): Promise<string> {
79+
throw new Error("Method not implemented.");
80+
}
7781
}

lib/msal-browser/test/app/PublicClientApplication.spec.ts

+36-27
Large diffs are not rendered by default.

lib/msal-browser/test/crypto/CryptoOps.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,5 +90,5 @@ describe("CryptoOps.ts Unit Tests", () => {
9090
expect(generateKeyPairSpy.calledWith(true, ["sign", "verify"]));
9191
expect(exportJwkSpy.calledWith((await generateKeyPairSpy.returnValues[0]).publicKey));
9292
expect(regExp.test(pkThumbprint)).to.be.true;
93-
}).timeout(2500);
93+
}).timeout(0);
9494
});

lib/msal-browser/test/interaction_handler/InteractionHandler.spec.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { expect } from "chai";
22
import { InteractionHandler } from "../../src/interaction_handler/InteractionHandler";
3-
import { PkceCodes, NetworkRequestOptions, LogLevel, AccountInfo, AuthorityFactory, AuthorizationCodeRequest, AuthenticationResult, CacheManager, AuthorizationCodeClient } from "@azure/msal-common";
3+
import { PkceCodes, NetworkRequestOptions, LogLevel, AccountInfo, AuthorityFactory, AuthorizationCodeRequest, AuthenticationResult, CacheManager, AuthorizationCodeClient, AuthenticationScheme } from "@azure/msal-common";
44
import { Configuration, buildConfiguration } from "../../src/config/Configuration";
55
import { TEST_CONFIG, TEST_URIS, TEST_DATA_CLIENT_INFO, TEST_TOKENS, TEST_TOKEN_LIFETIMES, TEST_HASHES, TEST_POP_VALUES } from "../utils/StringConstants";
66
import { BrowserStorage } from "../../src/cache/BrowserStorage";
@@ -111,6 +111,9 @@ describe("InteractionHandler.ts Unit Tests", () => {
111111
},
112112
getPublicKeyThumbprint: async (): Promise<string> => {
113113
return TEST_POP_VALUES.ENCODED_REQ_CNF;
114+
},
115+
signJwt: async (): Promise<string> => {
116+
return "signedJwt";
114117
}
115118
},
116119
storageInterface: new TestStorageInterface(),
@@ -187,7 +190,8 @@ describe("InteractionHandler.ts Unit Tests", () => {
187190
idTokenClaims: idTokenClaims,
188191
tenantId: idTokenClaims.tid,
189192
uniqueId: idTokenClaims.oid,
190-
state: "testState"
193+
state: "testState",
194+
tokenType: AuthenticationScheme.BEARER
191195
};
192196
sinon.stub(AuthorizationCodeClient.prototype, "handleFragmentResponse").returns(testCodeResponse);
193197
const acquireTokenSpy = sinon.stub(AuthorizationCodeClient.prototype, "acquireToken").resolves(testTokenResponse);

lib/msal-browser/test/interaction_handler/PopupHandler.spec.ts

+3
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ describe("PopupHandler.ts Unit Tests", () => {
9595
},
9696
getPublicKeyThumbprint: async (): Promise<string> => {
9797
return TEST_POP_VALUES.ENCODED_REQ_CNF;
98+
},
99+
signJwt: async (): Promise<string> => {
100+
return "signedJwt";
98101
}
99102
},
100103
storageInterface: new TestStorageInterface(),

lib/msal-browser/test/interaction_handler/RedirectHandler.spec.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ chai.use(chaiAsPromised);
44
const expect = chai.expect;
55
import sinon from "sinon";
66
import { Configuration, buildConfiguration } from "../../src/config/Configuration";
7-
import { PkceCodes, NetworkRequestOptions, LogLevel, AccountInfo, AuthorityFactory, AuthorizationCodeRequest, Constants, AuthenticationResult, CacheSchemaType, CacheManager, AuthorizationCodeClient } from "@azure/msal-common";
7+
import { PkceCodes, NetworkRequestOptions, LogLevel, AccountInfo, AuthorityFactory, AuthorizationCodeRequest, Constants, AuthenticationResult, CacheSchemaType, CacheManager, AuthorizationCodeClient, AuthenticationScheme } from "@azure/msal-common";
88
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";
99
import { BrowserStorage } from "../../src/cache/BrowserStorage";
1010
import { RedirectHandler } from "../../src/interaction_handler/RedirectHandler";
@@ -75,6 +75,9 @@ describe("RedirectHandler.ts Unit Tests", () => {
7575
},
7676
getPublicKeyThumbprint: async (): Promise<string> => {
7777
return TEST_POP_VALUES.ENCODED_REQ_CNF;
78+
},
79+
signJwt: async (): Promise<string> => {
80+
return "signedJwt";
7881
}
7982
},
8083
storageInterface: browserStorage,
@@ -211,7 +214,8 @@ describe("RedirectHandler.ts Unit Tests", () => {
211214
expiresOn: new Date(Date.now() + (TEST_TOKEN_LIFETIMES.DEFAULT_EXPIRES_IN * 1000)),
212215
idTokenClaims: idTokenClaims,
213216
tenantId: idTokenClaims.tid,
214-
uniqueId: idTokenClaims.oid
217+
uniqueId: idTokenClaims.oid,
218+
tokenType: AuthenticationScheme.BEARER
215219
};
216220
const browserCrypto = new CryptoOps();
217221
const testAuthCodeRequest: AuthorizationCodeRequest = {

lib/msal-browser/test/interaction_handler/SilentHandler.spec.ts

+3
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ describe("SilentHandler.ts Unit Tests", () => {
9696
},
9797
getPublicKeyThumbprint: async (): Promise<string> => {
9898
return TEST_POP_VALUES.ENCODED_REQ_CNF;
99+
},
100+
signJwt: async (): Promise<string> => {
101+
return "signedJwt";
99102
}
100103
},
101104
storageInterface: new TestStorageInterface(),
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License.
4+
*/
5+
import { TokenClaims } from "./TokenClaims";
6+
import { DecodedAuthToken } from "./DecodedAuthToken";
7+
import { ClientAuthError } from "../error/ClientAuthError";
8+
import { StringUtils } from "../utils/StringUtils";
9+
import { ICrypto } from "../crypto/ICrypto";
10+
11+
/**
12+
* JWT Token representation class. Parses token string and generates claims object.
13+
*/
14+
export class AuthToken {
15+
16+
// Raw Token string
17+
rawToken: string;
18+
// Claims inside token
19+
claims: TokenClaims;
20+
constructor(rawToken: string, crypto: ICrypto) {
21+
if (StringUtils.isEmpty(rawToken)) {
22+
throw ClientAuthError.createTokenNullOrEmptyError(rawToken);
23+
}
24+
25+
this.rawToken = rawToken;
26+
this.claims = AuthToken.extractTokenClaims(rawToken, crypto);
27+
}
28+
29+
/**
30+
* Extract token by decoding the rawToken
31+
*
32+
* @param encodedToken
33+
*/
34+
static extractTokenClaims(encodedToken: string, crypto: ICrypto): TokenClaims {
35+
// token will be decoded to get the username
36+
const decodedToken: DecodedAuthToken = StringUtils.decodeAuthToken(encodedToken);
37+
if (!decodedToken) {
38+
return null;
39+
}
40+
try {
41+
const base64TokenPayload = decodedToken.JWSPayload;
42+
// base64Decode() should throw an error if there is an issue
43+
const base64Decoded = crypto.base64Decode(base64TokenPayload);
44+
return JSON.parse(base64Decoded) as TokenClaims;
45+
} catch (err) {
46+
throw ClientAuthError.createTokenParsingError(err);
47+
}
48+
}
49+
}

lib/msal-common/src/account/DecodedJwt.ts renamed to lib/msal-common/src/account/DecodedAuthToken.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
/**
77
* Interface for Decoded JWT tokens.
88
*/
9-
export interface DecodedJwt {
9+
export interface DecodedAuthToken {
1010
header: string,
1111
JWSPayload: string,
1212
JWSSig: string

lib/msal-common/src/account/IdToken.ts

-49
This file was deleted.

lib/msal-common/src/account/IdTokenClaims.ts renamed to lib/msal-common/src/account/TokenClaims.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
/**
77
* Type which describes Id Token claims known by MSAL.
88
*/
9-
export type IdTokenClaims = {
9+
export type TokenClaims = {
1010
iss?: string,
1111
oid?: string,
1212
sub?: string,
@@ -19,5 +19,8 @@ export type IdTokenClaims = {
1919
exp?: number,
2020
home_oid?: string,
2121
sid?: string,
22-
cloud_instance_host_name?: string
22+
cloud_instance_host_name?: string,
23+
cnf?: {
24+
kid: string;
25+
};
2326
};

lib/msal-common/src/cache/entities/AccessTokenEntity.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
*/
55

66
import { CredentialEntity } from "./CredentialEntity";
7-
import { CredentialType } from "../../utils/Constants";
7+
import { CredentialType, AuthenticationScheme } from "../../utils/Constants";
88
import { TimeUtils } from "../../utils/TimeUtils";
9+
import { StringUtils } from "../../utils/StringUtils";
910

1011
/**
1112
* ACCESS_TOKEN Credential Type
@@ -60,7 +61,8 @@ export class AccessTokenEntity extends CredentialEntity {
6061
tenantId: string,
6162
scopes: string,
6263
expiresOn: number,
63-
extExpiresOn: number
64+
extExpiresOn: number,
65+
tokenType?: string
6466
): AccessTokenEntity {
6567
const atEntity: AccessTokenEntity = new AccessTokenEntity();
6668

@@ -81,6 +83,7 @@ export class AccessTokenEntity extends CredentialEntity {
8183
atEntity.realm = tenantId;
8284
atEntity.target = scopes;
8385

86+
atEntity.tokenType = StringUtils.isEmpty(tokenType) ? AuthenticationScheme.BEARER : tokenType;
8487
return atEntity;
8588
}
8689
}

lib/msal-common/src/cache/entities/AccountEntity.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
CacheType,
1010
} from "../../utils/Constants";
1111
import { Authority } from "../../authority/Authority";
12-
import { IdToken } from "../../account/IdToken";
12+
import { AuthToken } from "../../account/AuthToken";
1313
import { ICrypto } from "../../crypto/ICrypto";
1414
import { buildClientInfo } from "../../account/ClientInfo";
1515
import { StringUtils } from "../../utils/StringUtils";
@@ -125,7 +125,7 @@ export class AccountEntity {
125125
static createAccount(
126126
clientInfo: string,
127127
authority: Authority,
128-
idToken: IdToken,
128+
idToken: AuthToken,
129129
crypto: ICrypto
130130
): AccountEntity {
131131
const account: AccountEntity = new AccountEntity();
@@ -164,7 +164,7 @@ export class AccountEntity {
164164
*/
165165
static createADFSAccount(
166166
authority: Authority,
167-
idToken: IdToken
167+
idToken: AuthToken
168168
): AccountEntity {
169169
const account: AccountEntity = new AccountEntity();
170170

lib/msal-common/src/client/AuthorizationCodeClient.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,7 @@ export class AuthorizationCodeClient extends BaseClient {
7171

7272
// Validate response. This function throws a server error if an error is returned by the server.
7373
responseHandler.validateTokenResponse(response.body);
74-
const tokenResponse = responseHandler.handleServerTokenResponse(response.body, this.authority, cachedNonce, cachedState);
75-
76-
return tokenResponse;
74+
return await responseHandler.handleServerTokenResponse(response.body, this.authority, request.resourceRequestMethod, request.resourceRequestUri, cachedNonce, cachedState);
7775
}
7876

7977
/**
@@ -176,7 +174,7 @@ export class AuthorizationCodeClient extends BaseClient {
176174

177175
if (request.authenticationScheme === AuthenticationScheme.POP) {
178176
const popTokenGenerator = new PopTokenGenerator(this.cryptoUtils);
179-
const cnfString = await popTokenGenerator.generateCnf();
177+
const cnfString = await popTokenGenerator.generateCnf(request.resourceRequestMethod, request.resourceRequestUri);
180178
parameterBuilder.addPopToken(cnfString);
181179
}
182180

lib/msal-common/src/client/DeviceCodeClient.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@ export class DeviceCodeClient extends BaseClient {
4747

4848
// Validate response. This function throws a server error if an error is returned by the server.
4949
responseHandler.validateTokenResponse(response);
50-
const tokenResponse = responseHandler.handleServerTokenResponse(
50+
return await responseHandler.handleServerTokenResponse(
5151
response,
52-
this.authority
52+
this.authority,
53+
request.resourceRequestMethod,
54+
request.resourceRequestUri
5355
);
54-
55-
return tokenResponse;
5656
}
5757

5858
/**

lib/msal-common/src/client/RefreshTokenClient.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,10 @@ export class RefreshTokenClient extends BaseClient {
3434
);
3535

3636
responseHandler.validateTokenResponse(response.body);
37-
const tokenResponse = responseHandler.handleServerTokenResponse(
37+
return await responseHandler.handleServerTokenResponse(
3838
response.body,
3939
this.authority
4040
);
41-
42-
return tokenResponse;
4341
}
4442

4543
private async executeTokenRequest(request: RefreshTokenRequest, authority: Authority)

0 commit comments

Comments
 (0)