Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
06a744b
Initial Commit
tnorling May 15, 2020
f6fefcd
Create clientInfo from idToken
tnorling May 15, 2020
69e40cb
Merge branch 'cloud-discovery' of https://github.com/AzureAD/microsof…
tnorling May 27, 2020
56fdfdd
Update default type
tnorling May 27, 2020
10335de
Update Sample Readmes
tnorling May 27, 2020
520a465
Update package name for sample
tnorling May 27, 2020
c2ccdec
Merge branch 'cloud-discovery' of https://github.com/AzureAD/microsof…
tnorling Jun 3, 2020
00873a3
Merge branch 'dev' of https://github.com/AzureAD/microsoft-authentica…
tnorling Jun 3, 2020
93549a4
Strip Policy from UID
tnorling Jun 4, 2020
ccb09e7
Update policy strip logic
tnorling Jun 4, 2020
d8862d5
Sample
tnorling Jun 4, 2020
35ad895
Update with lab app registration
tnorling Jun 5, 2020
167ca67
Merge branch 'dev' of https://github.com/AzureAD/microsoft-authentica…
tnorling Jun 15, 2020
47d0a87
Update AuthorityType logic
tnorling Jun 15, 2020
336fc2a
Remove sample
tnorling Jun 15, 2020
c88a12e
Move isAdfs to Authority class
tnorling Jun 15, 2020
661f000
Unit tests
tnorling Jun 15, 2020
3b9c058
Add tests
tnorling Jun 16, 2020
5197bc4
Add upn as backup for username
tnorling Jun 16, 2020
76b1685
Revert "Add upn as backup for username"
tnorling Jun 16, 2020
c969187
Add upn as backup for username
tnorling Jun 16, 2020
c91e0f0
Merge branch 'dev' into b2c-multiple-policies
tnorling Jun 16, 2020
456ba37
Remove samples, will be added in different PR
tnorling Jun 16, 2020
5a5472b
Address Feedback
tnorling Jun 16, 2020
5398cca
Update to ternary
tnorling Jun 16, 2020
dfa6f5d
Update lib/msal-core/src/ClientInfo.ts
tnorling Jun 17, 2020
6114764
Merge branch 'msal-core-adfs' of https://github.com/AzureAD/microsoft…
tnorling Jun 17, 2020
a283256
Add sample + E2E
tnorling Jun 17, 2020
afd59f0
Update testRunner and LabClient
tnorling Jun 17, 2020
9b29939
Merge branch 'dev' into msal-core-adfs
tnorling Jun 17, 2020
4a6d1d3
Merge branch 'msal-core-adfs' of https://github.com/AzureAD/microsoft…
tnorling Jun 17, 2020
85a990a
Add readme to sample
tnorling Jun 22, 2020
c8c7a1e
Update server port
tnorling Jun 23, 2020
ab12b92
Update samples/VanillaJSTestApp/app/adfs/Readme.md
tnorling Jun 23, 2020
88c9132
Addressing feedback
tnorling Jun 24, 2020
6a962d5
Merge branch 'adfs-sample' of https://github.com/AzureAD/microsoft-au…
tnorling Jun 24, 2020
c1b8d1a
Merge branch 'dev' of https://github.com/AzureAD/microsoft-authentica…
tnorling Jul 7, 2020
4a664b9
Fix linting
tnorling Jul 7, 2020
176334f
Merge branch 'dev' into msal-core-adfs
tnorling Jul 25, 2020
2201bd3
Merge branch 'dev' of https://github.com/AzureAD/microsoft-authentica…
tnorling Aug 10, 2020
f674871
Cleanup
tnorling Aug 10, 2020
b861e2d
Update FAQ
tnorling Aug 10, 2020
553a623
Merge branch 'dev' into msal-core-adfs
tnorling Aug 11, 2020
63bac81
Change files
tnorling Aug 12, 2020
c08165c
Merge branch 'dev' of https://github.com/AzureAD/microsoft-authentica…
tnorling Aug 12, 2020
91dc3ac
Merge branch 'dev' of https://github.com/AzureAD/microsoft-authentica…
tnorling Aug 12, 2020
b082274
Tests passing
tnorling Aug 12, 2020
d5548a1
Change files
tnorling Aug 12, 2020
c400722
Merge branch 'dev' into msal-core-adfs
tnorling Aug 17, 2020
5f10555
Merge pull request #1668 from AzureAD/msal-core-adfs
tnorling Aug 17, 2020
30f7b66
Merge branch '1.4.0-release' of https://github.com/AzureAD/microsoft-…
tnorling Aug 17, 2020
603c101
[msal-core] Fix response type configuration by basing it mainly on sc…
Aug 17, 2020
cefd4ca
Merge branch '1.4.0-release' of https://github.com/AzureAD/microsoft-…
tnorling Aug 17, 2020
3782c0b
Update test
tnorling Aug 17, 2020
364621d
Update samples/VanillaJSTestApp/app/adfs/Readme.md
tnorling Aug 17, 2020
4fcdb41
Update welcome message
tnorling Aug 17, 2020
f5378d3
Merge branch 'adfs-sample' of https://github.com/AzureAD/microsoft-au…
tnorling Aug 17, 2020
c4fc53c
Add federationProvider to LabClient
tnorling Aug 17, 2020
2e2116d
Merge pull request #1791 from AzureAD/adfs-sample
tnorling Aug 18, 2020
065b8d8
Fix heading
tnorling Aug 18, 2020
83746d1
Update lib/msal-core/docs/FAQ.md
tnorling Aug 18, 2020
2be68fb
Merge branch '1.4.0-release' of https://github.com/AzureAD/microsoft-…
tnorling Aug 18, 2020
148fa9b
Address Feedback
tnorling Aug 18, 2020
9c011d5
Tests passing
tnorling Aug 19, 2020
f10c797
Merge pull request #1757 from AzureAD/b2c-multiple-policies
tnorling Aug 19, 2020
649a731
[msal-core] Fix invalid state issue by removing second layer of URL d…
Aug 20, 2020
c45ccd5
Merge branch 'dev' of https://github.com/AzureAD/microsoft-authentica…
tnorling Aug 24, 2020
efcfbb2
Update SRI hashes
tnorling Aug 24, 2020
2985cc2
Merge branch 'dev' into 1.4.0-release
tnorling Aug 24, 2020
552edd5
Merge branch 'dev' into 1.4.0-release
tnorling Aug 24, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions change/msal-2020-08-12-14-47-04-b2c-multiple-policies.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "minor",
"comment": "B2C Multiple Policy Support (#1757)",
"packageName": "msal",
"email": "[email protected]",
"dependentChangeType": "patch",
"date": "2020-08-12T21:47:04.427Z"
}
41 changes: 28 additions & 13 deletions lib/msal-core/docs/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
**[Common Issues](#common-issues)**
1. [How to avoid page reloads when acquiring and renewing tokens silently?](#how-to-avoid-page-reloads-when-acquiring-and-renewing-tokens-silently)
1. [Why is my application stuck in an infinite redirect loop?](#why-is-my-application-stuck-in-an-infinite-redirect-loop)
1. [I'm using one of your samples on Internet Explorer and I get the error SignIn() is not defined](#im-using-one-of-your-samples-on-internet-explorer-and-i-get-the-error-signin-is-not-defined)
1. [Why is MSAL throwing an error?](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-core/docs/errors.md)

***
Expand Down Expand Up @@ -346,25 +347,25 @@ Please see the documentation on [Tenancy in Azure Active Directory](https://docs

## My B2C application has more than one user-flow/policy. How do I work with multiple policies in MSAL.js?

Unfortunately, MSAL.js does not support multiple B2C policies _out-of-the-box_ at this moment. Nevertheless, you can still utilize the library to workaround this limitation. For instance, review our sample [here](https://github.com/Azure-Samples/active-directory-b2c-javascript-msal-singlepageapp) to see how to implement **sign-up/sign-in** and **password reset** user flows.
MSAL.js allows you to provide an authority on a per-request basis. To acquire an access token for a different policy than the one you signed in with, simply pass the relevant authority as a part of the request object.

## How can I implement password reset user flow in my B2C application with MSAL.js?
```javascript
const request = {
scopes: ["https://b2ctenant.onmicrosoft.com/exampleApi/exampleScope"],
authority: "https://b2ctenant.b2clogin.com/b2ctenant.onmicrosoft.com/examplePolicy"
}

Please review our sample [here](https://github.com/Azure-Samples/active-directory-b2c-javascript-msal-singlepageapp) to see how to implement the **password reset** user flow.
msal.acquireTokenPopup(request);
```

## I'm using one of your samples on Internet Explorer and I get the error "SignIn() is not defined"
A few additional things to keep in mind regarding multiple policy scenarios:

Our samples use [ES6](http://www.ecma-international.org/ecma-262/6.0/) conventions, in particular **promises**, **arrow functions** and **template literals**. As such, they will **not** work on Internet Explorer out-of-the-box. For **promises**, you need to add a polyfill, i.e.:
- MSAL.js 1.x is only able to cache one id_token at a time, which means that obtaining an id_token for a different policy will overwrite the cached id_token from the previous policy.
- Some policies, such as profile_edit and password_reset, require interaction and cannot be used to renew tokens silently. Obtaining a cached access token via `acquireTokenSilent` is still possible, however, if the token is expired the service will throw an "X-Frame Options DENY" error when MSAL attempts to renew it. When this happens your application must catch this error and fallback to calling an interactive method (`acquireTokenRedirect` or `acquireTokenPopup`)

```html
<head>
<!-- adding pollyfil for promises on IE11 -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/js-polyfills/0.1.42/polyfill.min.js">
</script>
</head>
```
## How can I implement password reset user flow in my B2C application with MSAL.js?

For **arrow functions** and **template literals**, you need to transpile them to old JavaScript. You can use [this tool](https://babeljs.io/repl) to help with the process.
Please checkout our sample [here](https://github.com/Azure-Samples/active-directory-b2c-javascript-msal-singlepageapp) to see how to implement the **password reset** user flow.

# Common Issues

Expand Down Expand Up @@ -534,3 +535,17 @@ msal = new Msal.UserAgentApplication({
}
})
```

## I'm using one of your samples on Internet Explorer and I get the error "SignIn() is not defined"

Our samples use [ES6](http://www.ecma-international.org/ecma-262/6.0/) conventions, in particular **promises**, **arrow functions** and **template literals**. As such, they will **not** work on Internet Explorer out-of-the-box. For **promises**, you need to add a polyfill, i.e.:

```html
<head>
<!-- adding pollyfil for promises on IE11 -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/js-polyfills/0.1.42/polyfill.min.js">
</script>
</head>
```

For **arrow functions** and **template literals**, you need to transpile them to old JavaScript. You can use [this tool](https://babeljs.io/repl) to help with the process.
35 changes: 31 additions & 4 deletions lib/msal-core/src/ClientInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,16 @@ export class ClientInfo {
this._utid = utid;
}

static createClientInfoFromIdToken(idToken:IdToken): string {
static createClientInfoFromIdToken(idToken:IdToken, authority: string): ClientInfo {
const clientInfo = {
uid: idToken.subject,
utid: ""
};

return CryptoUtils.base64Encode(JSON.stringify(clientInfo));
return new ClientInfo(CryptoUtils.base64Encode(JSON.stringify(clientInfo)), authority);
}

constructor(rawClientInfo: string) {
constructor(rawClientInfo: string, authority: string) {
if (!rawClientInfo || StringUtils.isEmpty(rawClientInfo)) {
this.uid = "";
this.utid = "";
Expand All @@ -52,7 +52,7 @@ export class ClientInfo {
const clientInfo: ClientInfo = <ClientInfo>JSON.parse(decodedClientInfo);
if (clientInfo) {
if (clientInfo.hasOwnProperty("uid")) {
this.uid = clientInfo.uid;
this.uid = authority ? ClientInfo.stripPolicyFromUid(clientInfo.uid, authority): clientInfo.uid;
}

if (clientInfo.hasOwnProperty("utid")) {
Expand All @@ -63,4 +63,31 @@ export class ClientInfo {
throw ClientAuthError.createClientInfoDecodingError(e);
}
}

static stripPolicyFromUid(uid: string, authority: string): string {
const uidSegments = uid.split("-");
// Reverse the url segments so the last one is more easily accessible
const urlSegments = authority.split("/").reverse();
let policy = "";

if (!StringUtils.isEmpty(urlSegments[0])) {
policy = urlSegments[0];
} else if (urlSegments.length > 1) {
// If the original url had a trailing slash, urlSegments[0] would be "" so take the next element
policy = urlSegments[1];
}

if (uidSegments[uidSegments.length - 1] === policy) {
// If the last segment of uid matches the last segment of authority url, remove the last segment of uid
return uidSegments.slice(0, uidSegments.length - 1).join("-");
}

return uid;
}

public encodeClientInfo() {
const clientInfo = JSON.stringify({uid: this.uid, utid: this.utid});

return CryptoUtils.base64Encode(clientInfo);
}
}
37 changes: 18 additions & 19 deletions lib/msal-core/src/UserAgentApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1600,11 +1600,10 @@ export class UserAgentApplication {
* @private
*/
/* tslint:disable:no-string-literal */
private saveAccessToken(response: AuthResponse, authority: string, parameters: any, clientInfo: string, idTokenObj: IdToken): AuthResponse {
private saveAccessToken(response: AuthResponse, authority: string, parameters: any, clientInfo: ClientInfo, idTokenObj: IdToken): AuthResponse {
this.logger.verbose("SaveAccessToken has been called");
let scope: string;
const accessTokenResponse = { ...response };
const clientObj: ClientInfo = new ClientInfo(clientInfo);
let expiration: number;

// if the response contains "scope"
Expand Down Expand Up @@ -1633,8 +1632,8 @@ export class UserAgentApplication {
const expiresIn = TimeUtils.parseExpiresIn(parameters[ServerHashParamKeys.EXPIRES_IN]);
const parsedState = RequestUtils.parseLibraryState(parameters[ServerHashParamKeys.STATE]);
expiration = parsedState.ts + expiresIn;
const accessTokenKey = new AccessTokenKey(authority, this.clientId, scope, clientObj.uid, clientObj.utid);
const accessTokenValue = new AccessTokenValue(parameters[ServerHashParamKeys.ACCESS_TOKEN], idTokenObj.rawIdToken, expiration.toString(), clientInfo);
const accessTokenKey = new AccessTokenKey(authority, this.clientId, scope, clientInfo.uid, clientInfo.utid);
const accessTokenValue = new AccessTokenValue(parameters[ServerHashParamKeys.ACCESS_TOKEN], idTokenObj.rawIdToken, expiration.toString(), clientInfo.encodeClientInfo());

this.cacheStorage.setItem(JSON.stringify(accessTokenKey), JSON.stringify(accessTokenValue));
this.logger.verbose("Saving token to cache");
Expand All @@ -1647,9 +1646,9 @@ export class UserAgentApplication {
this.logger.verbose("Response parameters does not contain scope, clientId set as scope");

// Generate and cache accessTokenKey and accessTokenValue
const accessTokenKey = new AccessTokenKey(authority, this.clientId, scope, clientObj.uid, clientObj.utid);
const accessTokenKey = new AccessTokenKey(authority, this.clientId, scope, clientInfo.uid, clientInfo.utid);
expiration = Number(idTokenObj.expiration);
const accessTokenValue = new AccessTokenValue(parameters[ServerHashParamKeys.ID_TOKEN], parameters[ServerHashParamKeys.ID_TOKEN], expiration.toString(), clientInfo);
const accessTokenValue = new AccessTokenValue(parameters[ServerHashParamKeys.ID_TOKEN], parameters[ServerHashParamKeys.ID_TOKEN], expiration.toString(), clientInfo.encodeClientInfo());
this.cacheStorage.setItem(JSON.stringify(accessTokenKey), JSON.stringify(accessTokenValue));
this.logger.verbose("Saving token to cache");
accessTokenResponse.scopes = Constants.oidcScopes;
Expand Down Expand Up @@ -1752,7 +1751,7 @@ export class UserAgentApplication {
}
response.accountState = this.getAccountState(stateInfo.state);

let clientInfo: string = "";
let clientInfo: ClientInfo;

// Process access_token
if (hashParams.hasOwnProperty(ServerHashParamKeys.ACCESS_TOKEN)) {
Expand Down Expand Up @@ -1782,14 +1781,14 @@ export class UserAgentApplication {
// retrieve client_info - if it is not found, generate the uid and utid from idToken
if (hashParams.hasOwnProperty(ServerHashParamKeys.CLIENT_INFO)) {
this.logger.verbose("Fragment has clientInfo");
clientInfo = hashParams[ServerHashParamKeys.CLIENT_INFO];
clientInfo = new ClientInfo(hashParams[ServerHashParamKeys.CLIENT_INFO], authority);
} else if (this.authorityInstance.AuthorityType === AuthorityType.Adfs) {
clientInfo = ClientInfo.createClientInfoFromIdToken(idTokenObj);
clientInfo = ClientInfo.createClientInfoFromIdToken(idTokenObj, authority);
} else {
this.logger.warning("ClientInfo not received in the response from AAD");
}

response.account = Account.createAccount(idTokenObj, new ClientInfo(clientInfo));
response.account = Account.createAccount(idTokenObj, clientInfo);
this.logger.verbose("Account object created from response");

let accountKey: string;
Expand Down Expand Up @@ -1835,20 +1834,20 @@ export class UserAgentApplication {
// set the idToken
idTokenObj = new IdToken(hashParams[ServerHashParamKeys.ID_TOKEN]);

// set authority
const authority: string = this.populateAuthority(stateInfo.state, this.inCookie, this.cacheStorage, idTokenObj);

response = ResponseUtils.setResponseIdToken(response, idTokenObj);
if (hashParams.hasOwnProperty(ServerHashParamKeys.CLIENT_INFO)) {
this.logger.verbose("Fragment has clientInfo");
clientInfo = hashParams[ServerHashParamKeys.CLIENT_INFO];
clientInfo = new ClientInfo(hashParams[ServerHashParamKeys.CLIENT_INFO], authority);
} else if (this.authorityInstance.AuthorityType === AuthorityType.Adfs) {
clientInfo = ClientInfo.createClientInfoFromIdToken(idTokenObj);
clientInfo = ClientInfo.createClientInfoFromIdToken(idTokenObj, authority);
} else {
this.logger.warning("ClientInfo not received in the response from AAD");
}

// set authority
const authority: string = this.populateAuthority(stateInfo.state, this.inCookie, this.cacheStorage, idTokenObj);

this.account = Account.createAccount(idTokenObj, new ClientInfo(clientInfo));
this.account = Account.createAccount(idTokenObj, clientInfo);
response.account = this.account;
this.logger.verbose("Account object created from response");

Expand All @@ -1866,7 +1865,7 @@ export class UserAgentApplication {
else {
this.logger.verbose("Nonce matches, saving idToken to cache");
this.cacheStorage.setItem(PersistentCacheKeys.IDTOKEN, hashParams[ServerHashParamKeys.ID_TOKEN], this.inCookie);
this.cacheStorage.setItem(PersistentCacheKeys.CLIENT_INFO, clientInfo, this.inCookie);
this.cacheStorage.setItem(PersistentCacheKeys.CLIENT_INFO, clientInfo.encodeClientInfo(), this.inCookie);

// Save idToken as access token for app itself
this.saveAccessToken(response, authority, hashParams, clientInfo, idTokenObj);
Expand Down Expand Up @@ -1961,7 +1960,7 @@ export class UserAgentApplication {

if (!StringUtils.isEmpty(rawIdToken) && !StringUtils.isEmpty(rawClientInfo)) {
const idToken = new IdToken(rawIdToken);
const clientInfo = new ClientInfo(rawClientInfo);
const clientInfo = new ClientInfo(rawClientInfo, "");
this.account = Account.createAccount(idToken, clientInfo);
return this.account;
}
Expand Down Expand Up @@ -1997,7 +1996,7 @@ export class UserAgentApplication {

for (let i = 0; i < accessTokenCacheItems.length; i++) {
const idToken = new IdToken(accessTokenCacheItems[i].value.idToken);
const clientInfo = new ClientInfo(accessTokenCacheItems[i].value.homeAccountIdentifier);
const clientInfo = new ClientInfo(accessTokenCacheItems[i].value.homeAccountIdentifier, "");
const account: Account = Account.createAccount(idToken, clientInfo);
accounts.push(account);
}
Expand Down
12 changes: 6 additions & 6 deletions lib/msal-core/test/Account.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { expect } from "chai";
import { ClientInfo } from "../src/ClientInfo";
import { IdToken } from "../src/IdToken";
import { Account } from "../src/Account";
import { TEST_TOKENS, TEST_DATA_CLIENT_INFO } from "./TestConstants";
import { TEST_TOKENS, TEST_DATA_CLIENT_INFO, TEST_CONFIG } from "./TestConstants";
import { CryptoUtils } from "../src/utils/CryptoUtils";

describe("Account.ts Class", function() {

let idToken: IdToken = new IdToken(TEST_TOKENS.IDTOKEN_V2);
let clientInfo: ClientInfo = new ClientInfo(TEST_DATA_CLIENT_INFO.TEST_RAW_CLIENT_INFO);
let clientInfo: ClientInfo = new ClientInfo(TEST_DATA_CLIENT_INFO.TEST_RAW_CLIENT_INFO, TEST_CONFIG.validAuthority);

describe("createAccount", () => {
it("verifies account object is created", () => {
Expand Down Expand Up @@ -49,7 +49,7 @@ describe("Account.ts Class", function() {
const tempIdToken = idToken;
tempIdToken.subject = "";

const emptyClientInfo = new ClientInfo(ClientInfo.createClientInfoFromIdToken(tempIdToken));
const emptyClientInfo = ClientInfo.createClientInfoFromIdToken(tempIdToken, TEST_CONFIG.validAuthority);
const account = Account.createAccount(idToken, emptyClientInfo);

expect(account.homeAccountIdentifier).to.be.undefined;
Expand All @@ -71,7 +71,7 @@ describe("Account.ts Class", function() {
const tempIdToken = idToken;
tempIdToken.subject = "";

const clientInfo2 = new ClientInfo(ClientInfo.createClientInfoFromIdToken(tempIdToken));
const clientInfo2 = ClientInfo.createClientInfoFromIdToken(tempIdToken, TEST_CONFIG.validAuthority);
const account1 = Account.createAccount(idToken, clientInfo2);
const account2 = Account.createAccount(idToken, clientInfo);

Expand All @@ -83,7 +83,7 @@ describe("Account.ts Class", function() {
const tempIdToken = idToken;
tempIdToken.subject = "";

const clientInfo2 = new ClientInfo(ClientInfo.createClientInfoFromIdToken(tempIdToken));
const clientInfo2 = ClientInfo.createClientInfoFromIdToken(tempIdToken, TEST_CONFIG.validAuthority);
const account1 = Account.createAccount(idToken, clientInfo);
const account2 = Account.createAccount(idToken, clientInfo2);

Expand All @@ -102,7 +102,7 @@ describe("Account.ts Class", function() {
const tempIdToken = idToken;
tempIdToken.subject = "test-oid";

const clientInfo2 = new ClientInfo(ClientInfo.createClientInfoFromIdToken(tempIdToken));
const clientInfo2 = ClientInfo.createClientInfoFromIdToken(tempIdToken, TEST_CONFIG.validAuthority);
const account1 = Account.createAccount(idToken, clientInfo);
const account2 = Account.createAccount(idToken, clientInfo2);

Expand Down
Loading