UNPKG

auth-vir

Version:

Auth made easy and secure via JWT cookies, CSRF tokens, and password hashing helpers.

137 lines (136 loc) 4.71 kB
import { clearAuthCookie, extractCookieJwt, generateAuthCookie, } from './cookie.js'; import { csrfTokenHeaderName, generateCsrfToken } from './csrf-token.js'; function readHeader(headers, headerName) { if (headers instanceof Headers) { return headers.get(headerName) || undefined; } else { const value = headers[headerName]; if (value == undefined) { return undefined; } else if (Array.isArray(value)) { return value[0]; } else { return String(value); } } } /** * Extract the user id from a request by checking both the request cookie and CSRF token. This is * used by host (backend) code to help verify a request. After extracting the user id using this, * you should compare it to users stored in your database. * * @category Auth : Host * @returns The extracted user id or `undefined` if no valid auth headers exist. */ export async function extractUserIdFromRequestHeaders(headers, jwtParams, cookieName) { try { const csrfToken = readHeader(headers, csrfTokenHeaderName); const cookie = readHeader(headers, 'cookie'); if (!cookie || !csrfToken) { return undefined; } const jwt = await extractCookieJwt(cookie, jwtParams, cookieName); if (!jwt || jwt.csrfToken !== csrfToken) { return undefined; } return jwt.userId; } catch { return undefined; } } /** * Extract a user id from just the cookie, without CSRF token validation. This is _less secure_ than * {@link extractUserIdFromRequestHeaders} as a result. This should only be used in rare * circumstances where you cannot rely on client-side JavaScript to insert the CSRF token. * * @deprecated Prefer {@link extractUserIdFromRequestHeaders} instead: it is more secure. */ export async function extractUserIdFromCookieAlone(headers, jwtParams, cookieName) { try { const cookie = readHeader(headers, 'cookie'); if (!cookie) { return undefined; } const jwt = await extractCookieJwt(cookie, jwtParams, cookieName); if (!jwt) { return undefined; } return jwt.userId; } catch { return undefined; } } /** * Used by host (backend) code to set headers on a response object. * * @category Auth : Host */ export async function generateSuccessfulLoginHeaders( /** The id from your database of the user you're authenticating. */ userId, cookieConfig) { const csrfToken = generateCsrfToken(); return { 'set-cookie': await generateAuthCookie({ csrfToken, userId, }, cookieConfig), [csrfTokenHeaderName]: csrfToken, }; } /** * Used by host (backend) code to set headers on a response object when the user has logged out or * failed to authorize. * * @category Auth : Host */ export function generateLogoutHeaders(...params) { return { 'set-cookie': clearAuthCookie(...params), [csrfTokenHeaderName]: 'redacted', }; } /** * Store auth data on a client (frontend) after receiving an auth response from the host (backend). * Specifically, this stores the CSRF token into local storage (which doesn't need to be a secret). * Alternatively, if the given response failed, this will wipe the existing (if anyone) stored CSRF * token. * * @category Auth : Client * @throws Error if no CSRF token header is found. */ export function handleAuthResponse(response, overrides = {}) { if (!response.ok) { wipeCurrentCsrfToken(overrides); return; } const headerName = overrides.csrfHeaderName || csrfTokenHeaderName; const csrfToken = response.headers.get(headerName); if (!csrfToken) { wipeCurrentCsrfToken(overrides); throw new Error('Did not receive any CSRF token.'); } (overrides.localStorage || globalThis.localStorage).setItem(headerName, csrfToken); } /** * Used in client (frontend) code to retrieve the current CSRF token in order to send it with * requests to the host (backend). * * @category Auth : Client */ export function getCurrentCsrfToken(overrides = {}) { return ((overrides.localStorage || globalThis.localStorage).getItem(overrides.csrfHeaderName || csrfTokenHeaderName) || undefined); } /** * Wipes the current stored CSRF token. This should be used by client (frontend) code to logout a * user or react to a session timeout. * * @category Auth : Client */ export function wipeCurrentCsrfToken(overrides = {}) { return (overrides.localStorage || globalThis.localStorage).removeItem(overrides.csrfHeaderName || csrfTokenHeaderName); }