Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/pages/Login/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import axios from "axios";
import { useNavigate, Link } from "react-router-dom";
import { ThemeContext } from "../../context/ThemeContext";
import type { ThemeContextType } from "../../context/ThemeContext";
import { csrfHeaders } from "../../utils/csrf";

const backendUrl = import.meta.env.VITE_BACKEND_URL;

Expand Down Expand Up @@ -30,7 +31,9 @@ const Login: React.FC = () => {
setIsLoading(true);

try {
const response = await axios.post(`${backendUrl}/api/auth/login`, formData);
const response = await axios.post(`${backendUrl}/api/auth/login`, formData, {
headers: csrfHeaders(),
});
setMessage(response.data.message);

if (response.data.message === 'Login successful') {
Expand Down
40 changes: 40 additions & 0 deletions src/utils/csrf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Client-side CSRF token utilities.
*
* Generates a per-session random token stored in sessionStorage and provides
* a helper that attaches it as the X-CSRF-Token header on state-mutating
* requests. The backend must validate this header against the value it set
* in the session. Without such a header, a cross-site form submission carries
* no distinguishing marker and cannot be rejected.
*/

const CSRF_TOKEN_KEY = 'csrf_token';

/**
* Return the existing session CSRF token, creating one if absent.
* The token is a 32-byte hex string generated via Web Crypto.
*/
export function getCsrfToken(): string {
const existing = sessionStorage.getItem(CSRF_TOKEN_KEY);
if (existing) return existing;

const bytes = new Uint8Array(32);
window.crypto.getRandomValues(bytes);
const token = Array.from(bytes)
.map((b) => b.toString(16).padStart(2, '0'))
.join('');

sessionStorage.setItem(CSRF_TOKEN_KEY, token);
return token;
}

/**
* Return an axios-compatible headers object that includes the CSRF token.
* Spread this into the headers of any POST / PUT / PATCH / DELETE request.
*
* @example
* axios.post(url, body, { headers: csrfHeaders() });
*/
export function csrfHeaders(): Record<string, string> {
return { 'X-CSRF-Token': getCsrfToken() };
}
Loading