Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | /** * CSRF Token Hook * * Provides CSRF token management for React components. * Uses NextAuth's built-in CSRF token endpoint. * * Usage: * ```typescript * const { csrfToken, getCsrfHeaders, isLoading } = useCsrfToken(); * * // Use in fetch requests * const response = await fetch('/api/some-endpoint', { * method: 'POST', * headers: { * 'Content-Type': 'application/json', * ...getCsrfHeaders(), * }, * body: JSON.stringify(data), * }); * ``` */ "use client"; import { useState, useEffect, useCallback } from "react"; const CSRF_HEADER_NAME = "x-csrf-token"; const TOKEN_REFRESH_INTERVAL = 50 * 60 * 1000; // 50 minutes interface UseCsrfTokenReturn { csrfToken: string | null; isLoading: boolean; error: Error | null; getCsrfHeaders: () => Record<string, string>; refreshToken: () => Promise<void>; } export function useCsrfToken(): UseCsrfTokenReturn { const [csrfToken, setCsrfToken] = useState<string | null>(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState<Error | null>(null); const fetchToken = useCallback(async () => { try { setIsLoading(true); setError(null); // Use NextAuth's built-in CSRF endpoint const response = await fetch("/api/auth/csrf", { method: "GET", credentials: "same-origin"}); if (!response.ok) { throw new Error(`Failed to fetch CSRF token: ${response.status}`); } const data = await response.json(); // NextAuth returns { csrfToken: "..." } setCsrfToken(data.csrfToken); } catch (err) { setError(err instanceof Error ? err : new Error("Unknown error")); } finally { setIsLoading(false); } }, []); // Fetch token on mount useEffect(() => { fetchToken(); }, [fetchToken]); // Auto-refresh token periodically useEffect(() => { const intervalId = setInterval(fetchToken, TOKEN_REFRESH_INTERVAL); return () => clearInterval(intervalId); }, [fetchToken]); // Helper function to get headers for requests const getCsrfHeaders = useCallback((): Record<string, string> => { if (!csrfToken) { return {}; } return { [CSRF_HEADER_NAME]: csrfToken}; }, [csrfToken]); return { csrfToken, isLoading, error, getCsrfHeaders, refreshToken: fetchToken}; } /** * Higher-order function to add CSRF headers to fetch requests * * Usage: * ```typescript * const { getCsrfHeaders } = useCsrfToken(); * const secureFetch = createSecureFetch(getCsrfHeaders); * * // All requests will include CSRF headers * const response = await secureFetch('/api/endpoint', { * method: 'POST', * body: JSON.stringify(data), * }); * ``` */ export function createSecureFetch( getCsrfHeaders: () => Record<string, string> ): typeof fetch { return async (input: RequestInfo | URL, init?: RequestInit) => { const csrfHeaders = getCsrfHeaders(); const mergedInit: RequestInit = { ...init, credentials: "same-origin", headers: { ...init?.headers, ...csrfHeaders}}; return fetch(input, mergedInit); }; } export default useCsrfToken; |