diff --git a/1-Authentication/5-sign-in-express/App/app.js b/1-Authentication/5-sign-in-express/App/app.js index 3a50080b2..25257a945 100644 --- a/1-Authentication/5-sign-in-express/App/app.js +++ b/1-Authentication/5-sign-in-express/App/app.js @@ -1,68 +1,146 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. - */ +const msal = require('@azure/msal-node'); +const express = require('express'); +const session = require('express-session'); +const createError = require('http-errors'); +const cookieParser = require('cookie-parser'); +const logger = require('morgan'); +const { msalConfig, TENANT_SUBDOMAIN, REDIRECT_URI, POST_LOGOUT_REDIRECT_URI } = require('./authConfig'); + +// Initialize MSAL Node (confidential app) + +const msalInstance = new msal.ConfidentialClientApplication(msalConfig); -require('dotenv').config(); +/** + * The MSAL.js library allows you to pass your custom state as state parameter in the Request object + * By default, MSAL.js passes a randomly generated unique state parameter value in the authentication requests. + * The state parameter can also be used to encode information of the app's state before redirect. + * You can pass the user's state in the app, such as the page or view they were on, as input to this parameter. + * For more information, visit: https://docs.microsoft.com/azure/active-directory/develop/msal-js-pass-custom-state-authentication-request + * In this scenario, the states also serve to show the action that was requested of B2C since only one redirect URL is possible. + */ -var path = require('path'); -var express = require('express'); -var session = require('express-session'); -var createError = require('http-errors'); -var cookieParser = require('cookie-parser'); -var logger = require('morgan'); +const APP_STATES = { + LOGIN: 'login' +} -var indexRouter = require('./routes/index'); -var usersRouter = require('./routes/users'); -var authRouter = require('./routes/auth'); +/** + * Request Configuration + * We manipulate these two request objects below + * to acquire a token with the appropriate claims. + */ + const authCodeRequest = { + redirectUri: REDIRECT_URI, +}; -// initialize express -var app = express(); +const tokenRequest = { + redirectUri: REDIRECT_URI, +}; /** - * Using express-session middleware for persistent user session. Be sure to - * familiarize yourself with available options. Visit: https://www.npmjs.com/package/express-session + * Using express-session middleware. Be sure to familiarize yourself with available options + * and set them as desired. Visit: https://www.npmjs.com/package/express-session */ -app.use( - session({ - secret: process.env.EXPRESS_SESSION_SECRET || 'Enter_the_Express_Session_Secret_Here', - resave: false, - saveUninitialized: false, - cookie: { - httpOnly: true, - secure: false, // set this to true on production - }, - }) -); - -// view engine setup -app.set('views', path.join(__dirname, 'views')); -app.set('view engine', 'hbs'); + const sessionConfig = { + secret: process.env.EXPRESS_SESSION_SECRET || 'Enter_the_Express_Session_Secret_Here', + resave: false, + saveUninitialized: false, + cookie: { + httpOnly: true, + secure: false, // set value to true in production + } +} + +//Create an express instance +const app = express(); +app.use(session(sessionConfig)); app.use(logger('dev')); app.use(express.json()); app.use(cookieParser()); -app.use(express.urlencoded({ extended: false })); -app.use(express.static(path.join(__dirname, 'public'))); +app.use(express.urlencoded({ extended: false })) + -app.use('/', indexRouter); -app.use('/users', usersRouter); -app.use('/auth', authRouter); +/** + * This method is used to generate an auth code request, the first leg of authorization code grant flow + * @param {array} scopes: scopes to request the auth code for + * @param {string} state: state of the application + * @param {Object} res: express middleware response object + */ -// catch 404 and forward to error handler -app.use(function (req, res, next) { - next(createError(404)); +const getAuthCode = (authority, scopes, state, res) => { + + // prepare the request + console.log("Fetching Authorization code") + authCodeRequest.authority = authority; + authCodeRequest.scopes = scopes; + authCodeRequest.state = state; + + // request an authorization code to exchange for a token + return msalInstance.getAuthCodeUrl(authCodeRequest) + .then((response) => { + console.log("\nAuthCodeURL: \n" + response); + //redirect to the auth code URL/send code to + res.redirect(response); + }) + .catch((error) => { + res.status(500).send(error); + }); +} + +app.get('/', (req, res) => { + if (req.session.isAuthenticated && req.session.isAuthenticated === true) { + res.send('View id token claims
Sign out'); + }else{ + res.send('Sign in
') + } }); -// error handler -app.use(function (err, req, res, next) { - // set locals, only providing error in development - res.locals.message = err.message; - res.locals.error = req.app.get('env') === 'development' ? err : {}; +app.get('/signin', (req, res) => { + //Initiate a Auth Code Flow >> for sign in + //no scopes passed. openid, profile and offline_access will be used by default. + getAuthCode(msalConfig.auth.authority, [], APP_STATES.LOGIN, res); +}); - // render the error page - res.status(err.status || 500); - res.render('error'); +app.get('/signout',async (req, res)=>{ + logoutUri = process.env.LOGOUT_ENDPOINT || `${msalConfig.auth.authority}${TENANT_SUBDOMAIN}.onmicrosoft.com/oauth2/v2.0/logout?post_logout_redirect_uri=${POST_LOGOUT_REDIRECT_URI}`; + req.session.destroy(() => { + //When session destruction succeeds, notify CIAM service using the logout uri. + res.redirect(logoutUri); + }); }); -module.exports = app; +app.get('/id-token-claims', (req, res) => { + if (req.session.isAuthenticated && req.session.isAuthenticated === true) { + // list all id token claims + for (let key in req.session.account.idTokenClaims){ + let claimAndValue = key + ":" + req.session.account.idTokenClaims[key]; + console.log(claimAndValue); + } + } else { + res.redirect('/'); + } +}); + +app.get('/redirect', (req, res) => { + //prepare the request to acquire id token by using acquireTokenByCode() + tokenRequest.code = req.query.code; + msalInstance.acquireTokenByCode(tokenRequest).then((tokenResponse)=>{ + + //add response info to express session + req.session.account = tokenResponse.account; + req.session.idToken = tokenResponse.idToken + req.session.isAuthenticated = true; + //log tokenResponse + console.log("\nAuthToken: \n" + JSON.stringify(tokenResponse)); + //log a claim, such as name to show how to get a token claim from the id token + console.log("\nGiven name: \n" + JSON.stringify(tokenResponse.account.idTokenClaims.name)); + res.redirect('/'); + }).catch((error)=>{ + console.log("\nErrorAtLogin: \n" + error); + }); +}); + + +app.listen(process.env.SERVER_PORT || 3000, () => { + console.log('Msal Node Auth Code Sample app listening on port ! ' + (process.env.SERVER_PORT || 3000)); +}); \ No newline at end of file diff --git a/1-Authentication/5-sign-in-express/App/auth/AuthProvider.js b/1-Authentication/5-sign-in-express/App/auth/AuthProvider.js deleted file mode 100644 index 5efcb91bd..000000000 --- a/1-Authentication/5-sign-in-express/App/auth/AuthProvider.js +++ /dev/null @@ -1,184 +0,0 @@ -const msal = require('@azure/msal-node'); -const axios = require('axios'); -const { msalConfig, TENANT_SUBDOMAIN, REDIRECT_URI, POST_LOGOUT_REDIRECT_URI } = require('../authConfig'); - -class AuthProvider { - config; - cryptoProvider; - - constructor(config) { - this.config = config; - this.cryptoProvider = new msal.CryptoProvider(); - } - - getMsalInstance(msalConfig) { - return new msal.ConfidentialClientApplication(msalConfig); - } - - async login(req, res, next, options = {}) { - // create a GUID for crsf - req.session.csrfToken = this.cryptoProvider.createNewGuid(); - - /** - * The MSAL Node library allows you to pass your custom state as state parameter in the Request object. - * The state parameter can also be used to encode information of the app's state before redirect. - * You can pass the user's state in the app, such as the page or view they were on, as input to this parameter. - */ - const state = this.cryptoProvider.base64Encode( - JSON.stringify({ - csrfToken: req.session.csrfToken, - redirectTo: '/', - }) - ); - - const authCodeUrlRequestParams = { - state: state, - - /** - * By default, MSAL Node will add OIDC scopes to the auth code url request. For more information, visit: - * https://docs.microsoft.com/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes - */ - scopes: [], - }; - - const authCodeRequestParams = { - state: state, - - /** - * By default, MSAL Node will add OIDC scopes to the auth code request. For more information, visit: - * https://docs.microsoft.com/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes - */ - scopes: [], - }; - - /** - * If the current msal configuration does not have cloudDiscoveryMetadata or authorityMetadata, we will - * make a request to the relevant endpoints to retrieve the metadata. This allows MSAL to avoid making - * metadata discovery calls, thereby improving performance of token acquisition process. - */ - if (!this.config.msalConfig.auth.authorityMetadata) { - const authorityMetadata = await this.getAuthorityMetadata(); - this.config.msalConfig.auth.authorityMetadata = JSON.stringify(authorityMetadata); - } - - const msalInstance = this.getMsalInstance(this.config.msalConfig); - - // trigger the first leg of auth code flow - return this.redirectToAuthCodeUrl( - req, - res, - next, - authCodeUrlRequestParams, - authCodeRequestParams, - msalInstance - ); - } - - async handleRedirect(req, res, next) { - const authCodeRequest = { - ...req.session.authCodeRequest, - code: req.body.code, // authZ code - codeVerifier: req.session.pkceCodes.verifier, // PKCE Code Verifier - }; - - try { - const msalInstance = this.getMsalInstance(this.config.msalConfig); - msalInstance.getTokenCache().deserialize(req.session.tokenCache); - - const tokenResponse = await msalInstance.acquireTokenByCode(authCodeRequest, req.body); - - req.session.tokenCache = msalInstance.getTokenCache().serialize(); - req.session.idToken = tokenResponse.idToken; - req.session.account = tokenResponse.account; - req.session.isAuthenticated = true; - - const state = JSON.parse(this.cryptoProvider.base64Decode(req.body.state)); - res.redirect(state.redirectTo); - } catch (error) { - next(error); - } - } - - async logout(req, res, next) { - /** - * Construct a logout URI and redirect the user to end the - * session with Azure AD. For more information, visit: - * https://docs.microsoft.com/azure/active-directory/develop/v2-protocols-oidc#send-a-sign-out-request - */ - const logoutUri = `${this.config.msalConfig.auth.authority}${TENANT_SUBDOMAIN}.onmicrosoft.com/oauth2/v2.0/logout?post_logout_redirect_uri=${this.config.postLogoutRedirectUri}`; - - req.session.destroy(() => { - res.redirect(logoutUri); - }); - } - - /** - * Prepares the auth code request parameters and initiates the first leg of auth code flow - * @param req: Express request object - * @param res: Express response object - * @param next: Express next function - * @param authCodeUrlRequestParams: parameters for requesting an auth code url - * @param authCodeRequestParams: parameters for requesting tokens using auth code - */ - async redirectToAuthCodeUrl(req, res, next, authCodeUrlRequestParams, authCodeRequestParams, msalInstance) { - // Generate PKCE Codes before starting the authorization flow - const { verifier, challenge } = await this.cryptoProvider.generatePkceCodes(); - - // Set generated PKCE codes and method as session vars - req.session.pkceCodes = { - challengeMethod: 'S256', - verifier: verifier, - challenge: challenge, - }; - - /** - * By manipulating the request objects below before each request, we can obtain - * auth artifacts with desired claims. For more information, visit: - * https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_node.html#authorizationurlrequest - * https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_node.html#authorizationcoderequest - **/ - - req.session.authCodeUrlRequest = { - ...authCodeUrlRequestParams, - redirectUri: this.config.redirectUri, - responseMode: 'form_post', // recommended for confidential clients - codeChallenge: req.session.pkceCodes.challenge, - codeChallengeMethod: req.session.pkceCodes.challengeMethod, - }; - - req.session.authCodeRequest = { - ...authCodeRequestParams, - redirectUri: this.config.redirectUri, - code: '', - }; - - try { - const authCodeUrlResponse = await msalInstance.getAuthCodeUrl(req.session.authCodeUrlRequest); - res.redirect(authCodeUrlResponse); - } catch (error) { - next(error); - } - } - - /** - * Retrieves oidc metadata from the openid endpoint - * @returns - */ - async getAuthorityMetadata() { - const endpoint = `${this.config.msalConfig.auth.authority}${TENANT_SUBDOMAIN}.onmicrosoft.com/v2.0/.well-known/openid-configuration`; - try { - const response = await axios.get(endpoint); - return await response.data; - } catch (error) { - console.log(error); - } - } -} - -const authProvider = new AuthProvider({ - msalConfig: msalConfig, - redirectUri: REDIRECT_URI, - postLogoutRedirectUri: POST_LOGOUT_REDIRECT_URI, -}); - -module.exports = authProvider; diff --git a/1-Authentication/5-sign-in-express/App/authConfig.js b/1-Authentication/5-sign-in-express/App/authConfig.js index 23b46545d..2ec8668eb 100644 --- a/1-Authentication/5-sign-in-express/App/authConfig.js +++ b/1-Authentication/5-sign-in-express/App/authConfig.js @@ -6,7 +6,7 @@ require('dotenv').config(); const TENANT_SUBDOMAIN = process.env.TENANT_SUBDOMAIN || 'Enter_the_Tenant_Subdomain_Here'; -const REDIRECT_URI = process.env.REDIRECT_URI || 'http://localhost:3000/auth/redirect'; +const REDIRECT_URI = process.env.REDIRECT_URI || 'http://localhost:3000/redirect'; const POST_LOGOUT_REDIRECT_URI = process.env.POST_LOGOUT_REDIRECT_URI || 'http://localhost:3000'; /** diff --git a/1-Authentication/5-sign-in-express/App/controller/authController.js b/1-Authentication/5-sign-in-express/App/controller/authController.js deleted file mode 100644 index 5255e5318..000000000 --- a/1-Authentication/5-sign-in-express/App/controller/authController.js +++ /dev/null @@ -1,13 +0,0 @@ -const authProvider = require('../auth/AuthProvider'); - -exports.signIn = async (req, res, next) => { - return authProvider.login(req, res, next); -}; - -exports.handleRedirect = async (req, res, next) => { - return authProvider.handleRedirect(req, res, next); -} - -exports.signOut = async (req, res, next) => { - return authProvider.logout(req, res, next); -}; diff --git a/1-Authentication/5-sign-in-express/App/package.json b/1-Authentication/5-sign-in-express/App/package.json index 2c88dda12..6caaa379f 100644 --- a/1-Authentication/5-sign-in-express/App/package.json +++ b/1-Authentication/5-sign-in-express/App/package.json @@ -1,21 +1,19 @@ { "name": "msal-node-auth-code", "version": "1.0.0", - "description": "sample web app for msal-node", + "description": "sample web app for msal-node no no-identity code", "scripts": { - "start": "node server.js" + "start": "node app.js" }, "author": "Microsoft", "license": "MIT", "dependencies": { - "@azure/msal-node": "^1.17.2", - "axios": "^1.0.0", + "@azure/msal-node": "^2.5.1", "cookie-parser": "^1.4.6", - "dotenv": "^16.0.3", - "express": "^4.18.1", + "dotenv": "^16.3.1", + "express": "^4.18.2", "express-session": "^1.17.3", - "hbs": "^4.2.0", "http-errors": "^2.0.0", "morgan": "^1.10.0" } -} +} \ No newline at end of file diff --git a/1-Authentication/5-sign-in-express/App/public/stylesheets/style.css b/1-Authentication/5-sign-in-express/App/public/stylesheets/style.css deleted file mode 100644 index 9453385b9..000000000 --- a/1-Authentication/5-sign-in-express/App/public/stylesheets/style.css +++ /dev/null @@ -1,8 +0,0 @@ -body { - padding: 50px; - font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; -} - -a { - color: #00B7FF; -} diff --git a/1-Authentication/5-sign-in-express/App/routes/auth.js b/1-Authentication/5-sign-in-express/App/routes/auth.js deleted file mode 100644 index 0b2982817..000000000 --- a/1-Authentication/5-sign-in-express/App/routes/auth.js +++ /dev/null @@ -1,9 +0,0 @@ -const express = require('express'); -const authController = require('../controller/authController'); -const router = express.Router(); - -router.get('/signin', authController.signIn); -router.get('/signout', authController.signOut); -router.post('/redirect', authController.handleRedirect); - -module.exports = router; diff --git a/1-Authentication/5-sign-in-express/App/routes/index.js b/1-Authentication/5-sign-in-express/App/routes/index.js deleted file mode 100644 index d0465d3e9..000000000 --- a/1-Authentication/5-sign-in-express/App/routes/index.js +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. - */ - -const express = require('express'); -const router = express.Router(); - -router.get('/', function (req, res, next) { - res.render('index', { - title: 'MSAL Node & Express Web App', - isAuthenticated: req.session.isAuthenticated, - username: req.session.account?.username !== '' ? req.session.account?.username : req.session.account?.name, - }); -}); - -module.exports = router; diff --git a/1-Authentication/5-sign-in-express/App/routes/users.js b/1-Authentication/5-sign-in-express/App/routes/users.js deleted file mode 100644 index 0241153f0..000000000 --- a/1-Authentication/5-sign-in-express/App/routes/users.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. - */ - -const express = require('express'); -const router = express.Router(); - -// custom middleware to check auth state -function isAuthenticated(req, res, next) { - if (!req.session.isAuthenticated) { - return res.redirect('/auth/signin'); // redirect to sign-in route - } - - next(); -}; - -router.get('/id', - isAuthenticated, // check if user is authenticated - async function (req, res, next) { - res.render('id', { idTokenClaims: req.session.account.idTokenClaims }); - } -); - -module.exports = router; diff --git a/1-Authentication/5-sign-in-express/App/server.js b/1-Authentication/5-sign-in-express/App/server.js deleted file mode 100644 index 9ca694e2f..000000000 --- a/1-Authentication/5-sign-in-express/App/server.js +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Module dependencies. - */ - -var app = require('./app'); -var debug = require('debug')('msal:server'); -var http = require('http'); - -/** - * Get port from environment and store in Express. - */ - -var port = normalizePort(process.env.PORT || '3000'); -app.set('port', port); - -/** - * Create HTTP server. - */ - -var server = http.createServer(app); - -/** - * Listen on provided port, on all network interfaces. - */ - -server.listen(port); -server.on('error', onError); -server.on('listening', onListening); - -/** - * Normalize a port into a number, string, or false. - */ - -function normalizePort(val) { - var port = parseInt(val, 10); - - if (isNaN(port)) { - // named pipe - return val; - } - - if (port >= 0) { - // port number - return port; - } - - return false; -} - -/** - * Event listener for HTTP server "error" event. - */ - -function onError(error) { - if (error.syscall !== 'listen') { - throw error; - } - - var bind = typeof port === 'string' - ? 'Pipe ' + port - : 'Port ' + port; - - // handle specific listen errors with friendly messages - switch (error.code) { - case 'EACCES': - console.error(bind + ' requires elevated privileges'); - process.exit(1); - break; - case 'EADDRINUSE': - console.error(bind + ' is already in use'); - process.exit(1); - break; - default: - throw error; - } -} - -/** - * Event listener for HTTP server "listening" event. - */ - -function onListening() { - console.log(`Server listening on port ${port}`); - - var addr = server.address(); - var bind = typeof addr === 'string' - ? 'pipe ' + addr - : 'port ' + addr.port; - debug('Listening on ' + bind); -} diff --git a/1-Authentication/5-sign-in-express/App/views/error.hbs b/1-Authentication/5-sign-in-express/App/views/error.hbs deleted file mode 100644 index 065976562..000000000 --- a/1-Authentication/5-sign-in-express/App/views/error.hbs +++ /dev/null @@ -1,3 +0,0 @@ -

{{message}}

-

{{error.status}}

-
{{error.stack}}
diff --git a/1-Authentication/5-sign-in-express/App/views/id.hbs b/1-Authentication/5-sign-in-express/App/views/id.hbs deleted file mode 100644 index 92ddfc7f5..000000000 --- a/1-Authentication/5-sign-in-express/App/views/id.hbs +++ /dev/null @@ -1,16 +0,0 @@ -

Azure AD

-

ID Token

- - - {{#each idTokenClaims}} - - - - - {{/each}} - -
{{@key}}{{this}}
-
-Learn about claims in this ID token -
-Go back diff --git a/1-Authentication/5-sign-in-express/App/views/index.hbs b/1-Authentication/5-sign-in-express/App/views/index.hbs deleted file mode 100644 index 97a6f7820..000000000 --- a/1-Authentication/5-sign-in-express/App/views/index.hbs +++ /dev/null @@ -1,10 +0,0 @@ -

{{title}}

-{{#if isAuthenticated }} -

Hi {{username}}!

-View ID token claims -
-Sign out -{{else}} -

Welcome to {{title}}

-Sign in -{{/if}} diff --git a/1-Authentication/5-sign-in-express/App/views/layout.hbs b/1-Authentication/5-sign-in-express/App/views/layout.hbs deleted file mode 100644 index 069e5b294..000000000 --- a/1-Authentication/5-sign-in-express/App/views/layout.hbs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - {{title}} - - - - - {{{body}}} - - - diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..a80afe046 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "ms-identity-ciam-javascript-tutorial", + "lockfileVersion": 3, + "requires": true, + "packages": {} +}