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.

120 lines (110 loc) 5.11 kB
import type { AccountInfo, AuthenticationResult, PublicClientApplication } from '@azure/msal-node'; import { AuthServerError, NoAccountsError, SilentTokenAcquisitionError } from './error.js'; import type { IAuthProvider } from './AuthProvider.interface.js'; /** * Implementation of the authentication provider for the Fusion MSAL Node module. * * Implements {@link IAuthProvider} and provides methods for managing authentication * and token acquisition using the MSAL (Microsoft Authentication Library) for Node.js. * * This implementation assumes the user is already logged in and does not support * triggering interactive login or logout flows. The `login` method will always throw, and `logout` * only clears the token cache. * * @see AuthProviderInteractive For interactive login/logout support (user-driven authentication flows). * @see AuthProviderTokenOnly For scenarios where a pre-obtained token is used (automation, CI/CD, etc). * * Developers extending this class can add support for additional authentication flows or modify token * acquisition logic. Ensure that any changes remain consistent with the interface contract. */ export class AuthProvider implements IAuthProvider { constructor(protected _client: PublicClientApplication) {} /** * Retrieves the first account from the list of all accounts available in the MSAL client. * * @returns A promise that resolves to the first `AccountInfo` object if available, or `null` if no accounts exist. */ public async getAccount(): Promise<AccountInfo | null> { const accounts = await this._client.getAllAccounts(); return accounts[0] ?? null; } /** * Acquires an access token for the specified scopes. * * @param options - An object containing the options for acquiring the token. * @param options.scopes - An array of strings representing the scopes for which the access token is requested. * @returns A promise that resolves to the acquired access token as a string. * @throws An error if the token acquisition process fails. */ public async acquireAccessToken(options: { scopes: string[]; }): Promise<string> { const { accessToken } = await this.acquireToken(options); return accessToken; } /** * Initiates the login process with the specified options. * * @param _options - An object containing the scopes required for authentication. * @returns A promise that resolves to an `AuthenticationResult` upon successful login. * @throws `AuthServerError` - Always throws this error as login is not supported in this implementation. * * @remarks * This method is not supported and is intended to be overridden by `AuthProviderInteractive`. */ public async login(_options: { scopes: string[] }): Promise<AuthenticationResult> { throw new AuthServerError('Login not supported, use AuthProviderInteractive instead'); } /** * Logs out the user by clearing the token cache and removing all accounts. * * This method retrieves all accounts from the token cache and removes them * individually. Afterward, it clears the entire cache to ensure no residual * authentication data remains. * * @returns A promise that resolves when the logout process is complete. */ public async logout() { const cache = this._client.getTokenCache(); const accounts = await cache.getAllAccounts(); for (const account of accounts) { await cache.removeAccount(account); } this._client.clearCache(); } /** * 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. */ public async acquireToken(options: { scopes: string[]; }): Promise<AuthenticationResult> { const account = await this.getAccount(); if (!account) { throw new NoAccountsError('No accounts found in cache'); } try { const tokenResponse = await this._client.acquireTokenSilent({ scopes: options.scopes, account, }); return tokenResponse; } catch (error) { throw new SilentTokenAcquisitionError('Error acquiring token', { cause: error, }); } } }