Skip to content

Commit a272cac

Browse files
authored
Merge pull request #143 from Azure-Samples/basher-ch-3
Update ch3 for BASHER
2 parents f9a2d96 + 92e7478 commit a272cac

Some content is hidden

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

78 files changed

+15966
-33541
lines changed

1-Authentication/1-sign-in/README-incremental.md

-305
This file was deleted.

1-Authentication/2-sign-in-b2c/README-incremental.md

-329
This file was deleted.

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

-454
This file was deleted.
+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
const express = require('express');
2+
const morgan = require('morgan');
3+
const cors = require('cors');
4+
const rateLimit = require('express-rate-limit');
5+
6+
const passport = require('passport');
7+
const passportAzureAd = require('passport-azure-ad');
8+
9+
const authConfig = require('./authConfig');
10+
const router = require('./routes/index');
11+
12+
const app = express();
13+
14+
/**
15+
* If your app is behind a proxy, reverse proxy or a load balancer, consider
16+
* letting express know that you are behind that proxy. To do so, uncomment
17+
* the line below.
18+
*/
19+
20+
// app.set('trust proxy', /* numberOfProxies */);
21+
22+
/**
23+
* HTTP request handlers should not perform expensive operations such as accessing the file system,
24+
* executing an operating system command or interacting with a database without limiting the rate at
25+
* which requests are accepted. Otherwise, the application becomes vulnerable to denial-of-service attacks
26+
* where an attacker can cause the application to crash or become unresponsive by issuing a large number of
27+
* requests at the same time. For more information, visit: https://cheatsheetseries.owasp.org/cheatsheets/Denial_of_Service_Cheat_Sheet.html
28+
*/
29+
const limiter = rateLimit({
30+
windowMs: 15 * 60 * 1000, // 15 minutes
31+
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
32+
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
33+
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
34+
});
35+
36+
// Apply the rate limiting middleware to all requests
37+
app.use(limiter)
38+
39+
/**
40+
* Enable CORS middleware. In production, modify as to allow only designated origins and methods.
41+
* If you are using Azure App Service, we recommend removing the line below and configure CORS on the App Service itself.
42+
*/
43+
app.use(cors());
44+
45+
app.use(express.json());
46+
app.use(express.urlencoded({ extended: false }));
47+
app.use(morgan('dev'));
48+
49+
const bearerStrategy = new passportAzureAd.BearerStrategy({
50+
identityMetadata: `https://${authConfig.metadata.authority}/${authConfig.credentials.tenantID}/${authConfig.metadata.version}/${authConfig.metadata.discovery}`,
51+
issuer: `https://${authConfig.metadata.authority}/${authConfig.credentials.tenantID}/${authConfig.metadata.version}`,
52+
clientID: authConfig.credentials.clientID,
53+
audience: authConfig.credentials.clientID, // audience is this application
54+
validateIssuer: authConfig.settings.validateIssuer,
55+
passReqToCallback: authConfig.settings.passReqToCallback,
56+
loggingLevel: authConfig.settings.loggingLevel,
57+
loggingNoPII: authConfig.settings.loggingNoPII,
58+
}, (req, token, done) => {
59+
60+
/**
61+
* Below you can do extended token validation and check for additional claims, such as:
62+
* - check if the caller's tenant is in the allowed tenants list via the 'tid' claim (for multi-tenant applications)
63+
* - check if the caller's account is homed or guest via the 'acct' optional claim
64+
* - check if the caller belongs to right roles or groups via the 'roles' or 'groups' claim, respectively
65+
*
66+
* Bear in mind that you can do any of the above checks within the individual routes and/or controllers as well.
67+
* For more information, visit: https://docs.microsoft.com/azure/active-directory/develop/access-tokens#validate-the-user-has-permission-to-access-this-data
68+
*/
69+
70+
71+
/**
72+
* Lines below verifies if the caller's client ID is in the list of allowed clients.
73+
* This ensures only the applications with the right client ID can access this API.
74+
* To do so, we use "azp" claim in the access token. Uncomment the lines below to enable this check.
75+
*/
76+
77+
// const myAllowedClientsList = [
78+
// /* add here the client IDs of the applications that are allowed to call this API */
79+
// ]
80+
81+
// if (!myAllowedClientsList.includes(token.azp)) {
82+
// return done(new Error('Unauthorized'), {}, "Client not allowed");
83+
// }
84+
85+
86+
/**
87+
* Access tokens that have neither the 'scp' (for delegated permissions) nor
88+
* 'roles' (for application permissions) claim are not to be honored.
89+
*/
90+
if (!token.hasOwnProperty('scp') && !token.hasOwnProperty('roles')) {
91+
return done(new Error('Unauthorized'), null, "No delegated or app permission claims found");
92+
}
93+
94+
/**
95+
* If needed, pass down additional user info to route using the second argument below.
96+
* This information will be available in the req.user object.
97+
*/
98+
return done(null, {}, token);
99+
});
100+
101+
app.use(passport.initialize());
102+
103+
passport.use(bearerStrategy);
104+
105+
app.use('/api', (req, res, next) => {
106+
passport.authenticate('oauth-bearer', {
107+
session: false,
108+
109+
/**
110+
* If you are building a multi-tenant application and you need supply the tenant ID or name dynamically,
111+
* uncomment the line below and pass in the tenant information. For more information, see:
112+
* https://github.com/AzureAD/passport-azure-ad#423-options-available-for-passportauthenticate
113+
*/
114+
115+
// tenantIdOrName: <some-tenant-id-or-name>
116+
117+
}, (err, user, info) => {
118+
if (err) {
119+
/**
120+
* An error occurred during authorization. Either pass the error to the next function
121+
* for Express error handler to handle, or send a response with the appropriate status code.
122+
*/
123+
return res.status(401).json({ error: err.message });
124+
}
125+
126+
if (!user) {
127+
// If no user object found, send a 401 response.
128+
return res.status(401).json({ error: 'Unauthorized' });
129+
}
130+
131+
if (info) {
132+
// access token payload will be available in req.authInfo downstream
133+
req.authInfo = info;
134+
return next();
135+
}
136+
})(req, res, next);
137+
},
138+
router, // the router with all the routes
139+
(err, req, res, next) => {
140+
/**
141+
* Add your custom error handling logic here. For more information, see:
142+
* http://expressjs.com/en/guide/error-handling.html
143+
*/
144+
145+
// set locals, only providing error in development
146+
res.locals.message = err.message;
147+
res.locals.error = req.app.get('env') === 'development' ? err : {};
148+
149+
// send error response
150+
res.status(err.status || 500).send(err);
151+
}
152+
);
153+
154+
const port = process.env.PORT || 5000;
155+
156+
app.listen(port, () => {
157+
console.log('Listening on port ' + port);
158+
});
159+
160+
module.exports = app;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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+
28+
/**
29+
* Ensures that the access token has the specified delegated permissions.
30+
* @param {Object} accessTokenPayload: Parsed access token payload
31+
* @param {Array} requiredPermission: list of required permissions
32+
* @returns {boolean}
33+
*/
34+
const hasRequiredDelegatedPermissions = (accessTokenPayload, requiredPermission) => {
35+
const normalizedRequiredPermissions = requiredPermission.map(permission => permission.toUpperCase());
36+
37+
if (accessTokenPayload.hasOwnProperty('scp') && accessTokenPayload.scp.split(' ')
38+
.some(claim => normalizedRequiredPermissions.includes(claim.toUpperCase()))) {
39+
return true;
40+
}
41+
42+
return false;
43+
}
44+
45+
/**
46+
* Ensures that the access token has the specified application permissions.
47+
* @param {Object} accessTokenPayload: Parsed access token payload
48+
* @param {Array} requiredPermission: list of required permissions
49+
* @returns {boolean}
50+
*/
51+
const hasRequiredApplicationPermissions = (accessTokenPayload, requiredPermission) => {
52+
const normalizedRequiredPermissions = requiredPermission.map(permission => permission.toUpperCase());
53+
54+
if (accessTokenPayload.hasOwnProperty('roles') && accessTokenPayload.roles
55+
.some(claim => normalizedRequiredPermissions.includes(claim.toUpperCase()))) {
56+
return true;
57+
}
58+
59+
return false;
60+
}
61+
62+
module.exports = {
63+
isAppOnlyToken,
64+
hasRequiredDelegatedPermissions,
65+
hasRequiredApplicationPermissions,
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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: true,
16+
},
17+
protectedRoutes: {
18+
todolist: {
19+
endpoint: "/api/todolist",
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+
}
31+
32+
module.exports = passportConfig;

3-Authorization-II/1-call-api/API/config.json

-17
This file was deleted.

0 commit comments

Comments
 (0)