UNPKG

@shane32/msoauth

Version:

A React library for Azure AD authentication with PKCE (Proof Key for Code Exchange) flow support. This library provides a secure and easy-to-use solution for implementing Azure AD authentication in React applications, with support for both API and Microso

177 lines (176 loc) 8.24 kB
var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; /** * Decodes a JWT token and returns its payload. * @param {string} token - The JWT token to decode * @returns {JwtPayload} The decoded JWT payload */ function jwtDecode(token) { var base64Url = token.split(".")[1]; var base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); return JSON.parse(atob(base64)); } /** * Generates a cryptographically secure random state string for OAuth authentication. * Used to prevent CSRF attacks by ensuring the auth response matches the request. * @returns {string} A base64url-encoded random string */ export function generateState() { var array = new Uint8Array(32); window.crypto.getRandomValues(array); return bufferToBase64Url(array); } /** * Generates PKCE codes for OAuth authorization code flow with PKCE. * Creates a random code verifier and its corresponding SHA-256 hashed challenge. * @returns {Promise<PKCECodes>} Object containing the code verifier and challenge */ export function generatePKCECodes() { return __awaiter(this, void 0, void 0, function () { var codeVerifier, encoder, data, hash, codeChallenge; return __generator(this, function (_a) { switch (_a.label) { case 0: codeVerifier = generateState(); encoder = new TextEncoder(); data = encoder.encode(codeVerifier); return [4 /*yield*/, window.crypto.subtle.digest("SHA-256", data)]; case 1: hash = _a.sent(); codeChallenge = bufferToBase64Url(new Uint8Array(hash)); return [2 /*return*/, { codeVerifier: codeVerifier, codeChallenge: codeChallenge }]; } }); }); } /** * Converts a Uint8Array buffer to a base64url-encoded string. * Base64url encoding is similar to base64 but uses URL-safe characters. * @param {Uint8Array} buffer - The buffer to convert * @returns {string} The base64url-encoded string */ export function bufferToBase64Url(buffer) { // Convert buffer to base64 var base64 = btoa(String.fromCharCode.apply(null, Array.from(buffer))); // Make base64URL by replacing chars that are different return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); } /** * Gets the current relative URL of the window, including path, search, and hash. * @returns {string} The relative URL of the window */ export function getCurrentRelativeUrl() { // Convert full URL to path for React navigation var url = new URL(window.location.href); return url.pathname + url.search + url.hash; } /** * Extracts user information from a JWT token * @param {string} idToken - The JWT token to decode * @returns {UserInfo} The extracted user information * @throws {Error} If required claims are missing from the token */ export function extractUserInfo(idToken) { var decoded = jwtDecode(idToken); if (!decoded.oid && !decoded.sub) throw new Error("Both Object ID and Subject not found in token"); return __assign(__assign({}, decoded), { oid: decoded.oid || decoded.sub, roles: decoded.roles ? (Array.isArray(decoded.roles) ? decoded.roles : [decoded.roles]) : [] }); } /** * Extracts the expiration timestamp from a JWT token. * @param {string} token - The JWT token to decode * @returns {number} The expiration timestamp in milliseconds since Unix epoch * @throws {Error} If the token doesn't contain an expiration claim */ export function extractTokenExpiration(token) { if (!token) { throw new Error("Token is empty"); } var decoded = jwtDecode(token); if (!decoded.exp) { throw new Error("Token does not contain expiration claim"); } // Convert from seconds to milliseconds return decoded.exp * 1000; } /** * Converts a TokenInfo from older versions to version 3 * @param {any} tokenData - The token data to convert * @returns {TokenInfo} The converted token info */ export function convertTokenInfoToV3(tokenData) { // Check if it's the old TokenInfoV1 format with apiAccessToken and msAccessToken if (tokenData.version === 1 && "apiAccessToken" in tokenData && "msAccessToken" in tokenData) { var v1Token = tokenData; // Convert to version 3 format return { version: 3, refreshToken: v1Token.refreshToken, idToken: v1Token.idToken, idTokenExpiresAt: v1Token.idToken ? extractTokenExpiration(v1Token.idToken) : 0, accessTokens: { // Map API token to default scope set default: { token: v1Token.apiAccessToken, expiresAt: v1Token.apiExpiresAt, }, // Map MS Graph token to ms scope set ms: { token: v1Token.msAccessToken, expiresAt: v1Token.msExpiresAt, }, }, }; } // Check if it's version 2 format (missing idTokenExpiresAt) if (tokenData.version === 2) { return __assign(__assign({}, tokenData), { version: 3, idTokenExpiresAt: tokenData.idToken ? extractTokenExpiration(tokenData.idToken) : 0 }); } // It's already version 3 return tokenData; }