Microsoft Authentication Library (MSAL)

JavaScriptTypeScriptMicrosoftAzure ADOAuthOpenID ConnectAuthentication LibrarySingle Sign-On

Authentication Library

Microsoft Authentication Library (MSAL)

Overview

Microsoft Authentication Library (MSAL) is a library for authenticating users and acquiring security tokens to call protected APIs using Microsoft Azure Active Directory and Microsoft Personal Accounts.

Details

MSAL (Microsoft Authentication Library) is an authentication library developed as part of the Microsoft identity platform (formerly Azure AD v2.0). It enables you to acquire security tokens to call protected APIs using industry-standard OAuth 2.0 and OpenID Connect protocols.

It supports multiple platforms and languages, with versions available for JavaScript/TypeScript, .NET, Java, Python, Android, and iOS/macOS. It covers a wide range of application types from browser-based applications (SPA), Node.js applications, desktop applications, to mobile applications.

For JavaScript, it's provided as MSAL Browser (for browsers), MSAL Node (for Node.js), MSAL Angular (Angular integration), and MSAL React (React integration). It supports various authentication flows including Authorization Code Flow (with PKCE), Implicit Flow, Device Code Flow, Client Credentials Flow, and On-Behalf-Of Flow for different authentication scenarios.

Token management features include automatic management of access tokens and refresh tokens, silent token renewal, and token caching capabilities. It also supports Azure AD B2C, multi-tenant authentication, conditional access, and MFA (multi-factor authentication), meeting a wide range of security requirements in enterprise environments.

Integration libraries for popular frameworks like Angular and React are also provided, making it easy to incorporate features such as route guards, HTTP interceptors, and authentication state management. Seamless integration with Microsoft Graph API and other Azure services is possible, making it optimized for enterprise-grade application development.

Pros and Cons

Pros

  • Microsoft Ecosystem Integration: Complete integration with Azure AD, Microsoft 365, and Dynamics 365
  • Multi-Platform Support: Supports web, mobile, and desktop applications
  • Rich Authentication Flows: Supports various authentication scenarios and security requirements
  • Automatic Token Management: Automatic renewal of access tokens and refresh tokens
  • Enterprise Features: Conditional access, MFA, and B2C support
  • Framework Integration: Dedicated integration libraries for Angular and React
  • Security Standards Compliance: OAuth 2.0 and OpenID Connect compliant
  • Comprehensive Documentation: Detailed Microsoft official documentation and samples

Cons

  • Microsoft Dependency: Limited support for non-Microsoft identity providers
  • High Learning Curve: Requires understanding of Azure AD and Microsoft identity platform
  • Configuration Complexity: Complex initial setup in enterprise environments
  • Bundle Size: Larger size compared to other lightweight authentication libraries
  • Vendor Lock-in: High dependency on Microsoft ecosystem
  • Over-engineering: May have too many features for simple authentication needs
  • Update Frequency: Requires updates due to Microsoft policy changes

Key Links

Code Examples

Basic Configuration (Browser)

import { PublicClientApplication } from "@azure/msal-browser";

const msalConfig = {
    auth: {
        clientId: "your_client_id_here",
        authority: "https://login.microsoftonline.com/your_tenant_id",
        redirectUri: "http://localhost:3000",
        postLogoutRedirectUri: "http://localhost:3000",
    },
    cache: {
        cacheLocation: "localStorage",
        storeAuthStateInCookie: false,
    },
    system: {
        loggerOptions: {
            loggerCallback: (level, message, containsPii) => {
                console.log(message);
            },
            logLevel: "Info",
            piiLoggingEnabled: false,
        },
    },
};

const msalInstance = new PublicClientApplication(msalConfig);

Popup Authentication Flow

const loginRequest = {
    scopes: ["openid", "profile", "User.Read"],
    prompt: "select_account"
};

async function signInPopup() {
    try {
        const loginResponse = await msalInstance.loginPopup(loginRequest);
        console.log("Login successful:", loginResponse);
        
        const currentAccounts = msalInstance.getAllAccounts();
        if (currentAccounts.length > 0) {
            const account = currentAccounts[0];
            console.log("Signed in user:", account.username);
        }
    } catch (error) {
        console.error("Login failed:", error);
    }
}

// Token acquisition (Silent → Popup fallback)
async function getTokenPopup(account) {
    const tokenRequest = {
        scopes: ["User.Read"],
        account: account
    };

    try {
        // Try silent acquisition first
        const response = await msalInstance.acquireTokenSilent(tokenRequest);
        return response.accessToken;
    } catch (error) {
        // Fallback to popup if silent fails
        console.log("Silent token acquisition failed, falling back to popup");
        const response = await msalInstance.acquireTokenPopup(tokenRequest);
        return response.accessToken;
    }
}

Redirect Authentication Flow

// Setup redirect handler
msalInstance.handleRedirectPromise().then((tokenResponse) => {
    if (tokenResponse !== null) {
        // Login successful
        const account = tokenResponse.account;
        const accessToken = tokenResponse.accessToken;
        console.log("Redirect login successful:", account.username);
    } else {
        // Check existing session
        const currentAccounts = msalInstance.getAllAccounts();
        if (currentAccounts.length > 0) {
            const account = currentAccounts[0];
            console.log("User already signed in:", account.username);
        }
    }
}).catch((error) => {
    console.error("Error handling redirect:", error);
});

function signInRedirect() {
    const loginRequest = {
        scopes: ["openid", "profile", "User.Read"],
        redirectUri: "http://localhost:3000/redirect"
    };
    
    msalInstance.loginRedirect(loginRequest);
}

async function getTokenRedirect(account) {
    const tokenRequest = {
        scopes: ["User.Read"],
        account: account
    };

    try {
        return await msalInstance.acquireTokenSilent(tokenRequest);
    } catch (error) {
        console.log("Silent token acquisition failed, using redirect");
        return msalInstance.acquireTokenRedirect(tokenRequest);
    }
}

Server-side Implementation with Node.js

const msal = require('@azure/msal-node');

const config = {
    auth: {
        clientId: "your_client_id_here",
        authority: "https://login.microsoftonline.com/your_tenant_id",
        clientSecret: "your_client_secret_here",
    },
    system: {
        loggerOptions: {
            loggerCallback(loglevel, message, containsPii) {
                console.log(message);
            },
            piiLoggingEnabled: false,
            logLevel: msal.LogLevel.Verbose,
        }
    }
};

const cca = new msal.ConfidentialClientApplication(config);

// Authorization Code Flow (Step 1: Generate auth URL)
async function getAuthUrl() {
    const authCodeUrlParameters = {
        scopes: ["user.read"],
        redirectUri: "http://localhost:3000/redirect",
    };

    try {
        const response = await cca.getAuthCodeUrl(authCodeUrlParameters);
        return response;
    } catch (error) {
        console.error("Error generating auth URL:", error);
    }
}

// Authorization Code Flow (Step 2: Acquire token)
async function acquireTokenByCode(authCode) {
    const tokenRequest = {
        code: authCode,
        redirectUri: "http://localhost:3000/redirect",
        scopes: ["user.read"],
    };

    try {
        const response = await cca.acquireTokenByCode(tokenRequest);
        return response;
    } catch (error) {
        console.error("Error acquiring token:", error);
    }
}

Angular Integration

// app.module.ts
import { NgModule } from '@angular/core';
import { 
    MsalModule, 
    MsalInterceptor, 
    MSAL_INSTANCE,
    MSAL_GUARD_CONFIG,
    MSAL_INTERCEPTOR_CONFIG
} from '@azure/msal-angular';
import { PublicClientApplication } from '@azure/msal-browser';

export function MSALInstanceFactory() {
    return new PublicClientApplication({
        auth: {
            clientId: 'your_client_id',
            authority: 'https://login.microsoftonline.com/your_tenant_id',
            redirectUri: '/',
        },
        cache: {
            cacheLocation: 'localStorage',
        },
    });
}

@NgModule({
    imports: [
        MsalModule
    ],
    providers: [
        {
            provide: MSAL_INSTANCE,
            useFactory: MSALInstanceFactory,
        },
        {
            provide: MSAL_GUARD_CONFIG,
            useValue: {
                interactionType: 'redirect',
                authRequest: {
                    scopes: ['user.read'],
                },
            },
        },
        {
            provide: MSAL_INTERCEPTOR_CONFIG,
            useValue: {
                interactionType: 'redirect',
                protectedResourceMap: new Map([
                    ['https://graph.microsoft.com/v1.0/me', ['user.read']],
                ]),
            },
        },
    ],
})
export class AppModule { }
// auth.service.ts
import { Injectable } from '@angular/core';
import { MsalService } from '@azure/msal-angular';

@Injectable({
    providedIn: 'root',
})
export class AuthService {
    constructor(private msalService: MsalService) {}

    login() {
        this.msalService.loginPopup({
            scopes: ['user.read'],
        });
    }

    logout() {
        this.msalService.logout();
    }

    isLoggedIn(): boolean {
        return this.msalService.instance.getAllAccounts().length > 0;
    }

    async getAccessToken(): Promise<string> {
        const accounts = this.msalService.instance.getAllAccounts();
        
        if (accounts.length > 0) {
            const request = {
                scopes: ['user.read'],
                account: accounts[0],
            };

            try {
                const response = await this.msalService.acquireTokenSilent(request).toPromise();
                return response.accessToken;
            } catch (error) {
                const response = await this.msalService.acquireTokenPopup(request).toPromise();
                return response.accessToken;
            }
        }
        
        throw new Error('No account found');
    }
}

Microsoft Graph API Calls

async function callMicrosoftGraph(accessToken) {
    const headers = new Headers();
    headers.append("Authorization", `Bearer ${accessToken}`);

    const options = {
        method: "GET",
        headers: headers
    };

    try {
        const response = await fetch("https://graph.microsoft.com/v1.0/me", options);
        const data = await response.json();
        return data;
    } catch (error) {
        console.error("Error calling Microsoft Graph:", error);
    }
}

// Usage example
async function getUserProfile() {
    const accounts = msalInstance.getAllAccounts();
    
    if (accounts.length > 0) {
        const tokenResponse = await getTokenPopup(accounts[0]);
        const userProfile = await callMicrosoftGraph(tokenResponse);
        console.log("User profile:", userProfile);
    }
}

Device Code Flow

// Device Code Flow (Node.js)
const pca = new msal.PublicClientApplication(config);

const deviceCodeRequest = {
    deviceCodeCallback: (response) => {
        console.log("Device code:", response.userCode);
        console.log("Verification URL:", response.verificationUri);
        console.log("Message:", response.message);
    },
    scopes: ["user.read"],
};

try {
    const response = await pca.acquireTokenByDeviceCode(deviceCodeRequest);
    console.log("Token acquisition successful:", response.accessToken);
} catch (error) {
    console.error("Device code flow failed:", error);
}

Sign Out

async function signOut() {
    const logoutRequest = {
        account: msalInstance.getActiveAccount(),
        postLogoutRedirectUri: "http://localhost:3000",
    };

    try {
        await msalInstance.logoutPopup(logoutRequest);
        console.log("Logout successful");
    } catch (error) {
        console.error("Logout failed:", error);
    }
}

// Or sign out with redirect
function signOutRedirect() {
    const logoutRequest = {
        account: msalInstance.getActiveAccount(),
        postLogoutRedirectUri: "http://localhost:3000",
    };

    msalInstance.logoutRedirect(logoutRequest);
}