UNPKG

@equinor/fusion-framework-module-msal-node

Version:

Fusion Framework module for secure Azure AD authentication in Node.js using MSAL. Supports interactive, silent, and token-only authentication modes with encrypted token storage.

122 lines 5.87 kB
import { createHash, randomBytes } from 'node:crypto'; import openBrowser from 'open'; import { createAuthServer } from './create-auth-server.js'; import { AuthProvider } from './AuthProvider.js'; /** * Encodes a byte buffer to Base64 URL encoding (RFC 4648 §5) without padding. * * Used for PKCE code verifier/challenge values (RFC 7636). * * @param buffer - Bytes to encode. * @returns Base64url-encoded string without trailing `=` padding. */ const base64UrlEncode = (buffer) => buffer.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); /** * Generates PKCE verifier and challenge for the authorization code flow. * * Uses a 32-octet cryptographically random verifier (recommended by RFC 7636) * and derives the S256 challenge as `BASE64URL-ENCODE(SHA256(ASCII(verifier)))`. * * @returns PKCE verifier and S256 challenge. */ const generatePkceCodes = () => { const verifier = base64UrlEncode(randomBytes(32)); const challenge = base64UrlEncode(createHash('sha256').update(verifier).digest()); return { verifier, challenge }; }; /** * Implementation of an interactive authentication provider for the Fusion MSAL Node module. * * Extends {@link AuthProvider} to support user-driven authentication flows using the authorization code flow with PKCE. * This class opens the user's default browser for authentication and handles the response via a local server. * * This implementation is intended for scenarios where interactive login is required, such as CLI tools or development utilities. * * Developers extending this provider can customize the interactive flow, server handling, or PKCE logic as needed. * Ensure that any changes remain consistent with the expected interface and security best practices. * * @see AuthProvider for non-interactive (silent) authentication flows. * @see AuthProviderTokenOnly for token-only scenarios. */ export class AuthProviderInteractive extends AuthProvider { #options; /** * @param client - The MSAL `PublicClientApplication` instance used for token operations. * @param options - Interactive login options, including local server configuration. */ constructor(client, options) { super(client); this.#options = options; } /** * Initiates the login process using the authorization code flow with PKCE. * * This method generates a PKCE code verifier and challenge to enhance security * and prevent authorization code interception attacks. It constructs an * authorization code URL, opens the default browser for user authentication, * and starts a local server to handle the authentication response. * * @param scopes - An array of scopes that specify the permissions being requested. * @returns A promise that resolves to an `AuthenticationResult` containing the * authentication details upon successful login. * * @throws Will throw an error if the PKCE code generation, browser opening, or * authentication server setup fails. */ async login(options) { const { scopes } = options.request; const { port, onOpen } = this.#options.server; // Generate a new PKCE code verifier and challenge // This is used to enhance security in the authorization code flow // by preventing authorization code interception attacks. const { verifier, challenge } = generatePkceCodes(); const authCodeUrl = await this._client.getAuthCodeUrl({ scopes, redirectUri: `http://localhost:${port}`, codeChallenge: challenge, codeChallengeMethod: 'S256', }); // open default browser to authenticate await openBrowser(authCodeUrl); // callback to open the auth code url if (onOpen) onOpen(authCodeUrl); return createAuthServer(this._client, scopes, { codeVerifier: verifier, port, }); } /** * Acquires an authentication token for the specified scopes. * * This method first attempts to acquire a token silently using the accounts * available in the token cache. If no accounts are found and interactive login * is allowed, it initiates an interactive login flow. If interactive login is * not allowed and no accounts are found, an error is thrown. * * @param scopes - An array of strings representing the scopes for which the token is requested. * @param options - Optional parameters for token acquisition. * @param options.interactive - A boolean indicating whether interactive login is allowed * if no accounts are found in the cache. Defaults to `false`. * @returns A promise that resolves to an `AuthenticationResult` containing the acquired token. * @throws {@link NoAccountsError} If no accounts are found in the cache and interactive login is not allowed. * @throws {@link SilentTokenAcquisitionError} If an error occurs during silent token acquisition. */ async acquireToken(options) { const { scopes } = options.request ?? { scopes: [] }; // No cached account — fall back to an interactive login flow if ((await this.getAccount()) === null) { return this.login({ request: { scopes } }); } try { return await super.acquireToken(options); } catch { // Silent acquisition failed (e.g. no cached token for this resource/audience). // Fall back to interactive login so the user only needs one browser session // rather than seeing an unhandled error or a separate prompted login elsewhere. return this.login({ request: { scopes } }); } } } //# sourceMappingURL=AuthProviderInteractive.js.map