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

Commit a961a2e

Browse files
committed
updated fetch functions, config scripts, config object and sample.json
1 parent ec9d145 commit a961a2e

18 files changed

+1033
-975
lines changed
+66-22
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,87 @@
11
const express = require('express');
22
const morgan = require('morgan');
33
const cors = require('cors');
4+
45
const passport = require('passport');
6+
const passportAzureAd = require('passport-azure-ad');
57

6-
const config = require('./authConfig');
8+
const authConfig = require('./authConfig');
79
const router = require('./routes/router');
10+
811
const routeGuard = require('./utils/guard');
912

10-
const BearerStrategy = require('passport-azure-ad').BearerStrategy;
11-
12-
const options = {
13-
identityMetadata: `https://${config.metadata.authority}/${config.credentials.tenantID}/${config.metadata.version}/${config.metadata.discovery}`,
14-
issuer: `https://${config.metadata.authority}/${config.credentials.tenantID}/${config.metadata.version}`,
15-
clientID: config.credentials.clientID,
16-
audience: config.credentials.clientID, // audience is this application
17-
validateIssuer: config.settings.validateIssuer,
18-
passReqToCallback: config.settings.passReqToCallback,
19-
loggingLevel: config.settings.loggingLevel,
20-
};
21-
22-
const bearerStrategy = new BearerStrategy(options, (token, done) => {
23-
// Send user info using the second argument
24-
done(null, {}, token);
25-
});
13+
const { requiredScopeOrAppPermission } = require('./auth/permissionUtils');
2614

2715
const app = express();
2816

17+
/**
18+
* Enable CORS middleware. In production, modify as to allow only designated origins and methods.
19+
* If you are using Azure App Service, we recommend removing the line below and configure CORS on the App Service itself.
20+
*/
21+
app.use(cors());
22+
2923
app.use(morgan('dev'));
24+
app.use(express.urlencoded({ extended: false }));
3025
app.use(express.json());
31-
app.use(cors());
26+
27+
const bearerStrategy = new passportAzureAd.BearerStrategy(
28+
{
29+
identityMetadata: `https://${authConfig.metadata.authority}/${authConfig.credentials.tenantID}/${authConfig.metadata.version}/${authConfig.metadata.discovery}`,
30+
issuer: `https://${authConfig.metadata.authority}/${authConfig.credentials.tenantID}/${authConfig.metadata.version}`,
31+
clientID: authConfig.credentials.clientID,
32+
audience: authConfig.credentials.clientID, // audience is this application
33+
validateIssuer: authConfig.settings.validateIssuer,
34+
passReqToCallback: authConfig.settings.passReqToCallback,
35+
loggingLevel: authConfig.settings.loggingLevel,
36+
loggingNoPII: authConfig.settings.loggingNoPII,
37+
},
38+
(req, token, done) => {
39+
/**
40+
* Below you can do extended token validation and check for additional claims, such as:
41+
* - check if the caller's tenant is in the allowed tenants list via the 'tid' claim (for multi-tenant applications)
42+
* - check if the caller's account is homed or guest via the 'acct' optional claim
43+
* - check if the caller belongs to right roles or groups via the 'roles' or 'groups' claim, respectively
44+
*
45+
* Bear in mind that you can do any of the above checks within the individual routes and/or controllers as well.
46+
* For more information, visit: https://docs.microsoft.com/azure/active-directory/develop/access-tokens#validate-the-user-has-permission-to-access-this-data
47+
*/
48+
if (
49+
requiredScopeOrAppPermission(token, [
50+
...authConfig.protectedRoutes.todolist.delegatedPermissions.read,
51+
...authConfig.protectedRoutes.todolist.delegatedPermissions.write,
52+
...authConfig.protectedRoutes.todolist.applicationPermissions.read,
53+
...authConfig.protectedRoutes.todolist.applicationPermissions.write,
54+
])
55+
) {
56+
/**
57+
* If needed, pass down additional user info to route using the second argument below.
58+
* This information will be available in the req.user object.
59+
*/
60+
return done(null, {}, token);
61+
} else {
62+
return done(new Error('Unauthorized'), {}, 'Unauthorized');
63+
}
64+
}
65+
);
3266

3367
app.use(passport.initialize());
3468

3569
passport.use(bearerStrategy);
3670

3771
// Validates token, checks for role and serve
38-
app.use('/api',
39-
passport.authenticate('oauth-bearer', { session: false }),
40-
routeGuard(config.accessMatrix),
72+
app.use(
73+
'/api',
74+
passport.authenticate('oauth-bearer', {
75+
session: false,
76+
/**
77+
* If you are building a multi-tenant application and you need supply the tenant ID or name dynamically, uncomment
78+
* the line below and pass in the tenant information. For more information, see:
79+
* https://github.com/AzureAD/passport-azure-ad#423-options-available-for-passportauthenticate
80+
*/
81+
82+
// tenantIdOrName: <some-tenant-id-or-name>
83+
}),
84+
routeGuard(authConfig.accessMatrix),
4185
router
4286
);
4387

@@ -47,4 +91,4 @@ app.listen(port, () => {
4791
console.log('Listening on port ' + port);
4892
});
4993

50-
module.exports = app;
94+
module.exports = app;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* Ensures that the access token has at least one allowed permission.
3+
* @param {Object} accessTokenPayload: Parsed access token payload
4+
* @param {Array} allowedPermissions: list of allowed permissions i.e. delegated + application
5+
* @returns {boolean}
6+
*/
7+
exports.requiredScopeOrAppPermission = (accessTokenPayload, allowedPermissions) => {
8+
/**
9+
* Access tokens that have neither the 'scp' (for delegated permissions) or
10+
* 'roles' (for application permissions) claim are not to be honored.
11+
*
12+
* An access token issued by Azure AD will have at least one of the two claims.
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+
18+
if (!accessTokenPayload.hasOwnProperty('scp') && !accessTokenPayload.hasOwnProperty('roles')) {
19+
return false;
20+
} else if (accessTokenPayload.hasOwnProperty('scp')) {
21+
return accessTokenPayload.scp.split(' ').some((scope) => allowedPermissions.includes(scope));
22+
} else if (accessTokenPayload.hasOwnProperty('roles')) {
23+
return accessTokenPayload.roles.some((role) => allowedPermissions.includes(role));
24+
}
25+
};
26+
27+
/**
28+
* Ensures that the access token has the required delegated permissions.
29+
* @param {Object} accessTokenPayload: Parsed access token payload
30+
* @param {Array} requiredPermission: list of required permissions
31+
* @returns {boolean}
32+
*/
33+
exports.hasDelegatedPermissions = (accessTokenPayload, requiredPermission) => {
34+
if (!accessTokenPayload.hasOwnProperty('scp')) {
35+
console.log('Access token does not have scp claim');
36+
return false;
37+
}
38+
39+
if (accessTokenPayload.scp.split(' ').some((claim) => requiredPermission.includes(claim))) {
40+
return true;
41+
}
42+
43+
return false;
44+
};
45+
46+
/**
47+
* Ensures that the access token has the required application permissions.
48+
* @param {Object} accessTokenPayload: Parsed access token payload
49+
* @param {Array} requiredPermission: list of required permissions
50+
* @returns {boolean}
51+
*/
52+
exports.hasApplicationPermissions = (accessTokenPayload, requiredPermission) => {
53+
if (!accessTokenPayload.hasOwnProperty('roles')) {
54+
console.log('Access token does not have roles claim');
55+
return false;
56+
}
57+
58+
if (accessTokenPayload.roles.some((claim) => requiredPermission.includes(claim))) {
59+
return true;
60+
}
61+
62+
return false;
63+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const passportConfig = {
2+
credentials: {
3+
tenantID: 'Enter_the_Tenant_Info_Here',
4+
clientID: 'Enter_the_Application_Id_Here',
5+
},
6+
metadata: {
7+
authority: 'login.microsoftonline.com',
8+
discovery: '.well-known/openid-configuration',
9+
version: 'v2.0',
10+
},
11+
settings: {
12+
validateIssuer: true,
13+
passReqToCallback: true,
14+
loggingLevel: 'info',
15+
loggingNoPII: false,
16+
},
17+
protectedRoutes: {
18+
todolist: {
19+
endpoint: '/api',
20+
delegatedPermissions: {
21+
read: ['Todolist.Read', 'Todolist.ReadWrite'],
22+
write: ['Todolist.ReadWrite'],
23+
},
24+
applicationPermissions: {
25+
read: ['Todolist.Read.All', 'Todolist.ReadWrite.All'],
26+
write: ['Todolist.ReadWrite.All'],
27+
},
28+
},
29+
},
30+
accessMatrix: {
31+
todolist: {
32+
path: '/todolist',
33+
methods: ['GET', 'POST', 'PUT', 'DELETE'],
34+
roles: ['TaskUser', 'TaskAdmin'],
35+
},
36+
dashboard: {
37+
path: '/dashboard',
38+
methods: ['GET'],
39+
roles: ['TaskAdmin'],
40+
},
41+
},
42+
};
43+
44+
module.exports = passportConfig;

5-AccessControl/1-call-api-roles/API/authConfig.json

-48
This file was deleted.

5-AccessControl/1-call-api-roles/API/controllers/dashboard.js

+25-4
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,30 @@ const FileSync = require('lowdb/adapters/FileSync');
33
const adapter = new FileSync('./data/db.json');
44
const db = lowdb(adapter);
55

6-
exports.getAllTodos = (req, res) => {
7-
const todos = db.get('todos')
8-
.value();
6+
const { hasDelegatedPermissions, hasApplicationPermissions } = require('../auth/permissionUtils');
7+
8+
const authConfig = require('../authConfig');
9+
10+
exports.getAllTodos = (req, res, next) => {
11+
if (hasDelegatedPermissions(req.authInfo, authConfig.protectedRoutes.todolist.delegatedPermissions.read)) {
12+
try {
13+
14+
const todos = db.get('todos').value();
15+
res.status(200).send(todos);
16+
17+
}catch(error) {
18+
next(error);
19+
}
20+
}else if(hasApplicationPermissions(req.authInfo, authConfig.protectedRoutes.todolist.applicationPermissions.read)) {
21+
try {
22+
const todos = db.get('todos').value();
23+
res.status(200).send(todos);
24+
25+
}catch(error) {
26+
next(error);
27+
}
28+
}else {
29+
next(new Error('User or application does not have the required permissions'));
30+
}
931

10-
res.status(200).send(todos);
1132
}

0 commit comments

Comments
 (0)