123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- const msal = require('@azure/msal-node');
- const axios = require('axios');
- const { msalConfig } = require('../authConfig');
- class AuthProvider {
- msalConfig;
- cryptoProvider;
- constructor(msalConfig) {
- this.msalConfig = msalConfig
- this.cryptoProvider = new msal.CryptoProvider();
- };
- login(options = {}) {
- return async (req, res, next) => {
- /**
- * 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({
- successRedirect: options.successRedirect || '/',
- })
- );
-
- 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: options.scopes || [],
- redirectUri: options.redirectUri,
- };
- 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: options.scopes || [],
- redirectUri: options.redirectUri,
- };
- /**
- * 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. For more, see:
- * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/performance.md
- */
- if (!this.msalConfig.auth.cloudDiscoveryMetadata || !this.msalConfig.auth.authorityMetadata) {
- const [cloudDiscoveryMetadata, authorityMetadata] = await Promise.all([
- this.getCloudDiscoveryMetadata(this.msalConfig.auth.authority),
- this.getAuthorityMetadata(this.msalConfig.auth.authority)
- ]);
- this.msalConfig.auth.cloudDiscoveryMetadata = JSON.stringify(cloudDiscoveryMetadata);
- this.msalConfig.auth.authorityMetadata = JSON.stringify(authorityMetadata);
- }
- const msalInstance = this.getMsalInstance(this.msalConfig);
- console.log(msalInstance);
- // trigger the first leg of auth code flow
- return this.redirectToAuthCodeUrl(
- authCodeUrlRequestParams,
- authCodeRequestParams,
- msalInstance
- )(req, res, next);
- };
- }
- acquireToken(options = {}) {
- return async (req, res, next) => {
- try {
- const msalInstance = this.getMsalInstance(this.msalConfig);
- /**
- * If a token cache exists in the session, deserialize it and set it as the
- * cache for the new MSAL CCA instance. For more, see:
- * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/caching.md
- */
- if (req.session.tokenCache) {
- msalInstance.getTokenCache().deserialize(req.session.tokenCache);
- }
-
- const tokenResponse = await msalInstance.acquireTokenSilent({
- account: req.session.account,
- scopes: options.scopes || req.body.scopes || [],
- });
- /**
- * On successful token acquisition, write the updated token
- * cache back to the session. For more, see:
- * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/caching.md
- */
- req.session.tokenCache = msalInstance.getTokenCache().serialize();
- req.session.accessToken = tokenResponse.accessToken;
- req.session.idToken = tokenResponse.idToken;
- req.session.account = tokenResponse.account;
- req.session.apiUri = req.body.api_uri;
- req.session.param = req.body.param;
- res.redirect(options.successRedirect);
- } catch (error) {
- if (error instanceof msal.InteractionRequiredAuthError) {
- return this.login({
- scopes: options.scopes || [],
- redirectUri: options.redirectUri,
- successRedirect: options.successRedirect || '/',
- })(req, res, next);
- }
- next(error);
- }
- };
- }
- handleRedirect(options = {}) {
- return async (req, res, next) => {
- if (!req.body || !req.body.state) {
- return next(new Error('Error: response not found'));
- }
- if (!req.session || !req.session.pkceCodes) {
- return next(new Error('Error: session pkceCodes not found'));
- }
- const authCodeRequest = {
- ...req.session.authCodeRequest,
- code: req.body.code,
- codeVerifier: req.session.pkceCodes.verifier,
- };
- try {
- const msalInstance = this.getMsalInstance(this.msalConfig);
- if (req.session.tokenCache) {
- 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.successRedirect);
- } catch (error) {
- next(error);
- }
- }
- }
- logout(options = {}) {
- return (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
- */
- let logoutUri = `${this.msalConfig.auth.authority}/oauth2/v2.0/`;
- if (options.postLogoutRedirectUri) {
- logoutUri += `logout?post_logout_redirect_uri=${options.postLogoutRedirectUri}`;
- }
- req.session.destroy(() => {
- res.redirect(logoutUri);
- });
- }
- }
- /**
- * Instantiates a new MSAL ConfidentialClientApplication object
- * @param msalConfig: MSAL Node Configuration object
- * @returns
- */
- getMsalInstance(msalConfig) {
- return new msal.ConfidentialClientApplication(msalConfig);
- }
- /**
- * 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
- */
- redirectToAuthCodeUrl(authCodeUrlRequestParams, authCodeRequestParams, msalInstance) {
- return async (req, res, next) => {
- // 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,
- responseMode: msal.ResponseMode.FORM_POST, // recommended for confidential clients
- codeChallenge: req.session.pkceCodes.challenge,
- codeChallengeMethod: req.session.pkceCodes.challengeMethod,
- };
- req.session.authCodeRequest = {
- ...authCodeRequestParams,
- code: '',
- };
-
- try {
- console.log(req.session);
- const authCodeUrlResponse = await msalInstance.getAuthCodeUrl(req.session.authCodeUrlRequest);
- res.redirect(authCodeUrlResponse);
- } catch (error) {
- next(error);
- }
- };
- }
- /**
- * Retrieves cloud discovery metadata from the /discovery/instance endpoint
- * @returns
- */
- async getCloudDiscoveryMetadata(authority) {
- const endpoint = 'https://login.microsoftonline.com/common/discovery/instance';
- try {
- const response = await axios.get(endpoint, {
- params: {
- 'api-version': '1.1',
- 'authorization_endpoint': `${authority}/oauth2/v2.0/authorize`
- }
- });
- return await response.data;
- } catch (error) {
- throw error;
- }
- }
- /**
- * Retrieves oidc metadata from the openid endpoint
- * @returns
- */
- async getAuthorityMetadata(authority) {
- const endpoint = `${authority}/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);
- module.exports = authProvider;
|