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

Commit 9ea5742

Browse files
committed
2 parents caabe42 + c52878a commit 9ea5742

File tree

119 files changed

+24453
-39408
lines changed

Some content is hidden

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

119 files changed

+24453
-39408
lines changed

.github/ISSUE_TEMPLATE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
- [ ] 5-1) Call a web API using App Roles
2020
- [ ] 5-2) Call a web API using Security Groups
2121
- [ ] 6-1) Call Microsoft Graph using on-behalf-of flow
22-
- [ ] 6-2) Call a web API using Proof of Possession tokens
2322
- [ ] 6-3) Call a web API using Conditional Access Auth Context
23+
- [ ] 6-4) Sign-in with Hybrid SPA flow
2424
```
2525

2626
## This issue is for a

.github/workflows/node.js.yml

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -121,18 +121,6 @@ jobs:
121121
npm audit --production
122122
npm run test --no-watch
123123
124-
- run: |
125-
cd 6-AdvancedScenarios/2-call-api-pop/SPA
126-
npm ci
127-
npm audit --production
128-
npm run test --no-watch
129-
130-
- run: |
131-
cd 6-AdvancedScenarios/2-call-api-pop/API
132-
npm ci
133-
npm audit --production
134-
npm run test
135-
136124
- run: |
137125
cd 6-AdvancedScenarios/3-call-api-acrs/SPA
138126
npm ci

1-Authentication/1-sign-in/SPA/package-lock.json

Lines changed: 2407 additions & 2703 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 88 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ In this chapter we will extend our React single-page application (SPA) by making
3131
|-------------------------------------|----------------------------------------------------------------------------|
3232
| `App.jsx` | Main application logic resides here. |
3333
| `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. |
3435
| `authConfig.js` | Contains authentication configuration parameters. |
3536
| `pages/Home.jsx` | Contains a table with ID token claims and description |
3637
| `pages/Profile.jsx` | Calls Microsoft Graph `/me` endpoint with Graph SDK. |
@@ -270,57 +271,65 @@ Once the client app receives the CAE claims challenge from Microsoft Graph, it n
270271
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.
271272

272273
```javascript
273-
export const Profile = () => {
274-
const { instance } = useMsal();
275-
const account = instance.getActiveAccount();
276-
const [graphData, setGraphData] = useState(null);
277-
const resource = new URL(protectedResources.graphMe.endpoint).hostname;
278-
const request = {
279-
scopes: protectedResources.graphMe.scopes,
280-
account: account,
281-
claims: account && getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}.${resource}`)
282-
? window.atob(getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}.${resource}`))
283-
: undefined, // e.g {"access_token":{"xms_cc":{"values":["cp1"]}}}
284-
};
285-
286-
const { login, result, error } = useMsalAuthentication(InteractionType.Popup, request);
287-
288-
useEffect(() => {
289-
if (!!graphData) {
290-
return;
291-
}
274+
export const Profile = () => {
275+
const { instance } = useMsal();
276+
const account = instance.getActiveAccount();
277+
const [graphData, setGraphData] = useState(null);
278+
const resource = new URL(protectedResources.graphMe.endpoint).hostname;
279+
const claims =
280+
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,
287+
account: account,
288+
claims: claims,
289+
};
292290

293-
if (!!error) {
294-
// in case popup is blocked, use redirect instead
295-
if (error.errorCode === "popup_window_error" || error.errorCode === "empty_window_error") {
296-
login(InteractionType.Redirect, request);
297-
}
291+
const { login, result, error } = useMsalAuthentication(InteractionType.Popup, request);
292+
useEffect(() => {
293+
if (!!graphData) {
294+
return;
295+
}
298296

299-
console.log(error);
300-
return;
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);
301301
}
302302

303-
if (result) {
304-
fetchData(result.accessToken, protectedResources.graphMe.endpoint)
305-
.then((response) => {
306-
if (response && response.error) throw response.error;
307-
setGraphData(response);
308-
}).catch((error) => {
309-
if (error === 'claims_challenge_occurred') {
310-
login(InteractionType.Redirect, request);
311-
}
312-
313-
console.log(error);
314-
});
315-
}
316-
}, [graphData, result, error, login]);
303+
console.log(error);
304+
return;
305+
}
317306

318-
return (
319-
<>
320-
{graphData ? <ProfileData response={result} graphData={graphData} /> : null}
321-
</>
322-
);
323-
};
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+
});
325+
}
326+
}, [graphData, result, error, login]);
327+
328+
if (error) {
329+
return <div>Error: {error.message}</div>;
330+
}
331+
return <>{graphData ? <ProfileData response={result} graphData={graphData} /> : null}</>;
332+
};
324333
```
325334

326335
### Access token validation
@@ -331,24 +340,44 @@ For more details on what's inside the access token, clients should use the token
331340

332341
### Calling the Microsoft Graph API
333342

334-
Using the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), simply add the `Authorization` header to your request, followed by the **access token** you have obtained previously for this resource/endpoint (as a [bearer token](https://tools.ietf.org/html/rfc6750)):
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
335344

336345
```javascript
337-
export const callApiWithToken = async (accessToken, apiEndpoint) => {
338-
const headers = new Headers();
339-
const bearer = `Bearer ${accessToken}`;
346+
export const getGraphClient = (accessToken) => {
347+
// Initialize Graph client
348+
const graphClient = Client.init({
349+
// Use the provided access token to authenticate requests
350+
authProvider: (done) => {
351+
done(null, accessToken);
352+
},
353+
});
340354

341-
headers.append('Authorization', bearer);
355+
return graphClient;
356+
};
357+
```
342358

343-
const options = {
344-
method: 'GET',
345-
headers: headers,
346-
};
359+
See [graph.js](./SPA/src/graph.js). The Graph client then can be used in your components as shown below:
347360

348-
return fetch(apiEndpoint, options)
349-
.then((response) => handleClaimsChallenge(response))
350-
.catch((error) => error);
351-
};
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+
}
352381
```
353382

354383
### Working with React routes

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

Lines changed: 88 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Here you'll learn how to [sign-in](https://docs.microsoft.com/azure/active-direc
4848
|-------------------------------------|----------------------------------------------------------------------------|
4949
| `App.jsx` | Main application logic resides here. |
5050
| `fetch.jsx` | Provides a helper method for making fetch calls using bearer token scheme. |
51+
| `graph.jsx` | Instantiates Graph SDK client using a custom authentication provider. |
5152
| `authConfig.js` | Contains authentication configuration parameters. |
5253
| `pages/Home.jsx` | Contains a table with ID token claims and description |
5354
| `pages/Profile.jsx` | Calls Microsoft Graph `/me` endpoint with Graph SDK. |
@@ -365,57 +366,65 @@ Once the client app receives the CAE claims challenge from Microsoft Graph, it n
365366
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.
366367

367368
```javascript
368-
export const Profile = () => {
369-
const { instance } = useMsal();
370-
const account = instance.getActiveAccount();
371-
const [graphData, setGraphData] = useState(null);
372-
const resource = new URL(protectedResources.graphMe.endpoint).hostname;
373-
const request = {
374-
scopes: protectedResources.graphMe.scopes,
375-
account: account,
376-
claims: account && getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}.${resource}`)
377-
? window.atob(getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}.${resource}`))
378-
: undefined, // e.g {"access_token":{"xms_cc":{"values":["cp1"]}}}
379-
};
380-
381-
const { login, result, error } = useMsalAuthentication(InteractionType.Popup, request);
382-
383-
useEffect(() => {
384-
if (!!graphData) {
385-
return;
386-
}
369+
export const Profile = () => {
370+
const { instance } = useMsal();
371+
const account = instance.getActiveAccount();
372+
const [graphData, setGraphData] = useState(null);
373+
const resource = new URL(protectedResources.graphMe.endpoint).hostname;
374+
const claims =
375+
account && getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}.${resource}`)
376+
? window.atob(
377+
getClaimsFromStorage(`cc.${msalConfig.auth.clientId}.${account.idTokenClaims.oid}.${resource}`)
378+
)
379+
: undefined; // e.g {"access_token":{"xms_cc":{"values":["cp1"]}}}
380+
const request = {
381+
scopes: protectedResources.graphMe.scopes,
382+
account: account,
383+
claims: claims,
384+
};
387385

388-
if (!!error) {
389-
// in case popup is blocked, use redirect instead
390-
if (error.errorCode === "popup_window_error" || error.errorCode === "empty_window_error") {
391-
login(InteractionType.Redirect, request);
392-
}
386+
const { login, result, error } = useMsalAuthentication(InteractionType.Popup, request);
387+
useEffect(() => {
388+
if (!!graphData) {
389+
return;
390+
}
393391

394-
console.log(error);
395-
return;
392+
if (!!error) {
393+
// in case popup is blocked, use redirect instead
394+
if (error.errorCode === 'popup_window_error' || error.errorCode === 'empty_window_error') {
395+
login(InteractionType.Redirect, request);
396396
}
397397

398-
if (result) {
399-
fetchData(result.accessToken, protectedResources.graphMe.endpoint)
400-
.then((response) => {
401-
if (response && response.error) throw response.error;
402-
setGraphData(response);
403-
}).catch((error) => {
404-
if (error === 'claims_challenge_occurred') {
405-
login(InteractionType.Redirect, request);
406-
}
407-
408-
console.log(error);
409-
});
410-
}
411-
}, [graphData, result, error, login]);
398+
console.log(error);
399+
return;
400+
}
412401

413-
return (
414-
<>
415-
{graphData ? <ProfileData response={result} graphData={graphData} /> : null}
416-
</>
417-
);
418-
};
402+
if (result) {
403+
getGraphClient(result.accessToken)
404+
.api('/me')
405+
.responseType(ResponseType.RAW)
406+
.get()
407+
.then((response) => {
408+
return handleClaimsChallenge(response, protectedResources.graphMe.endpoint);
409+
})
410+
.then((response) => {
411+
if (response && response.error === 'claims_challenge_occurred') throw response.error;
412+
setGraphData(response);
413+
})
414+
.catch((error) => {
415+
if (error === 'claims_challenge_occurred') {
416+
login(InteractionType.Redirect, request);
417+
}
418+
console.log(error);
419+
});
420+
}
421+
}, [graphData, result, error, login]);
422+
423+
if (error) {
424+
return <div>Error: {error.message}</div>;
425+
}
426+
return <>{graphData ? <ProfileData response={result} graphData={graphData} /> : null}</>;
427+
};
419428
```
420429

421430
### Access token validation
@@ -426,24 +435,44 @@ For more details on what's inside the access token, clients should use the token
426435

427436
### Calling the Microsoft Graph API
428437

429-
Using the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), simply add the `Authorization` header to your request, followed by the **access token** you have obtained previously for this resource/endpoint (as a [bearer token](https://tools.ietf.org/html/rfc6750)):
438+
[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.
430439

431440
```javascript
432-
export const callApiWithToken = async (accessToken, apiEndpoint) => {
433-
const headers = new Headers();
434-
const bearer = `Bearer ${accessToken}`;
441+
export const getGraphClient = (accessToken) => {
442+
// Initialize Graph client
443+
const graphClient = Client.init({
444+
// Use the provided access token to authenticate requests
445+
authProvider: (done) => {
446+
done(null, accessToken);
447+
},
448+
});
435449

436-
headers.append('Authorization', bearer);
450+
return graphClient;
451+
};
452+
```
437453

438-
const options = {
439-
method: 'GET',
440-
headers: headers,
441-
};
454+
See [graph.js](./SPA/src/graph.js). The Graph client then can be used in your components as shown below:
442455

443-
return fetch(apiEndpoint, options)
444-
.then((response) => handleClaimsChallenge(response))
445-
.catch((error) => error);
446-
};
456+
```javascript
457+
if (result) {
458+
getGraphClient(result.accessToken)
459+
.api('/me')
460+
.responseType(ResponseType.RAW)
461+
.get()
462+
.then((response) => {
463+
return handleClaimsChallenge(response, protectedResources.graphMe.endpoint);
464+
})
465+
.then((response) => {
466+
if (response && response.error === 'claims_challenge_occurred') throw response.error;
467+
setGraphData(response);
468+
})
469+
.catch((error) => {
470+
if (error === 'claims_challenge_occurred') {
471+
login(InteractionType.Redirect, request);
472+
}
473+
console.log(error);
474+
});
475+
}
447476
```
448477

449478
### Working with React routes

0 commit comments

Comments
 (0)