CSRF protection in frontend frameworks (such as React, Vue, Angular) needs to consider framework characteristics and best practices to ensure effective security protection in Single Page Applications (SPAs).
CSRF Protection in React
1. Using CSRF Token
jsx// Get CSRF Token import { useEffect, useState } from 'react'; function CSRFProtectedForm() { const [csrfToken, setCsrfToken] = useState(''); const [formData, setFormData] = useState({ name: '', email: '' }); useEffect(() => { // Get CSRF Token from server fetch('/api/csrf-token') .then(res => res.json()) .then(data => setCsrfToken(data.csrfToken)); }, []); const handleSubmit = async (e) => { e.preventDefault(); try { const response = await fetch('/api/submit', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken // Send Token in request header }, body: JSON.stringify(formData) }); if (response.ok) { alert('Submission successful!'); } else { alert('Submission failed'); } } catch (error) { console.error('Error:', error); } }; return ( <form onSubmit={handleSubmit}> <input type="text" value={formData.name} onChange={(e) => setFormData({...formData, name: e.target.value})} placeholder="Name" /> <input type="email" value={formData.email} onChange={(e) => setFormData({...formData, email: e.target.value})} placeholder="Email" /> <button type="submit">Submit</button> </form> ); }
2. Using Axios Interceptors
jsximport axios from 'axios'; // Create Axios instance const api = axios.create({ baseURL: '/api', withCredentials: true // Allow sending cookies }); // Request interceptor: automatically add CSRF Token api.interceptors.request.use(async (config) => { // For request methods that need CSRF protection if (['post', 'put', 'patch', 'delete'].includes(config.method)) { try { const response = await axios.get('/api/csrf-token'); config.headers['X-CSRF-Token'] = response.data.csrfToken; } catch (error) { console.error('Failed to get CSRF token:', error); } } return config; }); // Usage example function SubmitForm() { const handleSubmit = async () => { try { await api.post('/submit', { data: 'example' }); alert('Submission successful!'); } catch (error) { alert('Submission failed'); } }; return <button onClick={handleSubmit}>Submit</button>; }
CSRF Protection in Vue
1. Using Vue Router and Axios
vue<template> <form @submit.prevent="handleSubmit"> <input v-model="formData.name" placeholder="Name" /> <input v-model="formData.email" placeholder="Email" /> <button type="submit">Submit</button> </form> </template> <script> import axios from 'axios'; export default { data() { return { csrfToken: '', formData: { name: '', email: '' } }; }, async created() { // Get CSRF Token const response = await axios.get('/api/csrf-token'); this.csrfToken = response.data.csrfToken; }, methods: { async handleSubmit() { try { const response = await axios.post('/api/submit', this.formData, { headers: { 'X-CSRF-Token': this.csrfToken } }); if (response.data.success) { alert('Submission successful!'); } } catch (error) { alert('Submission failed'); } } } }; </script>
2. Using Vuex to Manage CSRF Token
javascript// store/csrf.js import axios from 'axios'; export default { namespaced: true, state: { token: null }, mutations: { SET_TOKEN(state, token) { state.token = token; } }, actions: { async fetchToken({ commit }) { try { const response = await axios.get('/api/csrf-token'); commit('SET_TOKEN', response.data.csrfToken); } catch (error) { console.error('Failed to fetch CSRF token:', error); } } } };
CSRF Protection in Angular
1. Using HttpClient Interceptors
typescript// csrf.interceptor.ts import { Injectable } from '@angular/core'; import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http'; import { Observable, from } from 'rxjs'; import { switchMap } from 'rxjs/operators'; @Injectable() export class CsrfInterceptor implements HttpInterceptor { constructor(private http: HttpClient) {} intercept( req: HttpRequest<any>, next: HttpHandler ): Observable<HttpEvent<any>> { // For request methods that need CSRF protection if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) { return from(this.getCsrfToken()).pipe( switchMap(token => { const csrfReq = req.clone({ setHeaders: { 'X-CSRF-Token': token } }); return next.handle(csrfReq); }) ); } return next.handle(req); } private async getCsrfToken(): Promise<string> { const response = await this.http.get<{ csrfToken: string }>('/api/csrf-token').toPromise(); return response.csrfToken; } } // app.module.ts import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; import { CsrfInterceptor } from './csrf.interceptor'; @NgModule({ imports: [HttpClientModule], providers: [ { provide: HTTP_INTERCEPTORS, useClass: CsrfInterceptor, multi: true } ] }) export class AppModule {}
General Best Practices
1. Token Refresh Strategy
javascript// General Token refresh logic class CSRFTokenManager { constructor() { this.token = null; this.tokenExpiry = null; } async getToken() { // Check if Token has expired if (!this.token || Date.now() > this.tokenExpiry) { await this.refreshToken(); } return this.token; } async refreshToken() { const response = await fetch('/api/csrf-token'); const data = await response.json(); this.token = data.csrfToken; this.tokenExpiry = Date.now() + 3600000; // Expire after 1 hour } clearToken() { this.token = null; this.tokenExpiry = null; } }
2. Error Handling and Retry
javascript// Request with retry mechanism async function makeRequestWithRetry(url, data, maxRetries = 3) { let retries = 0; while (retries < maxRetries) { try { const token = await csrfTokenManager.getToken(); const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': token }, body: JSON.stringify(data) }); if (response.status === 403) { // Token may have expired, refresh and retry csrfTokenManager.clearToken(); retries++; continue; } return await response.json(); } catch (error) { retries++; if (retries >= maxRetries) { throw error; } } } }
3. Security Configuration
javascript// Cookie configuration (server-side) res.cookie('sessionId', sessionId, { httpOnly: true, // Prevent XSS theft secure: true, // HTTPS only sameSite: 'strict', // Strictest CSRF protection maxAge: 3600000 // Expire after 1 hour });
Framework-Specific Considerations
React
- Use Context API to share CSRF Token
- Consider using React Query or SWR for request management
- Clean up Token when component unmounts
Vue
- Use Vuex or Pinia to manage Token state
- Use Vue lifecycle hooks to get Token
- Consider using VueUse's useFetch
Angular
- Use HTTP interceptors to automatically handle Token
- Use dependency injection to manage Token service
- Use RxJS for async operations
CSRF protection in frontend frameworks needs to combine framework characteristics and best practices to ensure good user experience without sacrificing security.