Skip to content
This repository was archived by the owner on May 3, 2024. It is now read-only.

Commit e2584e4

Browse files
committed
updated claims change parsing and storage schema
1 parent 9cb8df7 commit e2584e4

File tree

6 files changed

+102
-39
lines changed

6 files changed

+102
-39
lines changed

2-Authorization-I/1-call-graph/README-incremental.md

+5-11
Original file line numberDiff line numberDiff line change
@@ -249,18 +249,12 @@ Once the client app receives the CAE claims challenge from Microsoft Graph, it n
249249
if (response.headers.get('www-authenticate')) {
250250
const account = msalInstance.getActiveAccount();
251251
const authenticateHeader = response.headers.get('www-authenticate');
252-
253-
const claimsChallenge = authenticateHeader
254-
.split(' ')
255-
.find((entry) => entry.includes('claims='))
256-
.split('claims="')[1]
257-
.split('",')[0];
258-
252+
const claimsChallenge = parseChallenges(authenticateHeader);
259253
/**
260254
* This method stores the claim challenge to the session storage in the browser to be used when acquiring a token.
261255
* To ensure that we are fetching the correct claim from the storage, we are using the clientId
262256
* of the application and oid (user’s object id) as the key identifier of the claim with schema
263-
* cc.<clientId>.<oid>
257+
* cc.<clientId>.<oid>.<resource.hostname>
264258
*/
265259
addClaimsToStorage(claimsChallenge, `cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}`);
266260
return { error: 'claims_challenge_occurred', payload: claimsChallenge };
@@ -280,12 +274,12 @@ After that, we require a new access token via the `useMsalAuthentication` hook,
280274
const { instance } = useMsal();
281275
const account = instance.getActiveAccount();
282276
const [graphData, setGraphData] = useState(null);
283-
277+
const resource = new URL(protectedResources.graphMe.endpoint).hostname;
284278
const request = {
285279
scopes: protectedResources.graphMe.scopes,
286280
account: account,
287-
claims: account && getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}`)
288-
? window.atob(getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}`))
281+
claims: account && getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}.${resource}`)
282+
? window.atob(getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}.${resource}`))
289283
: undefined, // e.g {"access_token":{"xms_cc":{"values":["cp1"]}}}
290284
};
291285

2-Authorization-I/1-call-graph/README.md

+55-10
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ description: This sample demonstrates a React single-page application that signs
2222
* [Explore the sample](#explore-the-sample)
2323
* [Troubleshooting](#troubleshooting)
2424
* [About the code](#about-the-code)
25+
* [How to deploy this sample to Azure](#how-to-deploy-this-sample-to-azure)
2526
* [Next Steps](#next-steps)
2627
* [Contributing](#contributing)
2728
* [Learn More](#learn-more)
@@ -343,18 +344,13 @@ Once the client app receives the CAE claims challenge from Microsoft Graph, it n
343344
if (response.headers.get('www-authenticate')) {
344345
const account = msalInstance.getActiveAccount();
345346
const authenticateHeader = response.headers.get('www-authenticate');
346-
347-
const claimsChallenge = authenticateHeader
348-
.split(' ')
349-
.find((entry) => entry.includes('claims='))
350-
.split('claims="')[1]
351-
.split('",')[0];
347+
const claimsChallenge = parseChallenges(authenticateHeader);
352348

353349
/**
354350
* This method stores the claim challenge to the session storage in the browser to be used when acquiring a token.
355351
* To ensure that we are fetching the correct claim from the storage, we are using the clientId
356352
* of the application and oid (user’s object id) as the key identifier of the claim with schema
357-
* cc.<clientId>.<oid>
353+
* cc.<clientId>.<oid><resource.hostname>
358354
*/
359355
addClaimsToStorage(claimsChallenge, `cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}`);
360356
return { error: 'claims_challenge_occurred', payload: claimsChallenge };
@@ -374,12 +370,12 @@ After that, we require a new access token via the `useMsalAuthentication` hook,
374370
const { instance } = useMsal();
375371
const account = instance.getActiveAccount();
376372
const [graphData, setGraphData] = useState(null);
377-
373+
const resource = new URL(protectedResources.graphMe.endpoint).hostname;
378374
const request = {
379375
scopes: protectedResources.graphMe.scopes,
380376
account: account,
381-
claims: account && getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}`)
382-
? window.atob(getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}`))
377+
claims: account && getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}.${resource}`)
378+
? window.atob(getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}.${resource}`))
383379
: undefined, // e.g {"access_token":{"xms_cc":{"values":["cp1"]}}}
384380
};
385381

@@ -487,6 +483,55 @@ You can use [React Router](https://reactrouter.com/) component in conjunction wi
487483
};
488484
```
489485

486+
## How to deploy this sample to Azure
487+
488+
<details>
489+
<summary>Expand the section</summary>
490+
491+
### Deploying SPA to Azure Storage
492+
493+
There is one single-page application in this sample. To deploy it to **Azure Storage**, you'll need to:
494+
495+
* create an Azure Storage blob and obtain website coordinates
496+
* build your project and upload it to Azure Storage blob
497+
* update config files with website coordinates
498+
499+
> :information_source: If you would like to use **VS Code Azure Tools** extension for deployment, [watch the tutorial](https://docs.microsoft.com/azure/developer/javascript/tutorial-vscode-static-website-node-01) offered by Microsoft Docs.
500+
501+
#### Build and upload (ms-identity-react-c2s1) to an Azure Storage blob
502+
503+
Build your project to get a distributable files folder, where your built `html`, `css` and `javascript` files will be generated. Then follow the steps below:
504+
505+
> :warning: When uploading, make sure you upload the contents of your distributable files folder and **not** the entire folder itself.
506+
507+
> :information_source: If you don't have an account already, see: [How to create a storage account](https://docs.microsoft.com/azure/storage/common/storage-account-create).
508+
509+
1. Sign in to the [Azure portal](https://portal.azure.com).
510+
1. Locate your storage account and display the account overview.
511+
1. Select **Static website** to display the configuration page for static websites.
512+
1. Select **Enabled** to enable static website hosting for the storage account.
513+
1. In the **Index document name** field, specify a default index page (For example: `index.html`).
514+
1. The default **index page** is displayed when a user navigates to the root of your static website.
515+
1. Select **Save**. The Azure portal now displays your static website endpoint. Make a note of the **Primary endpoint field**.
516+
1. In the `ms-identity-react-c2s1` project source code, update your configuration file with the **Primary endpoint field** as your new **Redirect URI** (you will register this URI later).
517+
1. Next, select **Storage Explorer**.
518+
1. Expand the **BLOB CONTAINERS** node, and then select the `$web` container.
519+
1. Choose the **Upload** button to upload files.
520+
1. If you intend for the browser to display the contents of file, make sure that the content type of that file is set to `text/html`.
521+
1. In the pane that appears beside the **account overview page** of your storage account, select **Static Website**. The URL of your site appears in the **Primary endpoint field**. In the next section, you will register this URI.
522+
523+
#### Update the Azure AD app registration for ms-identity-react-c2s1
524+
525+
1. Navigate back to to the [Azure portal](https://portal.azure.com).
526+
1. In the left-hand navigation pane, select the **Azure Active Directory** service, and then select **App registrations**.
527+
1. In the resulting screen, select `ms-identity-react-c2s1`.
528+
1. In the app's registration screen, select **Authentication** in the menu.
529+
* In the **Redirect URIs** section, update the reply URLs to match the site URL of your Azure deployment. For example:
530+
* `https://ms-identity-react-c2s1.azurewebsites.net/`
531+
* `https://ms-identity-react-c2s1.azurewebsites.net/redirect.html`
532+
533+
</details>
534+
490535
### MSAL logging
491536

492537
The Microsoft Authentication Library (MSAL) apps generate log messages to help diagnose issues. An app can configure logging with a few lines of code and have custom control over the level of detail and whether or not personal and organizational data is logged. Please check the [authConfig.js](./SPA/src//authConfig.js) file to see an example of configuring the logger with MSAL.js. For more information about using the logger with MSAL.js, see the following [Logging in MSAL.js](https://docs.microsoft.com/azure/active-directory/develop/msal-logging-js).

2-Authorization-I/1-call-graph/SPA/src/fetch.js

+13-12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { msalInstance } from './index';
77
import { msalConfig } from '../src/authConfig';
88
import { addClaimsToStorage } from './utils/storageUtils';
9+
import { parseChallenges } from './utils/claimUtils';
910

1011
/**
1112
* Makes a GET request using authorization header. For more, visit:
@@ -16,7 +17,6 @@ import { addClaimsToStorage } from './utils/storageUtils';
1617
export const fetchData = async (accessToken, apiEndpoint) => {
1718
const headers = new Headers();
1819
const bearer = `Bearer ${accessToken}`;
19-
2020
headers.append('Authorization', bearer);
2121

2222
const options = {
@@ -25,7 +25,7 @@ export const fetchData = async (accessToken, apiEndpoint) => {
2525
};
2626

2727
return fetch(apiEndpoint, options)
28-
.then((response) => handleClaimsChallenge(response))
28+
.then((response) => handleClaimsChallenge(response, apiEndpoint))
2929
.catch((error) => error);
3030
};
3131

@@ -36,32 +36,33 @@ export const fetchData = async (accessToken, apiEndpoint) => {
3636
* @param {object} response
3737
* @returns response
3838
*/
39-
const handleClaimsChallenge = async (response) => {
39+
const handleClaimsChallenge = async (response, apiEndpoint) => {
4040
if (response.status === 200) {
4141
return response.json();
4242
} else if (response.status === 401) {
4343
if (response.headers.get('www-authenticate')) {
4444
const account = msalInstance.getActiveAccount();
4545
const authenticateHeader = response.headers.get('www-authenticate');
46-
47-
const claimsChallenge = authenticateHeader
48-
.split(' ')
49-
.find((entry) => entry.includes('claims='))
50-
.split('claims="')[1]
51-
.split('",')[0];
46+
const claimsChallenge = parseChallenges(authenticateHeader);
5247

5348
/**
5449
* This method stores the claim challenge to the session storage in the browser to be used when acquiring a token.
5550
* To ensure that we are fetching the correct claim from the storage, we are using the clientId
5651
* of the application and oid (user’s object id) as the key identifier of the claim with schema
57-
* cc.<clientId>.<oid>
52+
* cc.<clientId>.<oid>.<resource.hostname>
5853
*/
59-
addClaimsToStorage(claimsChallenge, `cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}`);
60-
return { error: 'claims_challenge_occurred', payload: claimsChallenge };
54+
addClaimsToStorage(
55+
claimsChallenge.claims,
56+
`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}.${new URL(apiEndpoint).hostname}`
57+
);
58+
return { error: 'claims_challenge_occurred', payload: claimsChallenge.claims };
6159
}
6260

6361
throw new Error(`Unauthorized: ${response.status}`);
6462
} else {
6563
throw new Error(`Something went wrong with the request: ${response.status}`);
6664
}
6765
};
66+
67+
68+

2-Authorization-I/1-call-graph/SPA/src/pages/Contacts.jsx

+5-3
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ export const Contacts = () => {
1111
const { instance } = useMsal();
1212
const account = instance.getActiveAccount();
1313
const [graphData, setGraphData] = useState(null);
14-
14+
const resource = new URL(protectedResources.graphContacts.endpoint).hostname;
1515
const request = {
1616
scopes: protectedResources.graphContacts.scopes,
1717
account: account,
1818
claims:
19-
account && getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}`)
20-
? window.atob(getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}`))
19+
account && getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}.${resource}`)
20+
? window.atob(
21+
getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}.${resource}`)
22+
)
2123
: undefined, // e.g {"access_token":{"xms_cc":{"values":["cp1"]}}}
2224
};
2325

2-Authorization-I/1-call-graph/SPA/src/pages/Profile.jsx

+5-3
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ export const Profile = () => {
1111
const { instance } = useMsal();
1212
const account = instance.getActiveAccount();
1313
const [graphData, setGraphData] = useState(null);
14-
14+
const resource = new URL(protectedResources.graphMe.endpoint).hostname;
1515
const request = {
1616
scopes: protectedResources.graphMe.scopes,
1717
account: account,
1818
claims:
19-
account && getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}`)
20-
? window.atob(getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}`))
19+
account && getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}.${resource}`)
20+
? window.atob(
21+
getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}.${resource}`)
22+
)
2123
: undefined, // e.g {"access_token":{"xms_cc":{"values":["cp1"]}}}
2224
};
2325

2-Authorization-I/1-call-graph/SPA/src/utils/claimUtils.js

+19
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,22 @@ const changeDateFormat = (date) => {
222222
};
223223

224224

225+
/**
226+
* This method parses WWW-Authenticate authentication headers
227+
* @param header
228+
* @return {Object} challengeMap
229+
*/
230+
export const parseChallenges = (header) => {
231+
const schemeSeparator = header.indexOf(' ');
232+
const challenges = header.substring(schemeSeparator + 1).split(',');
233+
const challengeMap = {};
234+
235+
challenges.forEach((challenge) => {
236+
const [key, value] = challenge.split('=');
237+
challengeMap[key.trim()] = window.decodeURI(value.replace(/['"]+/g, ''));
238+
});
239+
240+
return challengeMap;
241+
}
242+
243+

0 commit comments

Comments
 (0)