1.0.0-beta • Published 2 years ago

microsoft-identity-express v1.0.0-beta

Weekly downloads
-
License
MIT
Repository
-
Last release
2 years ago

microsoft-identity-express (beta)

Build E2E 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. In doing so it takes inspiration from the Microsoft.Identity.Web with respect to developer experience.

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

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

Features

:warning: Protected web API scenarios are currently not supported.

Prerequisites

Installation

    npm install azure-samples/microsoft-identity-express 

or download and extract the repository .zip file.

Build

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

:information_source: This project is generated using tsdx.

Getting started

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 path or the full URI configured on Azure AD
        unauthorized: "/unauthorized" // unauthorized access attempts will be redirected to this route
        frontChannelLogout: "/sso_logout" // front-channel logout path or the full URI configured on Azure AD
    },
    protectedResources: {
        graphAPI: {
            endpoint: "https://graph.microsoft.com/v1.0/me", // Microsoft Graph API
            scopes: ["user.read"]
        },
        armAPI: {
            endpoint: "https://management.azure.com/tenants?api-version=2020-01-01", // Azure Resource Manager 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 MsalWebAppAuthClient class, via the WebAppAuthClientBuilder, 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 MsIdExpress = require('microsoft-identity-express');

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 msid = new MsIdExpress.WebAppAuthClientBuilder(appSettings).build();

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

app.use(router(msid)); // use MsalWebAppAuthClient 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.protectedResources.<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 = (msid) => {

    // 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 });
    });

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

    router.get('/signout',
        msid.signOut({
            postLogoutRedirect: "/",
        }),
    );

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

    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', 
    msid.isAuthenticated(), // 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',
        msid.isAuthenticated(),
        msid.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 ${req.session["graphAPI"].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',
        msid.isAuthenticated(),
        msid.hasAccess({
            accessRule: {
                methods: [ "GET", "POST", "DELETE" ],
                roles: [ "admin_role" ]
            }
        }),
        (req, res) => {
            res.render('dashboard', { isAuthenticated: req.session.isAuthenticated });
        }
    );

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. This is not meant to be production-ready. As such, you might want to implement persistent caching using a 3rd party library like redis.

Information