Skip to content

Commit

Permalink
Merge pull request #143 from Azure-Samples/basher-ch-3
Browse files Browse the repository at this point in the history
Update ch3 for BASHER
  • Loading branch information
derisen authored Jun 30, 2023
2 parents f9a2d96 + 92e7478 commit a272cac
Show file tree
Hide file tree
Showing 78 changed files with 15,966 additions and 33,541 deletions.
305 changes: 0 additions & 305 deletions 1-Authentication/1-sign-in/README-incremental.md

This file was deleted.

329 changes: 0 additions & 329 deletions 1-Authentication/2-sign-in-b2c/README-incremental.md

This file was deleted.

454 changes: 0 additions & 454 deletions 2-Authorization-I/1-call-graph/README-incremental.md

This file was deleted.

160 changes: 160 additions & 0 deletions 3-Authorization-II/1-call-api/API/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
const express = require('express');
const morgan = require('morgan');
const cors = require('cors');
const rateLimit = require('express-rate-limit');

const passport = require('passport');
const passportAzureAd = require('passport-azure-ad');

const authConfig = require('./authConfig');
const router = require('./routes/index');

const app = express();

/**
* If your app is behind a proxy, reverse proxy or a load balancer, consider
* letting express know that you are behind that proxy. To do so, uncomment
* the line below.
*/

// app.set('trust proxy', /* numberOfProxies */);

/**
* HTTP request handlers should not perform expensive operations such as accessing the file system,
* executing an operating system command or interacting with a database without limiting the rate at
* which requests are accepted. Otherwise, the application becomes vulnerable to denial-of-service attacks
* where an attacker can cause the application to crash or become unresponsive by issuing a large number of
* requests at the same time. For more information, visit: https://cheatsheetseries.owasp.org/cheatsheets/Denial_of_Service_Cheat_Sheet.html
*/
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
});

// Apply the rate limiting middleware to all requests
app.use(limiter)

/**
* Enable CORS middleware. In production, modify as to allow only designated origins and methods.
* If you are using Azure App Service, we recommend removing the line below and configure CORS on the App Service itself.
*/
app.use(cors());

app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(morgan('dev'));

const bearerStrategy = new passportAzureAd.BearerStrategy({
identityMetadata: `https://${authConfig.metadata.authority}/${authConfig.credentials.tenantID}/${authConfig.metadata.version}/${authConfig.metadata.discovery}`,
issuer: `https://${authConfig.metadata.authority}/${authConfig.credentials.tenantID}/${authConfig.metadata.version}`,
clientID: authConfig.credentials.clientID,
audience: authConfig.credentials.clientID, // audience is this application
validateIssuer: authConfig.settings.validateIssuer,
passReqToCallback: authConfig.settings.passReqToCallback,
loggingLevel: authConfig.settings.loggingLevel,
loggingNoPII: authConfig.settings.loggingNoPII,
}, (req, token, done) => {

/**
* Below you can do extended token validation and check for additional claims, such as:
* - check if the caller's tenant is in the allowed tenants list via the 'tid' claim (for multi-tenant applications)
* - check if the caller's account is homed or guest via the 'acct' optional claim
* - check if the caller belongs to right roles or groups via the 'roles' or 'groups' claim, respectively
*
* Bear in mind that you can do any of the above checks within the individual routes and/or controllers as well.
* For more information, visit: https://docs.microsoft.com/azure/active-directory/develop/access-tokens#validate-the-user-has-permission-to-access-this-data
*/


/**
* Lines below verifies if the caller's client ID is in the list of allowed clients.
* This ensures only the applications with the right client ID can access this API.
* To do so, we use "azp" claim in the access token. Uncomment the lines below to enable this check.
*/

// const myAllowedClientsList = [
// /* add here the client IDs of the applications that are allowed to call this API */
// ]

// if (!myAllowedClientsList.includes(token.azp)) {
// return done(new Error('Unauthorized'), {}, "Client not allowed");
// }


/**
* Access tokens that have neither the 'scp' (for delegated permissions) nor
* 'roles' (for application permissions) claim are not to be honored.
*/
if (!token.hasOwnProperty('scp') && !token.hasOwnProperty('roles')) {
return done(new Error('Unauthorized'), null, "No delegated or app permission claims found");
}

/**
* If needed, pass down additional user info to route using the second argument below.
* This information will be available in the req.user object.
*/
return done(null, {}, token);
});

app.use(passport.initialize());

passport.use(bearerStrategy);

app.use('/api', (req, res, next) => {
passport.authenticate('oauth-bearer', {
session: false,

/**
* If you are building a multi-tenant application and you need supply the tenant ID or name dynamically,
* uncomment the line below and pass in the tenant information. For more information, see:
* https://github.com/AzureAD/passport-azure-ad#423-options-available-for-passportauthenticate
*/

// tenantIdOrName: <some-tenant-id-or-name>

}, (err, user, info) => {
if (err) {
/**
* An error occurred during authorization. Either pass the error to the next function
* for Express error handler to handle, or send a response with the appropriate status code.
*/
return res.status(401).json({ error: err.message });
}

if (!user) {
// If no user object found, send a 401 response.
return res.status(401).json({ error: 'Unauthorized' });
}

if (info) {
// access token payload will be available in req.authInfo downstream
req.authInfo = info;
return next();
}
})(req, res, next);
},
router, // the router with all the routes
(err, req, res, next) => {
/**
* Add your custom error handling logic here. For more information, see:
* http://expressjs.com/en/guide/error-handling.html
*/

// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};

// send error response
res.status(err.status || 500).send(err);
}
);

const port = process.env.PORT || 5000;

app.listen(port, () => {
console.log('Listening on port ' + port);
});

module.exports = app;
66 changes: 66 additions & 0 deletions 3-Authorization-II/1-call-api/API/auth/permissionUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Indicates whether the access token was issued to a user or an application.
* @param {Object} accessTokenPayload
* @returns {boolean}
*/
const isAppOnlyToken = (accessTokenPayload) => {
/**
* An access token issued by Azure AD will have at least one of the two claims. Access tokens
* issued to a user will have the 'scp' claim. Access tokens issued to an application will have
* the roles claim. Access tokens that contain both claims are issued only to users, where the scp
* claim designates the delegated permissions, while the roles claim designates the user's role.
*
* To determine whether an access token was issued to a user (i.e delegated) or an application
* more easily, we recommend enabling the optional claim 'idtyp'. For more information, see:
* https://docs.microsoft.com/azure/active-directory/develop/access-tokens#user-and-application-tokens
*/
if (!accessTokenPayload.hasOwnProperty('idtyp')) {
if (accessTokenPayload.hasOwnProperty('scp')) {
return false;
} else if (!accessTokenPayload.hasOwnProperty('scp') && accessTokenPayload.hasOwnProperty('roles')) {
return true;
}
}

return accessTokenPayload.idtyp === 'app';
};

/**
* Ensures that the access token has the specified delegated permissions.
* @param {Object} accessTokenPayload: Parsed access token payload
* @param {Array} requiredPermission: list of required permissions
* @returns {boolean}
*/
const hasRequiredDelegatedPermissions = (accessTokenPayload, requiredPermission) => {
const normalizedRequiredPermissions = requiredPermission.map(permission => permission.toUpperCase());

if (accessTokenPayload.hasOwnProperty('scp') && accessTokenPayload.scp.split(' ')
.some(claim => normalizedRequiredPermissions.includes(claim.toUpperCase()))) {
return true;
}

return false;
}

/**
* Ensures that the access token has the specified application permissions.
* @param {Object} accessTokenPayload: Parsed access token payload
* @param {Array} requiredPermission: list of required permissions
* @returns {boolean}
*/
const hasRequiredApplicationPermissions = (accessTokenPayload, requiredPermission) => {
const normalizedRequiredPermissions = requiredPermission.map(permission => permission.toUpperCase());

if (accessTokenPayload.hasOwnProperty('roles') && accessTokenPayload.roles
.some(claim => normalizedRequiredPermissions.includes(claim.toUpperCase()))) {
return true;
}

return false;
}

module.exports = {
isAppOnlyToken,
hasRequiredDelegatedPermissions,
hasRequiredApplicationPermissions,
}
32 changes: 32 additions & 0 deletions 3-Authorization-II/1-call-api/API/authConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const passportConfig = {
credentials: {
tenantID: "Enter_the_Tenant_Info_Here",
clientID: "Enter_the_Application_Id_Here"
},
metadata: {
authority: "login.microsoftonline.com",
discovery: ".well-known/openid-configuration",
version: "v2.0"
},
settings: {
validateIssuer: true,
passReqToCallback: true,
loggingLevel: "info",
loggingNoPII: true,
},
protectedRoutes: {
todolist: {
endpoint: "/api/todolist",
delegatedPermissions: {
read: ["Todolist.Read", "Todolist.ReadWrite"],
write: ["Todolist.ReadWrite"]
},
applicationPermissions: {
read: ["Todolist.Read.All", "Todolist.ReadWrite.All"],
write: ["Todolist.ReadWrite.All"]
}
}
}
}

module.exports = passportConfig;
17 changes: 0 additions & 17 deletions 3-Authorization-II/1-call-api/API/config.json

This file was deleted.

Loading

0 comments on commit a272cac

Please sign in to comment.