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

Commit a6f49be

Browse files
committed
review changes
1 parent 92571b8 commit a6f49be

File tree

10 files changed

+81
-94
lines changed

10 files changed

+81
-94
lines changed

6-AdvancedScenarios/1-call-api-obo/API/MsalOnBehalfOfClient.js renamed to 6-AdvancedScenarios/1-call-api-obo/API/auth/onBehalfOfClient.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const msal = require('@azure/msal-node');
2-
const config = require('./authConfig');
2+
const config = require('../authConfig');
33

44
const msalConfig = {
55
auth: {
@@ -25,7 +25,7 @@ const cca = new msal.ConfidentialClientApplication(msalConfig);
2525
const getOboToken = async (oboAssertion) => {
2626
const oboRequest = {
2727
oboAssertion: oboAssertion,
28-
scopes: config.resources.downstreamAPI.scopes,
28+
scopes: config.protectedResources.graphApi.scopes
2929
};
3030

3131
try {
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,49 @@
1+
/**
2+
* Indicates whether the access token was issued to a user or an application.
3+
* @param {Object} accessTokenPayload
4+
* @returns {boolean}
5+
*/
6+
const isAppOnlyToken = (accessTokenPayload) => {
7+
/**
8+
* An access token issued by Azure AD will have at least one of the two claims. Access tokens
9+
* issued to a user will have the 'scp' claim. Access tokens issued to an application will have
10+
* the roles claim. Access tokens that contain both claims are issued only to users, where the scp
11+
* claim designates the delegated permissions, while the roles claim designates the user's role.
12+
*
13+
* To determine whether an access token was issued to a user (i.e delegated) or an application
14+
* more easily, we recommend enabling the optional claim 'idtyp'. For more information, see:
15+
* https://docs.microsoft.com/azure/active-directory/develop/access-tokens#user-and-application-tokens
16+
*/
17+
if (!accessTokenPayload.hasOwnProperty('idtyp')) {
18+
if (accessTokenPayload.hasOwnProperty('scp')) {
19+
return false;
20+
} else if (!accessTokenPayload.hasOwnProperty('scp') && accessTokenPayload.hasOwnProperty('roles')) {
21+
return true;
22+
}
23+
}
24+
25+
return accessTokenPayload.idtyp === 'app';
26+
};
27+
128
/**
229
* Ensures that the access token has the specified delegated permissions.
330
* @param {Object} accessTokenPayload: Parsed access token payload
431
* @param {Array} requiredPermission: list of required permissions
532
* @returns {boolean}
633
*/
7-
const hasRequiredDelegatedPermissions = (accessTokenPayload, requiredPermission) => {
34+
const hasRequiredDelegatedPermissions = (accessTokenPayload, requiredPermission) => {
835
const normalizedRequiredPermissions = requiredPermission.map(permission => permission.toUpperCase());
936

1037
if (accessTokenPayload.hasOwnProperty('scp') && accessTokenPayload.scp.split(' ')
1138
.some(claim => normalizedRequiredPermissions.includes(claim.toUpperCase()))) {
39+
1240
return true;
1341
}
1442

1543
return false;
1644
}
1745

1846
module.exports = {
47+
isAppOnlyToken,
1948
hasRequiredDelegatedPermissions,
2049
}

6-AdvancedScenarios/1-call-api-obo/API/authConfig.js

+8-7
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,20 @@ const authConfig = {
1515
loggingLevel: 'info',
1616
loggingNoPII: true,
1717
},
18-
19-
resources: {
20-
downstreamAPI: {
21-
endpoint: 'https://graph.microsoft.com/v1.0',
22-
scopes: ['User.Read', 'offline_access'],
23-
},
24-
middleTierAPI: {
18+
protectedRoutes: {
19+
profile: {
2520
endpoint: '/api/profile',
2621
delegatedPermissions: {
2722
scopes: ['access_graph_on_behalf_of_user'],
2823
},
2924
},
3025
},
26+
protectedResources: {
27+
graphApi: {
28+
endpoint: 'https://graph.microsoft.com/v1.0',
29+
scopes: ['User.Read', 'offline_access'],
30+
}
31+
},
3132
};
3233

3334
module.exports = authConfig;

6-AdvancedScenarios/1-call-api-obo/API/controllers/profileController.js

+17-8
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
1-
const { getOboToken } = require('../MsalOnBehalfOfClient');
2-
const { getGraphClient } = require('../util/graphClient');
31
const { ResponseType } = require('@microsoft/microsoft-graph-client');
2+
const { getOboToken } = require('../auth/onBehalfOfClient');
3+
const { getGraphClient } = require('../util/graphClient');
4+
45
const authConfig = require('../authConfig');
56

67
const {
8+
isAppOnlyToken,
79
hasRequiredDelegatedPermissions,
810
} = require('../auth/permissionUtils');
911

1012
exports.getProfile = async (req, res, next) => {
13+
if (isAppOnlyToken(req.authInfo)) {
14+
return next(new Error('This route requires a user token'));
15+
}
16+
1117
const userToken = req.get('authorization');
1218
const [bearer, tokenValue] = userToken.split(' ');
1319

14-
let accessToken;
15-
if (hasRequiredDelegatedPermissions(req.authInfo, authConfig.resources.middleTierAPI.delegatedPermissions.scopes)) {
20+
if (hasRequiredDelegatedPermissions(req.authInfo, authConfig.protectedRoutes.profile.delegatedPermissions.scopes)) {
1621
try {
17-
accessToken = await getOboToken(tokenValue);
18-
let graphResponse = await getGraphClient(accessToken).api('/me').responseType(ResponseType.RAW).get();
19-
graphResponse = await graphResponse.json();
20-
res.json(graphResponse);
22+
const accessToken = await getOboToken(tokenValue);
23+
const graphResponse = await getGraphClient(accessToken)
24+
.api('/me')
25+
.responseType(ResponseType.RAW)
26+
.get();
27+
28+
const response = await graphResponse.json();
29+
res.json(response);
2130
} catch (error) {
2231
next(error);
2332
}

6-AdvancedScenarios/1-call-api-obo/API/package-lock.json

-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

6-AdvancedScenarios/1-call-api-obo/API/package.json

-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
"express-rate-limit": "^6.7.0",
2121
"isomorphic-fetch": "^3.0.0",
2222
"morgan": "^1.10.0",
23-
"node-fetch": "^2.6.7",
2423
"passport": "^0.6.0",
2524
"passport-azure-ad": "^4.3.3"
2625
},

6-AdvancedScenarios/1-call-api-obo/API/util/graphClient.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const graph = require('@microsoft/microsoft-graph-client');
22
require('isomorphic-fetch');
3+
34
/**
45
* Creating a Graph client instance via options method. For more information, visit:
56
* https://github.com/microsoftgraph/msgraph-sdk-javascript/blob/dev/docs/CreatingClientInstance.md#2-create-with-options

6-AdvancedScenarios/1-call-api-obo/README.md

+18-42
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ page_type: sample
33
name: Authenticate a user with Azure AD using msal.js and call an Azure AD protected Node.js Web Api using on-behalf of flow
44
description: Handling Conditional Access challenges in an Azure AD protected Node.js web API calling another protected Node.js web API on behalf of a user using the on-behalf of flow
55
languages:
6-
- typescript
7-
- Node
6+
- javascript
87
products:
98
- azure-active-directory
109
- msal-js
10+
- msal-react
1111
- passport-azure-ad
1212
urlFragment: ms-identity-javascript-react-tutorial
1313
extensions:
@@ -42,7 +42,7 @@ This sample demonstrates a React single-page application (SPA) which lets a user
4242

4343
1. The client app uses **MSAL React** to sign-in a user and obtain a **JWT** [Access Token](https://aka.ms/access-tokens) from **Azure AD** for the **API**.
4444
1. The access token is used as a *bearer token* to authorize the user to call the Node.js **API** protected by **Azure AD**.
45-
1. This access token is also used by the Node.js API to obtain another Access token to call the MS Graph API **on user's behalf** using the [OAuth 2.0 on-behalf-of flow](https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow).
45+
1. This access token is also used by the Node.js API to obtain another Access token to call the MS Graph API **on user's behalf** using the [OAuth 2.0 on-behalf-of flow](https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow).
4646
1. The Node.js **API** uses the [Microsoft Graph SDK](https://docs.microsoft.com/graph/sdks/sdks-overview) to call MS Graph
4747

4848
![Scenario Image](./ReadmeFiles/topology.png)
@@ -54,7 +54,7 @@ This sample demonstrates a React single-page application (SPA) which lets a user
5454
| `AppCreationScripts/` | Contains Powershell scripts to automate app registration. |
5555
| `SPA/src/authConfig.js` | Contains configuration parameters for the SPA. |
5656
| `API/authConfig.json`| Contains authentication parameters for the API. |
57-
| `API/MsalOnBehalfOfClient.js`| Contains the logic to Acquire an access token for Graph API using OBO flow. |
57+
| `API/auth/onBehalfOfClient.js`| Contains logic to acquire an access token for Graph API using OBO flow. |
5858

5959
## Prerequisites
6060

@@ -119,7 +119,7 @@ There are two projects in this sample. Each needs to be separately registered in
119119
```PowerShell
120120
cd .\AppCreationScripts\
121121
.\Configure.ps1 -TenantId "[Optional] - your tenant id" -AzureEnvironmentName "[Optional] - Azure environment, defaults to 'Global'"
122-
```
122+
```
123123
124124
> Other ways of running the scripts are described in [App Creation Scripts guide](./AppCreationScripts/AppCreationScripts.md). The scripts also provide a guide to automated application registration, configuration and removal which can help in your CI/CD scenarios.
125125
@@ -189,19 +189,18 @@ To manually register the apps, as a first step you'll need to:
189189
1. Set the **optionalClaims** property as shown below to request client capabilities claim *xms_cc*:
190190
191191
```json
192-
"optionalClaims":
193-
{
194-
"accessToken": [
195-
{
196-
"additionalProperties": [],
197-
"essential": false,
198-
"name": "xms_cc",
199-
"source": null
200-
}
201-
],
202-
"idToken": [],
203-
"saml2Token": []
204-
}
192+
"optionalClaims": {
193+
"accessToken": [
194+
{
195+
"additionalProperties": [],
196+
"essential": false,
197+
"name": "xms_cc",
198+
"source": null
199+
}
200+
],
201+
"idToken": [],
202+
"saml2Token": []
203+
}
205204
```
206205
207206
##### Configure the service app (msal-node-api) to use your app registration
@@ -319,32 +318,9 @@ The middle-tier application adds the client to the `knownClientApplications` lis
319318
320319
### Acquire an access token with the OBO flow
321320

322-
To get the access token for Graph API using the OBO flow, the middle-tier web API will initialize a **ConfidentialClientApplication** to exchange the access token using the **acquireTokenOnBehalfOf** API to get a new access token for the down-stream resource in the case Graph API.
321+
To get the access token for Graph API using the OBO flow, the middle-tier web API will initialize a **ConfidentialClientApplication** to exchange the access token using the **acquireTokenOnBehalfOf** API to get a new access token for the down-stream resource in the case Graph API. This is shown in [onBehalfOfClient.js](./API/auth/onBehalfOfClient.js):
323322

324323
```javascript
325-
const msal = require('@azure/msal-node');
326-
const config = require('./authConfig');
327-
328-
const msalConfig = {
329-
auth: {
330-
clientId: config.credentials.clientID,
331-
authority: `https://${config.metadata.authority}/${config.credentials.tenantID}`,
332-
clientSecret: config.credentials.clientSecret,
333-
},
334-
system: {
335-
loggerOptions: {
336-
loggerCallback(loglevel, message, containsPii) {
337-
console.log(message);
338-
},
339-
piiLoggingEnabled: false,
340-
logLevel: msal.LogLevel.Info,
341-
},
342-
},
343-
};
344-
345-
// Create msal application object
346-
const cca = new msal.ConfidentialClientApplication(msalConfig);
347-
348324
const getOboToken = async (oboAssertion) => {
349325
const oboRequest = {
350326
oboAssertion: oboAssertion,

6-AdvancedScenarios/1-call-api-obo/SPA/src/components/NavigationBar.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const NavigationBar = () => {
5353
</a>
5454
<AuthenticatedTemplate>
5555
<Nav.Link className="navbarButton" href="/profile">
56-
Call API
56+
Profile
5757
</Nav.Link>
5858
<div className="collapse navbar-collapse justify-content-end">
5959
<DropdownButton

6-AdvancedScenarios/1-call-api-obo/SPA/src/fetch.js

+4-31
Original file line numberDiff line numberDiff line change
@@ -14,34 +14,7 @@ export const callApiWithToken = async (accessToken, apiEndpoint) => {
1414
headers: headers
1515
};
1616

17-
return fetch(apiEndpoint, options)
18-
.then(response => response.json())
19-
.then(response => {
20-
return new Promise((resolve, reject) => {
21-
// check for any errors
22-
if (response['error_codes']) {
23-
24-
/**
25-
* Conditional access MFA requirement throws an AADSTS50076 error.
26-
* If the user has not enrolled in MFA, an AADSTS50079 error will be thrown instead.
27-
* If this occurs, sample middle-tier API will propagate this to client
28-
* For more, visit: https://docs.microsoft.com/azure/active-directory/develop/v2-conditional-access-dev-guide
29-
*/
30-
if (response['error_codes'].includes(50076) || response['error_codes'].includes(50079)) {
31-
32-
// stringified JSON claims challenge
33-
reject(response['claims']);
34-
35-
/**
36-
* If the user has not consented to the required scopes,
37-
* an AADSTS65001 error will be thrown.
38-
*/
39-
} else if (response['error_codes'].includes(65001)) {
40-
reject();
41-
}
42-
} else {
43-
resolve(response);
44-
}
45-
})
46-
});
47-
}
17+
const response = await fetch(apiEndpoint, options);
18+
const responseJson = await response.json();
19+
return responseJson;
20+
};

0 commit comments

Comments
 (0)