UNPKG

@csrf-armor/nextjs

Version:

CSRF protection middleware for Next.js applications

182 lines (180 loc) 5.33 kB
'use client'; //#region src/client/client.ts /** * Retrieves the current CSRF token from client-side storage. * * Attempts to find the CSRF token in the following order: * 1. HTTP-only cookie (primary source for most strategies) * 2. Meta tag fallback (for server-side rendered pages) * * The token retrieval is optimized for client-side access while maintaining * security through HTTP-only cookies where possible. * * @param config - Optional configuration for cookie and header names * @returns The CSRF token string, or null if not found or not in browser * * @example * ```typescript * // Basic usage * const token = getCsrfToken(); * * // With custom configuration * const token = getCsrfToken({ * cookieName: 'my-csrf-cookie' * }); * * if (token) { * // Use token in requests * fetch('/api/data', { * headers: { 'X-CSRF-Token': token } * }); * } * ``` */ function getCsrfToken(config) { if (typeof window === "undefined") return null; const cookieName = config?.cookieName ?? "csrf-token"; const csrfCookie = document.cookie.split(";").find((c) => c.trim().startsWith(`${cookieName}=`)); if (csrfCookie) { const value = csrfCookie.slice(csrfCookie.indexOf("=") + 1).trim(); try { return decodeURIComponent(value); } catch { return value; } } return document.querySelector("meta[name=\"csrf-token\"]")?.getAttribute("content") ?? null; } /** * Creates HTTP headers object with CSRF token for requests. * * Convenience function that retrieves the current CSRF token and formats * it as headers ready to be used with fetch() or other HTTP clients. * * @param config - Optional configuration for cookie and header names * @returns Headers object with CSRF token, or empty object if no token * * @example * ```typescript * // Basic usage * const headers = createCsrfHeaders(); * fetch('/api/data', { * method: 'POST', * headers * }); * * // With custom header name * const headers = createCsrfHeaders({ * headerName: 'X-Custom-CSRF' * }); * * // Merge with existing headers * const headers = { * 'Content-Type': 'application/json', * ...createCsrfHeaders() * }; * ``` */ function createCsrfHeaders(config) { const token = getCsrfToken(config); if (!token) return {}; return { [config?.headerName ?? "x-csrf-token"]: token }; } /** * Enhanced fetch function with automatic CSRF token injection. * * A drop-in replacement for the standard fetch() function that automatically * includes the CSRF token in request headers. Merges CSRF headers with any * existing headers in the request. * * @param input - URL or Request object for the fetch * @param init - Optional request initialization options * @param config - Optional CSRF configuration * @returns Promise resolving to fetch Response * * @example * ```typescript * // Basic POST request with automatic CSRF protection * const response = await csrfFetch('/api/data', { * method: 'POST', * body: JSON.stringify({ name: 'John' }), * headers: { 'Content-Type': 'application/json' } * }); * * // GET request (CSRF token included for consistency) * const data = await csrfFetch('/api/users').then(r => r.json()); * * // With custom configuration * const response = await csrfFetch('/api/data', { * method: 'DELETE' * }, { * headerName: 'X-Custom-CSRF', * cookieName: 'my-csrf-token' * }); * ``` */ function csrfFetch(input, init, config) { const headers = new Headers(input instanceof Request ? input.headers : void 0); for (const [key, value] of new Headers(init?.headers)) headers.set(key, value); for (const [key, value] of Object.entries(createCsrfHeaders(config))) headers.set(key, value); return fetch(input, { ...init, headers }); } /** * Refreshes the CSRF token by making a lightweight server request. * * Sends a HEAD request to the server to trigger token refresh, then returns * the new token from response headers or cookies. This is useful when tokens * expire or when you need to ensure you have the latest token. * * The function gracefully handles failures by returning the current token * if the refresh request fails. * * @param config - Optional configuration for endpoints and header names * @returns Promise resolving to the refreshed token, or null if unavailable * * @example * ```typescript * // Basic token refresh * const newToken = await refreshCsrfToken(); * if (newToken) { * console.log('Token refreshed:', newToken); * } * * // Refresh with custom endpoint * const token = await refreshCsrfToken({ * refreshEndpoint: '/api/csrf/refresh', * headerName: 'X-Custom-CSRF' * }); * * // Use in error handling * try { * await csrfFetch('/api/data', { method: 'POST' }); * } catch (error) { * if (error.status === 403) { * await refreshCsrfToken(); * // Retry the request * } * } * ``` */ async function refreshCsrfToken(config) { if (typeof window === "undefined") return null; try { const response = await fetch(config?.refreshEndpoint ?? window.location.pathname, { method: "HEAD", credentials: "same-origin" }); const headerName = config?.headerName ?? "x-csrf-token"; const newToken = response.headers.get(headerName); if (newToken) return newToken; return getCsrfToken(config); } catch { return getCsrfToken(config); } } //#endregion export { createCsrfHeaders, csrfFetch, getCsrfToken, refreshCsrfToken }; //# sourceMappingURL=client.js.map