@ownid/azure-b2c
Version:
Server-side library for integrating OwnID passwordless authentication with Azure Active Directory B2C
111 lines (110 loc) • 4.73 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OwnIDB2CAuth = void 0;
const microsoft_graph_client_1 = require("@microsoft/microsoft-graph-client");
const identity_1 = require("@azure/identity");
const crypto_1 = __importDefault(require("crypto"));
/**
* Authentication and validation utilities for OwnID Azure B2C integration
*/
class OwnIDB2CAuth {
constructor(config) {
this.config = config;
}
/**
* Verify that a request came from OwnID by checking the signature
*
* @param body - The request body
* @param headers - The request headers containing ownid-signature and ownid-timestamp
* @returns true if the signature is valid, throws an error otherwise
*/
verifyOwnIdRequest(body, headers) {
// Skip verification only if explicitly disabled
if (this.config.disableRequestVerification === true) {
return true;
}
// Require a shared secret for verification
if (!this.config.ownIdSharedSecret) {
throw new Error("Request verification failed: No shared secret provided. Set ownIdSharedSecret or explicitly disable verification with disableRequestVerification=true.");
}
const keyBuffer = Buffer.from(this.config.ownIdSharedSecret, 'base64');
const bodyString = JSON.stringify(body);
// Handle header values that might be strings or arrays
const ownIdSignature = this.getHeaderValue(headers['ownid-signature']);
const ownIdTimestamp = this.getHeaderValue(headers['ownid-timestamp']);
if (!ownIdSignature || !ownIdTimestamp) {
throw new Error("Request rejected: Missing required OwnID headers.");
}
const expirationTimeInSeconds = 60000; // 1 minute
if (Math.abs(Date.now() - parseInt(ownIdTimestamp)) > expirationTimeInSeconds) {
throw new Error("Request rejected: Signature has expired.");
}
const dataToSign = `${bodyString}.${ownIdTimestamp}`;
const hmac = crypto_1.default.createHmac('sha256', keyBuffer);
hmac.update(dataToSign);
const signature = hmac.digest('base64');
if (signature !== ownIdSignature) {
throw new Error("Request rejected: Invalid signature.");
}
return true;
}
/**
* Safely gets a single string value from a header that might be a string or string array
* @param headerValue - The header value which might be a string, string array, or undefined
* @returns A single string value or undefined
*/
getHeaderValue(headerValue) {
if (!headerValue) {
return undefined;
}
if (Array.isArray(headerValue)) {
return headerValue[0];
}
return headerValue;
}
/**
* Get a Microsoft Graph API client with proper authentication
* @returns Authenticated Microsoft Graph client
*/
getGraphClient() {
const credential = new identity_1.ClientSecretCredential(this.config.azureTenantId, this.config.azureClientId, this.config.azureClientSecret);
return microsoft_graph_client_1.Client.initWithMiddleware({
authProvider: {
getAccessToken: async () => (await credential.getToken("https://graph.microsoft.com/.default")).token
}
});
}
/**
* Get Azure AD B2C authentication tokens for a user
*
* @param userId - Azure B2C user ID
* @param email - User's email address
* @returns Authentication tokens
*/
async getTokens(userId, email) {
const credential = new identity_1.ClientSecretCredential(this.config.azureTenantId, this.config.azureClientId, this.config.azureClientSecret);
const response = await credential.getToken("https://graph.microsoft.com/.default");
return {
accessToken: response.token,
expiresOn: new Date(Date.now() + (response.expiresOnTimestamp - Date.now())),
scopes: ["https://graph.microsoft.com/.default"],
account: {
homeAccountId: userId,
environment: "login.microsoftonline.com",
tenantId: this.config.azureTenantId,
username: email
}
};
}
/**
* Get the attribute name for storing OwnID data in Azure B2C
* @returns The extension attribute name
*/
getOwnIdDataAttributeName() {
return `extension_${this.config.azureB2cExtensionAppId.replace(/-/g, '')}_ownIdData`;
}
}
exports.OwnIDB2CAuth = OwnIDB2CAuth;