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

Commit 149f9fb

Browse files
authored
Merge pull request #16 from Azure-Samples/auth-context-sample
Auth context sample
2 parents c8fc700 + aa2fc72 commit 149f9fb

Some content is hidden

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

55 files changed

+3032
-8
lines changed

.gitignore

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,6 @@ typings/
6868
# Yarn Integrity file
6969
.yarn-integrity
7070

71-
# dotenv environment variables file
72-
.env
73-
.env.test
74-
7571
# parcel-bundler cache (https://parceljs.org/)
7672
.cache
7773

5-AccessControl/1-call-api-roles/README.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,10 +243,6 @@ In a separate console window, execute the following commands:
243243
npm start
244244
```
245245

246-
> For Visual Studio Users
247-
>
248-
> Clean the solution, rebuild the solution, and run it. You might want to go into the solution properties and set both projects as startup projects, with the service project starting first.
249-
250246
## Explore the sample
251247

252248
1. Open your browser and navigate to `http://localhost:3000`.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
AUTHORITY="login.microsoftonline.com"
2+
3+
TENANT_ID="Enter_the_Tenant_Info_Here"
4+
CLIENT_ID="Enter_the_Application_Id_Here"
5+
CLIENT_SECRET="Enter_the_Client_Secret_Here"
6+
7+
REDIRECT_URI="http://localhost:5000/admin/redirect"
8+
9+
API_REQUIRED_PERMISSION="access_as_user"
10+
11+
EXPRESS_SESSION_SECRET="ENTER_YOUR_SECRET_HERE"
12+
13+
CORS_ALLOWED_DOMAINS="http://localhost:3000"
14+
15+
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License.
4+
*/
5+
6+
const express = require('express');
7+
const session = require('express-session');
8+
const methodOverride = require('method-override');
9+
const cors = require('cors');
10+
const path = require('path');
11+
require('dotenv').config();
12+
13+
const msalWrapper = require('msal-express-wrapper');
14+
const passport = require('passport');
15+
const BearerStrategy = require('passport-azure-ad').BearerStrategy;
16+
17+
const cache = require('./utils/cachePlugin');
18+
const todolistRoutes = require('./routes/todolistRoutes');
19+
const adminRoutes = require('./routes/adminRoutes');
20+
const routeGuard = require('./utils/routeGuard');
21+
22+
const app = express();
23+
24+
app.set('views', path.join(__dirname, './views'));
25+
app.set('view engine', 'ejs');
26+
27+
app.use('/css', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/css')));
28+
app.use('/js', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/js')));
29+
30+
app.use(express.static(path.join(__dirname, './public')));
31+
32+
app.use(methodOverride('_method'));
33+
app.use(express.urlencoded({ extended: true }));
34+
app.use(express.json());
35+
36+
/**
37+
* We need to enable CORS for client's domain in order to
38+
* expose www-authenticate header in response from the web API
39+
*/
40+
app.use(cors({
41+
origin: process.env.CORS_ALLOWED_DOMAINS, // replace with client domain
42+
exposedHeaders: "www-authenticate",
43+
}));
44+
45+
/**
46+
* Using express-session middleware. Be sure to familiarize yourself with available options
47+
* and set them as desired. Visit: https://www.npmjs.com/package/express-session
48+
*/
49+
const sessionConfig = {
50+
secret: process.env.EXPRESS_SESSION_SECRET,
51+
resave: false,
52+
saveUninitialized: false,
53+
cookie: {
54+
secure: false, // set this to true on production
55+
}
56+
}
57+
58+
if (app.get('env') === 'production') {
59+
60+
/**
61+
* In App Service, SSL termination happens at the network load balancers, so all HTTPS requests reach your app as unencrypted HTTP requests.
62+
* The line below is needed for getting the correct absolute URL for redirectUri configuration. For more information, visit:
63+
* https://docs.microsoft.com/azure/app-service/configure-language-nodejs?pivots=platform-linux#detect-https-session
64+
*/
65+
66+
app.set('trust proxy', 1) // trust first proxy e.g. App Service
67+
sessionConfig.cookie.secure = true // serve secure cookies
68+
}
69+
70+
app.use(session(sessionConfig));
71+
72+
// =========== Initialize Passport ==============
73+
74+
const bearerOptions = {
75+
identityMetadata: `https://${process.env.AUTHORITY}/${process.env.TENANT_ID}/v2.0/.well-known/openid-configuration`,
76+
issuer: `https://${process.env.AUTHORITY}/${process.env.TENANT_ID}/v2.0`,
77+
clientID: process.env.CLIENT_ID,
78+
audience: process.env.CLIENT_ID, // audience is this application
79+
validateIssuer: true,
80+
passReqToCallback: false,
81+
loggingLevel: "info",
82+
scope: [process.env.API_REQUIRED_PERMISSION] // scope you set during app registration
83+
};
84+
85+
const bearerStrategy = new BearerStrategy(bearerOptions, (token, done) => {
86+
// Send user info using the second argument
87+
done(null, {}, token);
88+
});
89+
90+
app.use(passport.initialize());
91+
92+
passport.use(bearerStrategy);
93+
94+
// protected api endpoints
95+
app.use('/api',
96+
passport.authenticate('oauth-bearer', { session: false }), // validate access tokens
97+
routeGuard, // check for auth context
98+
todolistRoutes
99+
);
100+
101+
// =========== Initialize MSAL Node Wrapper==============
102+
103+
const appSettings = {
104+
appCredentials: {
105+
clientId: process.env.CLIENT_ID,
106+
tenantId: process.env.TENANT_ID,
107+
clientSecret: process.env.CLIENT_SECRET,
108+
},
109+
authRoutes: {
110+
redirect: process.env.REDIRECT_URI, // enter the path component of your redirect URI
111+
error: "/admin/error", // the wrapper will redirect to this route in case of any error
112+
unauthorized: "/admin/unauthorized" // the wrapper will redirect to this route in case of unauthorized access attempt
113+
},
114+
remoteResources: {
115+
// Microsoft Graph beta authenticationContextClassReference endpoint. For more information,
116+
// visit: https://docs.microsoft.com/en-us/graph/api/resources/authenticationcontextclassreference?view=graph-rest-beta
117+
msGraphAcrs: {
118+
endpoint: "https://graph.microsoft.com/beta/identity/conditionalAccess/policies",
119+
scopes: ["Policy.ReadWrite.ConditionalAccess", "Policy.Read.ConditionalAccess"]
120+
},
121+
}
122+
}
123+
124+
// instantiate the wrapper
125+
const authProvider = new msalWrapper.AuthProvider(appSettings, cache);
126+
127+
// initialize the wrapper
128+
app.use(authProvider.initialize());
129+
130+
// pass down to the authProvider instance to use in router
131+
app.use('/admin',
132+
adminRoutes(authProvider)
133+
);
134+
135+
const port = process.env.PORT || 5000;
136+
137+
app.listen(port, () => {
138+
console.log('Listening on port ' + port);
139+
});
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
const AuthContext = require('../models/authContext');
2+
const msGraph = require('../utils/graphClient');
3+
4+
exports.getHomePage = (req, res, next) => {
5+
res.render('home', { isAuthenticated: req.session.isAuthenticated, username: req.session.isAuthenticated ? req.session.account.username : null });
6+
}
7+
8+
exports.getDetailsPage = (req, res, next) => {
9+
try {
10+
const acrsList = AuthContext.getAuthContexts();
11+
res.render('details', { isAuthenticated: req.session.isAuthenticated, acrsList: acrsList });
12+
} catch (error) {
13+
next(error);
14+
}
15+
}
16+
17+
exports.postDetailsPage = async(req, res, next) => {
18+
try {
19+
const authContext = new AuthContext(
20+
req.session.account.idTokenClaims.tid,
21+
req.body.authContext.split(' ')[0], // id
22+
req.body.authContext.split(' ')[1], // displayName
23+
req.body.operation
24+
);
25+
26+
AuthContext.postAuthContext(authContext);
27+
res.redirect('/admin/details');
28+
} catch (error) {
29+
next(error);
30+
}
31+
}
32+
33+
exports.deleteDetailsPage = async(req, res, next) => {
34+
try {
35+
const authContextId = req.body.authContextId;
36+
AuthContext.deleteAuthContext(authContextId);
37+
res.redirect('/admin/details');
38+
} catch (error) {
39+
next(error);
40+
}
41+
}
42+
43+
exports.getDashboardPage = (req, res, next) => {
44+
res.render('dashboard', { isAuthenticated: req.session.isAuthenticated, isLoaded: false });
45+
}
46+
47+
exports.postDashboardPage = async(req, res, next) => {
48+
try {
49+
// pass the access token to create a graph client
50+
const graphClient = msGraph.getAuthenticatedClient(req.session.remoteResources["msGraphAcrs"].accessToken);
51+
52+
let acrs = await graphClient
53+
.api('/identity/conditionalAccess/authenticationContextClassReferences')
54+
.version('beta')
55+
.get();
56+
57+
// check if acrs is empty
58+
if (acrs.value.length === 0) {
59+
60+
defaultAcrsList = [
61+
{
62+
id: 'c1',
63+
displayName: 'Require strong authentication',
64+
description: 'User needs to perform multifactor authentication',
65+
isAvailable: true
66+
},
67+
{
68+
id: 'c2',
69+
displayName: 'Require compliant devices',
70+
description: 'Users needs to prove using a compliant device',
71+
isAvailable: true
72+
},
73+
{
74+
id: 'c3',
75+
displayName: 'Require trusted locations',
76+
description: 'User needs to prove connecting from a trusted location',
77+
isAvailable: true
78+
},
79+
]
80+
81+
try {
82+
83+
// create default auth contexts
84+
defaultAcrsList.forEach(async(ac) => {
85+
await graphClient
86+
.api('/identity/conditionalAccess/authenticationContextClassReferences')
87+
.version('beta')
88+
.post(ac);
89+
});
90+
91+
return res.render('dashboard', { isAuthenticated: req.session.isAuthenticated, isLoaded: true, acrsList: defaultAcrsList });
92+
} catch (error) {
93+
next(error);
94+
}
95+
}
96+
97+
res.render('dashboard', { isAuthenticated: req.session.isAuthenticated, isLoaded: true, acrsList: acrs.value });
98+
} catch (error) {
99+
console.log(error);
100+
next(error);
101+
}
102+
}
103+
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
const Todo = require('../models/todo');
2+
3+
exports.getTodo = (req, res) => {
4+
const id = req.params.id;
5+
const owner = req.authInfo['preferred_username'];
6+
7+
try {
8+
const todo = Todo.getTodo(owner, id);
9+
res.status(200).send(todo);
10+
} catch (error) {
11+
console.log(error);
12+
next(error);
13+
}
14+
}
15+
16+
exports.getTodos = (req, res) => {
17+
const owner = req.authInfo['preferred_username'];
18+
19+
try {
20+
const todos = Todo.getTodos(owner);
21+
res.status(200).send(todos);
22+
} catch (error) {
23+
console.log(error);
24+
next(error);
25+
}
26+
}
27+
28+
exports.postTodo = (req, res) => {
29+
const id = req.body.id;
30+
const name = req.body.name;
31+
const owner = req.authInfo['preferred_username'];
32+
33+
const newTodo = new Todo(id, name, owner);
34+
35+
try {
36+
Todo.postTodo(newTodo);
37+
res.status(200).json({ message: "success" });
38+
} catch (error) {
39+
console.log(error);
40+
next(error);
41+
}
42+
}
43+
44+
exports.updateTodo = (req, res) => {
45+
const id = req.params.id;
46+
const owner = req.authInfo['preferred_username'];
47+
48+
try {
49+
Todo.updateTodo(owner, id, req.body)
50+
res.status(200).json({ message: "success" });
51+
} catch (error) {
52+
console.log(error);
53+
next(error);
54+
}
55+
}
56+
57+
exports.deleteTodo = (req, res) => {
58+
const id = req.params.id;;
59+
const owner = req.authInfo['preferred_username'];
60+
61+
try {
62+
Todo.deleteTodo(id, owner);
63+
res.status(200).json({ message: "success" });
64+
} catch (error) {
65+
console.log(error);
66+
next(error);
67+
}
68+
}

6-AdvancedScenarios/3-call-api-acrs/API/data/cache.json

Whitespace-only changes.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"todos": [],
3+
"acrs": []
4+
}

0 commit comments

Comments
 (0)