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

Commit 20ce9d1

Browse files
committed
updated readme
1 parent d634271 commit 20ce9d1

File tree

2 files changed

+305
-111
lines changed

2 files changed

+305
-111
lines changed

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

Lines changed: 153 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ This sample app declares that it's CAE-capable by adding the `clientCapabilities
295295

296296
#### Processing the CAE challenge from Microsoft Graph
297297

298-
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 `acquireTokenRedirect` API and provide the claims challenge as a parameter in the token request. This is shown in [graph.service.ts](../SPA/src/app/graph.service.ts), where we handle the response from the Microsoft Graph API with the `handleClaimsChallenge` method:
298+
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 `acquireToken` API and provide the claims challenge as a parameter in the token request. This is shown in [graph.service.ts](../SPA/src/app/graph.service.ts), where we handle the response from the Microsoft Graph API with the `handleClaimsChallenge` method:
299299

300300
```typescript
301301
/**
@@ -304,20 +304,19 @@ Once the client app receives the CAE claims challenge from Microsoft Graph, it n
304304
* For more information, visit: https://docs.microsoft.com/en-us/azure/active-directory/develop/claims-challenge#claims-challenge-header-format
305305
* @param response
306306
*/
307-
handleClaimsChallenge(response: any, endpoint: string): void {
307+
handleClaimsChallenge(response: any, providerOptions: ProviderOptions): void {
308308
const authenticateHeader: string = response.headers.get('www-authenticate');
309-
310309
const claimsChallengeMap: any = this.parseChallenges(authenticateHeader);
311-
312310
let account: AccountInfo = this.authService.instance.getActiveAccount()!;
313311
addClaimsToStorage(
314312
claimsChallengeMap.claims,
315313
`cc.${msalConfig.auth.clientId}.${account?.idTokenClaims?.oid}.${
316-
new URL(endpoint).hostname
314+
new URL(providerOptions.endpoint).hostname
317315
}`
318316
);
319-
320-
this.getAccessTokenInteractively(endpoint);
317+
318+
new MsalAuthenticationProvider(providerOptions, this.authService).getAccessToken()
319+
321320
}
322321

323322

@@ -341,36 +340,7 @@ Once the client app receives the CAE claims challenge from Microsoft Graph, it n
341340

342341
```
343342

344-
After that, we require a new access token via the `acquireTokenRedirect` API, fetch the claims challenge from the browser's localStorage, and pass it to the `acquireTokenRedirect` hook in the request parameter.
345-
346-
```typescript
347-
getAccessTokenInteractively(endpoint: string): void {
348-
this.authService.instance.acquireTokenRedirect({
349-
account: this.authService.instance.getActiveAccount()!,
350-
scopes:
351-
Object.values(protectedResources).find(
352-
(resource: { endpoint: string; scopes: string[] }) =>
353-
resource.endpoint === endpoint
354-
)?.scopes || [],
355-
claims:
356-
this.authService.instance.getActiveAccount()! &&
357-
getClaimsFromStorage(
358-
`cc.${msalConfig.auth.clientId}.${
359-
this.authService.instance.getActiveAccount()?.idTokenClaims?.oid
360-
}.${new URL(endpoint).hostname}`
361-
)
362-
? window.atob(
363-
getClaimsFromStorage(
364-
`cc.${msalConfig.auth.clientId}.${
365-
this.authService.instance.getActiveAccount()?.idTokenClaims
366-
?.oid
367-
}.${new URL(endpoint).hostname}`
368-
)
369-
)
370-
: undefined,
371-
});
372-
}
373-
```
343+
After that, we require a new access token via the `MsalAuthenticationProvider` Class, fetch the claims challenge from the browser's localStorage, and pass it to the `acquireToken` API in the request parameter. This is shown in [graph.service.ts](../SPA/src/app/graph.service.ts)
374344

375345
### Working with multiple resources
376346

@@ -432,35 +402,162 @@ Clients should treat access tokens as opaque strings, as the contents of the tok
432402

433403
### Calling the Microsoft Graph API
434404

435-
Using the httpClient , 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):
405+
[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 [clientOptions](https://github.com/microsoftgraph/msgraph-sdk-javascript/blob/dev/docs/CreatingClientInstance.md) method, which contains an `authProvider` object of class **MyAuthenticationProvider** that handles the token acquisition process for the client. We offer this as a service to other components as shown below:
436406

437407
```typescript
438-
/**
439-
* Makes a GET request using authorization header For more, visit:
440-
* https://tools.ietf.org/html/rfc6750
441-
* @param endpoint
442-
* @returns
408+
export class GraphService {
409+
constructor(private authService: MsalService) { }
410+
getGraphClient = (providerOptions: ProviderOptions) => {
411+
/**
412+
* Pass the instance as authProvider in ClientOptions to instantiate the Client which will create and set the default middleware chain.
413+
* For more information, visit: https://github.com/microsoftgraph/msgraph-sdk-javascript/blob/dev/docs/CreatingClientInstance.md
414+
*/
415+
let clientOptions = {
416+
authProvider: new MyAuthenticationProvider(providerOptions, this.authService),
417+
};
418+
const graphClient = Client.initWithMiddleware(clientOptions);
419+
return graphClient;
420+
}
421+
}
422+
```
423+
424+
**MyAuthenticationProvider** class needs to implement the [IAuthenticationProvider](https://github.com/microsoftgraph/msgraph-sdk-javascript/blob/dev/src/IAuthenticationProvider.ts) interface, which can be done as shown below:
425+
426+
```typescript
427+
/**
428+
* This method will get called before every request to the ms graph server
429+
* This should return a Promise that resolves to an accessToken (in case of success) or rejects with error (in case of failure)
430+
* Basically this method will contain the implementation for getting and refreshing accessTokens
443431
*/
444-
getData(endpoint: string): Promise<any> {
445-
return new Promise((resolve, reject) => {
446-
this.http.get(endpoint).subscribe( {
447-
next: (response) => {
448-
resolve(response);
449-
},
450-
error: (error) => {
451-
if (error.status === 401) {
452-
if (error.headers.get('www-authenticate')) {
453-
this.handleClaimsChallenge(error, endpoint);
454-
}
432+
getAccessToken(): Promise<string> {
433+
return new Promise(async (resolve, reject) => {
434+
let response: AuthenticationResult;
435+
let resource = new URL(this.endpoint).hostname;
436+
try {
437+
response = await this.authService.instance.acquireTokenSilent({
438+
account: this.account,
439+
scopes: this.scopes,
440+
claims:
441+
this.authService.instance.getActiveAccount()! &&
442+
getClaimsFromStorage(
443+
`cc.${msalConfig.auth.clientId}.${
444+
this.authService.instance.getActiveAccount()?.idTokenClaims?.oid
445+
}.${resource}`
446+
)
447+
? window.atob(
448+
getClaimsFromStorage(
449+
`cc.${msalConfig.auth.clientId}.${
450+
this.authService.instance.getActiveAccount()
451+
?.idTokenClaims?.oid
452+
}.${resource}`
453+
)
454+
)
455+
: undefined,
456+
});
457+
458+
if (response.accessToken) {
459+
resolve(response.accessToken);
460+
} else {
461+
reject(Error('Failed to acquire an access token'));
462+
}
463+
} catch (error) {
464+
// in case if silent token acquisition fails, fallback to an interactive method
465+
if (error instanceof InteractionRequiredAuthError) {
466+
switch (this.interactionType) {
467+
case InteractionType.Popup:
468+
response = await this.authService.instance.acquireTokenPopup({
469+
scopes: this.scopes,
470+
claims:
471+
this.authService.instance.getActiveAccount()! &&
472+
getClaimsFromStorage(
473+
`cc.${msalConfig.auth.clientId}.${
474+
this.authService.instance.getActiveAccount()
475+
?.idTokenClaims?.oid
476+
}.${resource}`
477+
)
478+
? window.atob(
479+
getClaimsFromStorage(
480+
`cc.${msalConfig.auth.clientId}.${
481+
this.authService.instance.getActiveAccount()
482+
?.idTokenClaims?.oid
483+
}.${resource}`
484+
)
485+
)
486+
: undefined,
487+
});
488+
489+
if (response.accessToken) {
490+
resolve(response.accessToken);
491+
} else {
492+
reject(Error('Failed to acquire an access token'));
493+
}
494+
495+
break;
496+
497+
case InteractionType.Redirect:
498+
/**
499+
* This will cause the app to leave the current page and redirect to the consent screen.
500+
* Once consent is provided, the app will return back to the current page and then the
501+
* silent token acquisition will succeed.
502+
*/
503+
this.authService.instance.acquireTokenRedirect({
504+
scopes: this.scopes,
505+
claims:
506+
this.authService.instance.getActiveAccount()! &&
507+
getClaimsFromStorage(
508+
`cc.${msalConfig.auth.clientId}.${
509+
this.authService.instance.getActiveAccount()
510+
?.idTokenClaims?.oid
511+
}.${resource}`
512+
)
513+
? window.atob(
514+
getClaimsFromStorage(
515+
`cc.${msalConfig.auth.clientId}.${
516+
this.authService.instance.getActiveAccount()
517+
?.idTokenClaims?.oid
518+
}.${resource}`
519+
)
520+
)
521+
: undefined,
522+
});
523+
break;
524+
525+
default:
526+
break;
455527
}
456-
reject(error);
457528
}
458529
}
459-
);
460530
});
461531
}
462532
```
463533

534+
See [graph.service.ts](./SPA/src/app/graph.service.ts). The Graph client then can be used in your components as shown below:
535+
536+
```typescript
537+
getProfile(providerOptions: ProviderOptions) {
538+
this.graphService
539+
.getGraphClient(providerOptions)
540+
.api('/me')
541+
.responseType(ResponseType.RAW)
542+
.get()
543+
.then((response: any) => {
544+
if (response.status === 200) return response.json();
545+
if (response.status === 401) {
546+
if (response.headers.get('www-authenticate')) {
547+
this.graphService.handleClaimsChallenge(response, providerOptions);
548+
}
549+
}
550+
})
551+
.then((profileResponse: Profile) => {
552+
// do something with response
553+
})
554+
.catch((error: any) => {
555+
// do something with response
556+
});
557+
}
558+
```
559+
560+
464561
## Next Steps
465562

466563
Learn how to:

0 commit comments

Comments
 (0)