UNPKG

@authlink/client

Version:

Official client SDK for integrating with the Authlink Identity Provider

198 lines (197 loc) 7.63 kB
import { createCodeVerifier, createCodeChallenge, generateRandomString, createSilentIframe, listenForWebMessage, mapTokenResponse, mapAuthorizeResponse, mapOpenIdConfiguration } from "../utils"; export class IdentityProviderClient { constructor(baseUrl) { this.codeVerifierKey = "authlink_code_verifier"; this.stateKey = "authlink_state"; this.nonceKey = "authlink_nonce"; this.baseUrl = baseUrl; } async authorize(options) { let { clientId, responseType, redirectUri, scope, codeChallenge, codeChallengeMethod, prompt, responseMode, state, nonce, loginHint, requestId } = options; const params = new URLSearchParams(); if (clientId) params.append("client_id", clientId); if (responseType) { params.append("response_type", responseType); if (responseType.includes("code")) { if (!codeChallenge) { let codeVerifier = createCodeVerifier(); localStorage.setItem(this.codeVerifierKey, codeVerifier); codeChallenge = await createCodeChallenge(codeVerifier, codeChallengeMethod ?? "S256"); } params.append("code_challenge", codeChallenge); params.append("code_challenge_method", codeChallengeMethod ?? "S256"); } } if (!nonce) { nonce = generateRandomString(32); localStorage.setItem(this.nonceKey, nonce); } params.append("nonce", nonce); if (redirectUri) params.append("redirect_uri", redirectUri); if (scope) params.append("scope", scope); if (prompt) params.append("prompt", prompt); if (responseMode) params.append("response_mode", responseMode); if (loginHint) params.append("login_hint", loginHint); if (requestId) params.append("request_id", requestId); if (!state) { state = generateRandomString(32); localStorage.setItem(this.stateKey, state); } params.append("state", state); const url = `${this.baseUrl}/api/connect/authorize?${params.toString()}`; if (options.autoRedirect ?? true) { window.location.href = url; return; } return url; } async authorizeSilently(options) { const state = options.state ?? generateRandomString(32); localStorage.setItem(this.stateKey, state); const silentOptions = { ...options, prompt: "none", responseMode: "web_message", state, autoRedirect: false }; const url = await this.authorize(silentOptions); const iframe = createSilentIframe(url); try { let json = await listenForWebMessage(state, this.baseUrl); return mapAuthorizeResponse(json); } finally { document.body.removeChild(iframe); } } async token(options) { const form = new URLSearchParams({ grant_type: options.grantType, client_id: options.clientId }); let codeVerifier = undefined; if (options.grantType === "authorization_code") codeVerifier = options.codeVerifier ?? localStorage.getItem(this.codeVerifierKey); if (options.clientSecret) form.append("client_secret", options.clientSecret); if (options.redirectUri) form.append("redirect_uri", options.redirectUri); if (options.code) form.append("code", options.code); if (codeVerifier) form.append("code_verifier", codeVerifier); if (options.refreshToken) form.append("refresh_token", options.refreshToken); if (options.username) form.append("username", options.username); if (options.password) form.append("password", options.password); if (options.scope) form.append("scope", options.scope); if (options.deviceCode) form.append("device_code", options.deviceCode); if (options.clientAssertionType) form.append("client_assertion_type", options.clientAssertionType); if (options.clientAssertion) form.append("client_assertion", options.clientAssertion); if (options.audience) form.append("audience", options.audience); const response = await fetch(`${this.baseUrl}/api/connect/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: form }); if (!response.ok) { const error = await response.json(); throw new { ...error, status: response.status }; } let json = await response.json(); return mapTokenResponse(json); } async logout(options) { const params = new URLSearchParams({ client_id: options.clientId, id_token_hint: options.idTokenHint, post_logout_redirect_uri: options.postLogoutRedirectUri }); if (options.state) params.append("state", options.state); else { const state = generateRandomString(32); localStorage.setItem(this.stateKey, state); params.append("state", state); } if (options.sessionId) params.append("session_id", options.sessionId); if (options.reason) params.append("logout_hint", options.reason); window.location.href = `${this.baseUrl}/api/connect/logout?${params.toString()}`; } validateState(state) { const storedState = localStorage.getItem(this.stateKey); return storedState === state; } validateNonce(nonce) { const storedNonce = localStorage.getItem(this.nonceKey); return storedNonce === nonce; } async jwks() { const response = await fetch(`${this.baseUrl}/api/connect/jwks`, { method: "GET" }); if (!response.ok) { const error = await response.json(); throw new { ...error, status: response.status }; } return await response.json(); } async configuration() { const response = await fetch(`${this.baseUrl}/.well-known/openid-configuration`, { method: "GET" }); if (!response.ok) { const error = await response.json(); throw new { ...error, status: response.status }; } let json = await response.json(); return mapOpenIdConfiguration(json); } async userInfo(accessToken) { const response = await fetch("/api/connect/userinfo", { method: "GET", headers: { "Authorization": `Bearer ${accessToken}`, "Content-Type": "application/json" } }); if (!response.ok) { const error = await response.json(); throw new { ...error, status: response.status }; } return await response.json(); } clear() { localStorage.removeItem(this.codeVerifierKey); localStorage.removeItem(this.stateKey); localStorage.removeItem(this.nonceKey); } getCodeVerifier() { return localStorage.getItem(this.codeVerifierKey); } getState() { return localStorage.getItem(this.stateKey); } getNonce() { return localStorage.getItem(this.nonceKey); } }