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

Commit c021cf0

Browse files
authored
Merge pull request #262 from Azure-Samples/refactor-component
Refactoring CAE code in the sample
2 parents dc897ef + d381bab commit c021cf0

File tree

7 files changed

+261
-376
lines changed

7 files changed

+261
-376
lines changed

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

+56-105
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ In this chapter we will extend our React single-page application (SPA) by making
2929

3030
| File/folder | Description |
3131
|-------------------------------------|----------------------------------------------------------------------------|
32-
| `App.jsx` | Main application logic resides here. |
33-
| `fetch.jsx` | Provides a helper method for making fetch calls using bearer token scheme. |
34-
| `graph.jsx` | Instantiates Graph SDK client using a custom authentication provider. |
35-
| `authConfig.js` | Contains authentication configuration parameters. |
36-
| `pages/Home.jsx` | Contains a table with ID token claims and description |
37-
| `pages/Profile.jsx` | Calls Microsoft Graph `/me` endpoint with Graph SDK. |
38-
| `pages/Contacts.jsx` | Calls Microsoft Graph `/me/contacts` endpoint with Graph SDK. |
39-
| `components/AccountPicker.jsx` | Contains logic to handle multiple `account` selection with MSAL.js |
32+
| `App.jsx` | Main application logic resides here. |
33+
| `graph.jsx` | Instantiates Graph SDK client using MSAL as authentication provider. |
34+
| `authConfig.js` | Contains authentication configuration parameters. |
35+
| `pages/Home.jsx` | Contains a table with ID token claims and description |
36+
| `pages/Profile.jsx` | Calls Microsoft Graph `/me` by executing `useGraphWithMsal` custom hook. |
37+
| `pages/Contacts.jsx` | Calls Microsoft Graph `/me/contacts` by executing `useGraphWithMsal` custom hook. |
38+
| `components/AccountPicker.jsx` | Contains logic to handle multiple `account` selection with MSAL.js |
39+
| `hooks/useGraphWithMsal.jsx` | Contains token acquisition logic to call Microsoft Graph endpoints with Graph SDK. |
4040

4141
## Setup the sample
4242

@@ -240,95 +240,68 @@ This sample app declares that it's CAE-capable by adding the `clientCapabilities
240240

241241
#### Processing the CAE challenge from Microsoft Graph
242242

243-
Once the client app receives the CAE claims challenge from Microsoft Graph, it needs to present the user with a prompt for satisfying the challenge via Azure AD authorization endpoint. To do so, we use MSAL's `useMsalAuthentication` hook and provide the claims challenge as a parameter in the token request. This is shown in [fetch.js](./SPA/src/fetch.js), where we handle the response from the Microsoft Graph API with the `handleClaimsChallenge` method:
243+
Once the client app receives the CAE claims challenge from Microsoft Graph, it needs to present the user with a prompt for satisfying the challenge via Azure AD authorization endpoint. To do so, we use MSAL's `useMsalAuthentication` hook and provide the claims challenge as a parameter in the token request. This is shown in the [useGraphWithMsal](./SPA/src/hooks/useGraphWithMsal.jsx) custom hook:
244244

245245
```javascript
246-
const handleClaimsChallenge = async (response) => {
247-
if (response.status === 200) {
248-
return response.json();
249-
} else if (response.status === 401) {
250-
if (response.headers.get('www-authenticate')) {
251-
const account = msalInstance.getActiveAccount();
252-
const authenticateHeader = response.headers.get('www-authenticate');
253-
const claimsChallenge = parseChallenges(authenticateHeader);
254-
/**
255-
* This method stores the claim challenge to the session storage in the browser to be used when acquiring a token.
256-
* To ensure that we are fetching the correct claim from the storage, we are using the clientId
257-
* of the application and oid (user’s object id) as the key identifier of the claim with schema
258-
* cc.<clientId>.<oid>.<resource.hostname>
259-
*/
260-
addClaimsToStorage(claimsChallenge, `cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}`);
261-
return { error: 'claims_challenge_occurred', payload: claimsChallenge };
262-
}
263-
264-
throw new Error(`Unauthorized: ${response.status}`);
265-
} else {
266-
throw new Error(`Something went wrong with the request: ${response.status}`);
267-
}
268-
};
269-
```
270-
271-
After that, we require a new access token via the `useMsalAuthentication` hook, fetch the claims challenge from the browser's localStorage, and pass it to the `useMsalAuthentication` hook in the request parameter.
272-
273-
```javascript
274-
export const Profile = () => {
246+
const useGraphWithMsal = (request, endpoint) => {
247+
const [error, setError] = useState(null);
275248
const { instance } = useMsal();
249+
276250
const account = instance.getActiveAccount();
277-
const [graphData, setGraphData] = useState(null);
278-
const resource = new URL(protectedResources.graphMe.endpoint).hostname;
251+
const resource = new URL(endpoint).hostname;
252+
279253
const claims =
280254
account && getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}.${resource}`)
281-
? window.atob(
282-
getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}.${resource}`)
283-
)
284-
: undefined; // e.g {"access_token":{"xms_cc":{"values":["cp1"]}}}
285-
const request = {
286-
scopes: protectedResources.graphMe.scopes,
255+
?
256+
window.atob(getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}.${resource}`))
257+
:
258+
undefined; // e.g {"access_token":{"xms_cc":{"values":["cp1"]}}}
259+
260+
const { result, login, error: msalError } = useMsalAuthentication(InteractionType.Popup, {
261+
...request,
262+
redirectUri: '/redirect.html',
287263
account: account,
288264
claims: claims,
289-
};
265+
});
290266

291-
const { login, result, error } = useMsalAuthentication(InteractionType.Popup, request);
292-
useEffect(() => {
293-
if (!!graphData) {
267+
/**
268+
* Execute a fetch request with Graph SDK
269+
* @param {String} endpoint
270+
* @returns JSON response
271+
*/
272+
const execute = async (endpoint) => {
273+
if (msalError) {
274+
setError(msalError);
294275
return;
295276
}
296277

297-
if (!!error) {
298-
// in case popup is blocked, use redirect instead
299-
if (error.errorCode === 'popup_window_error' || error.errorCode === 'empty_window_error') {
300-
login(InteractionType.Redirect, request);
301-
}
278+
if (result) {
279+
let accessToken = result.accessToken;
302280

303-
console.log(error);
304-
return;
305-
}
281+
try {
282+
const graphResponse = await getGraphClient(accessToken)
283+
.api(endpoint)
284+
.responseType(ResponseType.RAW)
285+
.get();
306286

307-
if (result) {
308-
getGraphClient(result.accessToken)
309-
.api('/me')
310-
.responseType(ResponseType.RAW)
311-
.get()
312-
.then((response) => {
313-
return handleClaimsChallenge(response, protectedResources.graphMe.endpoint);
314-
})
315-
.then((response) => {
316-
if (response && response.error === 'claims_challenge_occurred') throw response.error;
317-
setGraphData(response);
318-
})
319-
.catch((error) => {
320-
if (error === 'claims_challenge_occurred') {
321-
login(InteractionType.Redirect, request);
322-
}
323-
console.log(error);
324-
});
287+
const responseHasClaimsChallenge = await handleClaimsChallenge(graphResponse, endpoint, account);
288+
return responseHasClaimsChallenge;
289+
290+
} catch (error) {
291+
if (error.name === 'ClaimsChallengeAuthError') {
292+
login(InteractionType.Redirect, request);
293+
} else {
294+
setError(error);
295+
}
296+
}
325297
}
326-
}, [graphData, result, error, login]);
298+
};
327299

328-
if (error) {
329-
return <div>Error: {error.message}</div>;
330-
}
331-
return <>{graphData ? <ProfileData response={result} graphData={graphData} /> : null}</>;
300+
return {
301+
error,
302+
result: result,
303+
execute: useCallback(execute, [result, msalError]),
304+
};
332305
};
333306
```
334307

@@ -340,7 +313,7 @@ For more details on what's inside the access token, clients should use the token
340313

341314
### Calling the Microsoft Graph API
342315

343-
[Microsoft Graph JavaScript SDK](https://github.com/microsoftgraph/msgraph-sdk-javascript) provides various utility methods to query the Graph API. While the SDK has a default authentication provider that can be used in basic scenarios, it can also be extended to use with a custom authentication provider such as MSAL. To do so, we will initialize the Graph SDK client with an [authProvider function](https://github.com/microsoftgraph/msgraph-sdk-javascript/blob/dev/docs/CreatingClientInstance.md#2-create-with-options). In this case, user has to provide their own implementation for getting and refreshing accessToken. A callback will be passed into this `authProvider` function, accessToken or error needs to be passed in to that callback
316+
[Microsoft Graph JavaScript SDK](https://github.com/microsoftgraph/msgraph-sdk-javascript) provides various utility methods to query the Graph API. While the SDK has a default authentication provider that can be used in basic scenarios, it can also be extended to use with a custom authentication provider such as MSAL. To do so, we will initialize the Graph SDK client with an [authProvider function](https://github.com/microsoftgraph/msgraph-sdk-javascript/blob/dev/docs/CreatingClientInstance.md#2-create-with-options). In this case, user has to provide their own implementation for getting and refreshing access tokens.
344317

345318
```javascript
346319
export const getGraphClient = (accessToken) => {
@@ -356,29 +329,7 @@ For more details on what's inside the access token, clients should use the token
356329
};
357330
```
358331

359-
See [graph.js](./SPA/src/graph.js). The Graph client then can be used in your components as shown below:
360-
361-
```javascript
362-
if (result) {
363-
getGraphClient(result.accessToken)
364-
.api('/me')
365-
.responseType(ResponseType.RAW)
366-
.get()
367-
.then((response) => {
368-
return handleClaimsChallenge(response, protectedResources.graphMe.endpoint);
369-
})
370-
.then((response) => {
371-
if (response && response.error === 'claims_challenge_occurred') throw response.error;
372-
setGraphData(response);
373-
})
374-
.catch((error) => {
375-
if (error === 'claims_challenge_occurred') {
376-
login(InteractionType.Redirect, request);
377-
}
378-
console.log(error);
379-
});
380-
}
381-
```
332+
The Graph client then can be used in your application as shown in the [useGraphWithMsal](./SPA/src/hooks/useGraphWithMsal.jsx) custom hook.
382333

383334
### Working with React routes
384335

0 commit comments

Comments
 (0)