Skip to content

Simple wrapper around MSAL Node for handling authN/authZ in Express.js web apps with the Microsoft identity platform

License

Notifications You must be signed in to change notification settings

thangchung/msal-express-wrapper

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

71 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MSAL Express Wrapper (Beta)

Node.js CI Code Scanning Typedoc License: MIT


This project illustrates a simple wrapper around the ConfidentialClientApplication class of the Microsoft Authentication Library for Node.js (MSAL Node), in order to streamline routine authentication tasks such as login, logout and token acquisition, as well as securing routes and controlling access.

This is an open source project. Suggestions and contributions are welcome!

đź“Ł This project backs the tutorial: Enable your Node.js web app to sign-in users and call APIs with the Microsoft identity platform

Features

⚠️ Protected web API scenarios are currently not supported.

Prerequisites

Installation

    npm install azure-samples/msal-express-wrapper 

or download and extract the repository .zip file.

Build

    git clone https://github.com/Azure-Samples/msal-express-wrapper.git
    cd msal-express-wrapper
    npm install
    npm run build

ℹ️ This project is generated using tsdx.

Getting started

Check out the demo app. Read below for how to configure it.

Configuration

  1. Initialize the wrapper in your app by providing a settings object. The object looks like the follows:
const appSettings = {
    appCredentials: {
        clientId: "CLIENT_ID", // Application (client) ID on Azure AD
        tenantId: "TENANT_ID", // alt. "common" "organizations" "consumers"
        clientSecret: "CLIENT_SECRET" // alt. client certificate or key vault credential
    },
    authRoutes: {
        redirect: "/redirect", // redirect URI configured on Azure AD
        error: "/error", // errors will be redirected to this route
        unauthorized: "/unauthorized" // unauthorized access attempts will be redirected to this route
    },
    remoteResources: {
        graphAPI: {
            endpoint: "https://graph.microsoft.com/v1.0/me", // Microsoft Graph
            scopes: ["user.read"]
        },
        armAPI: {
            endpoint: "https://management.azure.com/tenants?api-version=2020-01-01", // Azure REST API
            scopes: ["https://management.azure.com/user_impersonation"]
        }
    }
}
  1. If you are authenticating with Azure AD B2C, user-flows should be provided as well. The first item is used as default authority.
const appSettings = {
        // ...
        b2cPolicies: {
            signUpSignIn: {
                authority: "https://fabrikamb2c.b2clogin.com/fabrikamb2c.onmicrosoft.com/B2C_1_susi"
            }
        }
    }

Integration with Express.js

Import the package and instantiate AuthProvider class, which exposes the middleware you can use in your routes. The constructor takes the settings object and an (optional) persistent cache:

const express = require('express');
const session = require('express-session');
const msalWrapper = require('msal-express-wrapper');

const settings = require('./appSettings');
const cache = require('./utils/cachePlugin');
const router = require('./routes/router');

const SERVER_PORT = process.env.PORT || 4000;

const app = express();

app.use(session({
    secret: 'ENTER_YOUR_SECRET_HERE',
    resave: false,
    saveUninitialized: false,
    cookie: {
        secure: false, // set to true on deployment
    }
}));

const authProvider = new msalWrapper.AuthProvider(settings, cache);

app.use(authProvider.initialize()); // initialize default routes

app.use(router(authProvider)); // use authProvider in routers downstream

app.listen(SERVER_PORT, () => console.log(`Server is listening on port ${SERVER_PORT}!`));

The wrapper stores user data on req.session variable. Below are some of the useful variables:

  • req.session.isAuthenticated: indicates if user is currently authenticated (boolean)
  • req.session.account: MSAL.js account object containing useful information like ID token claims (see AccountInfo)
  • req.session.remoteResources.{resourceName}: Contains parameters related to an Azure AD / Azure AD B2C protected resource, including raw access tokens (see Resource)

Middleware

Authentication

Add signIn() and signOut() middleware to routes you want users to sign-in and sign-out, respectively. You will need to pass the routes for redirect after as parameters to each:

const express = require('express');
const appSettings = require('../appSettings');
const mainController = require('../controllers/mainController');

module.exports = (authProvider) => {

    // initialize router
    const router = express.Router();

    router.get('/', (req, res, next) => res.redirect('/home'));

    router.get('/home', (req, res, next) => {
        res.render('home', { isAuthenticated: req.session.isAuthenticated });
    });

    // unauthorized
    router.get('/error', (req, res) => res.redirect('/401.html'));

    // error
    router.get('/unauthorized', (req, res) => res.redirect('/500.html'));

    // auth routes
    router.get('/signin',
        authProvider.signIn({
            successRedirect: "/",
        }),
    );

    router.get('/signout',
        authProvider.signOut({
            successRedirect: "/",
        }),
    );

    router.get('*', (req, res) => res.redirect('/404.html'));

    return router;
}

Securing routes

Simply add the isAuthenticated() middleware before the controller that serves the page you would like to secure:

// secure routes
app.get('/id', 
    authProvider.isAuthenticated({
            unauthorizedRedirect: "/sign-in"
        }
    ), // checks if authenticated via session
    (req, res, next) => {
        res.render('id', { isAuthenticated: req.session.isAuthenticated, claims: req.session.account.idTokenClaims });
    }
);

Acquiring tokens

getToken() can be used before middleware that calls a web API. The access token will be available via req.session:

    router.get('/profile',
        authProvider.isAuthenticated(),
        authProvider.getToken({
            resource: {
                endpoint: "https://graph.microsoft.com/v1.0/me",
                scopes: [ "User.Read" ]
            }
        }),
        async(req, res, next) => {        
            try {
                // use axios or a similar alternative
                const response = await axios.default.get("https://graph.microsoft.com/v1.0/me", {
                    headers: {
                        Authorization: `Bearer ${accessToken}`
                    }
                });

                res.render('profile', { isAuthenticated: req.session.isAuthenticated, profile: response.data });
            } catch (error) {
                console.log(error);
                next(error);
            }
        }
    ); // get token for this route to call web API

Controlling access

Use hasAccess() middleware to control access for Azure AD App Roles and/or Security Groups:

    router.use('/admin',
        authProvider.isAuthenticated(),
        authProvider.hasAccess({
            accessRule: {
                methods: [ "GET", "POST", "DELETE" ],
                roles: [ "admin_role" ]
            }
        }),
        (req, res) => {
            const users = User.getAllUsers();
        
            res.render('dashboard', { isAuthenticated: req.session.isAuthenticated, users: users });
        }
    );

Remarks

Session support

Session support in this sample is provided by the express-session package using in-memory session store. in-memory session store is unfit for production, and you should either use a compatible session store or implement your own storage solution.

Persistent caching

MSAL Node has an in-memory cache by default. The demo app also features a persistent cache plugin in order to save the cache to disk. This plugin is not meant to be production-ready. As such, you might want to implement persistent caching using a 3rd party library like redis.

Information

About

Simple wrapper around MSAL Node for handling authN/authZ in Express.js web apps with the Microsoft identity platform

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Languages

  • TypeScript 100.0%