// Express.js avec cors
const express = require('express');
const cors = require('cors');
const app = express();
// Configuration simple
app.use(cors());
// Configuration détaillée
const corsOptions = {
origin: ['https://example.com', 'https://admin.example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
exposedHeaders: ['Content-Range', 'X-Content-Range'],
credentials: true,
maxAge: 3600,
optionsSuccessStatus: 204
};
app.use(cors(corsOptions));
// CORS dynamique
app.use(cors((req, callback) => {
const origin = req.header('Origin');
const allowedOrigins = [
'https://example.com',
'https://admin.example.com'
];
callback(null, {
origin: allowedOrigins.includes(origin),
methods: 'GET,POST,PUT,DELETE',
credentials: true
});
}));
// CORS par route
app.get('/api/public', cors(), (req, res) => {
res.json({ message: 'Public API' });
});
app.get('/api/private', cors(corsOptions), (req, res) => {
res.json({ message: 'Private API' });
});
// Express.js avec csurf
const csrf = require('csurf');
const cookieParser = require('cookie-parser');
// Middleware CSRF
app.use(cookieParser());
app.use(csrf({ cookie: true }));
// Middleware pour envoyer le token
app.use((req, res, next) => {
res.locals.csrfToken = req.csrfToken();
next();
});
// Route pour le formulaire
app.get('/form', (req, res) => {
res.render('form', {
csrfToken: req.csrfToken()
});
});
// Protection des routes POST
app.post('/submit', (req, res) => {
// CSRF vérifié automatiquement
res.json({ success: true });
});
// Gestion des erreurs CSRF
app.use((err, req, res, next) => {
if (err.code === 'EBADCSRFTOKEN') {
res.status(403).json({
error: 'Session invalide'
});
} else {
next(err);
}
});
// Template form.ejs
<form action="/submit" method="POST">
<input type="hidden"
name="_csrf"
value="<%= csrfToken %>">
<!-- autres champs -->
</form>
// React avec Axios
axios.defaults.xsrfCookieName = 'XSRF-TOKEN';
axios.defaults.xsrfHeaderName = 'X-XSRF-TOKEN';
axios.defaults.withCredentials = true;
// Backend (Express)
const crypto = require('crypto');
// Générer un token
function generateToken() {
return crypto.randomBytes(32).toString('hex');
}
// Stockage des tokens (en mémoire pour l'exemple)
const tokenStore = new Map();
// Middleware de vérification
const verifyToken = (req, res, next) => {
const csrfToken = req.header('X-CSRF-Token');
const sessionToken = tokenStore.get(req.sessionID);
if (!csrfToken || !sessionToken || csrfToken !== sessionToken) {
return res.status(403).json({
error: 'Invalid CSRF token'
});
}
next();
};
// Route pour obtenir un token
app.get('/csrf-token', (req, res) => {
const token = generateToken();
tokenStore.set(req.sessionID, token);
res.json({ token });
});
// Frontend (React)
class Api {
constructor() {
this.csrfToken = null;
}
async getCsrfToken() {
if (!this.csrfToken) {
const response = await fetch('/csrf-token', {
credentials: 'include'
});
const { token } = await response.json();
this.csrfToken = token;
}
return this.csrfToken;
}
async post(url, data) {
const token = await this.getCsrfToken();
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': token
},
credentials: 'include',
body: JSON.stringify(data)
});
}
}
// Backend (Express + ws)
const WebSocket = require('ws');
const url = require('url');
const wss = new WebSocket.Server({
server,
verifyClient: (info, cb) => {
const token = url.parse(info.req.url, true).query.token;
// Vérifier le token
if (!isValidToken(token)) {
cb(false, 403, 'Unauthorized');
return;
}
cb(true);
}
});
wss.on('connection', (ws, req) => {
const origin = req.headers.origin;
if (!isAllowedOrigin(origin)) {
ws.close();
return;
}
ws.on('message', (data) => {
// Traiter les messages
});
});
// Frontend
class SecureWebSocket {
constructor(url) {
this.url = url;
this.token = null;
}
async connect() {
if (!this.token) {
this.token = await this.getCsrfToken();
}
this.ws = new WebSocket(
`${this.url}?token=${this.token}`
);
this.ws.onmessage = this.onMessage.bind(this);
}
async getCsrfToken() {
const response = await fetch('/csrf-token', {
credentials: 'include'
});
const { token } = await response.json();
return token;
}
onMessage(event) {
// Traiter les messages
}
}
const request = require('supertest');
const app = require('../app');
describe('CORS', () => {
test('should allow requests from allowed origin', async () => {
const response = await request(app)
.get('/api/data')
.set('Origin', 'https://example.com');
expect(response.headers['access-control-allow-origin'])
.toBe('https://example.com');
});
test('should reject requests from invalid origin', async () => {
const response = await request(app)
.get('/api/data')
.set('Origin', 'https://evil.com');
expect(response.headers['access-control-allow-origin'])
.toBeUndefined();
});
});
describe('CSRF Protection', () => {
let csrfToken;
beforeEach(async () => {
const response = await request(app)
.get('/csrf-token')
.set('Cookie', ['connect.sid=test']);
csrfToken = response.body.token;
});
test('should accept valid CSRF token', async () => {
const response = await request(app)
.post('/api/data')
.set('Cookie', ['connect.sid=test'])
.set('X-CSRF-Token', csrfToken)
.send({ data: 'test' });
expect(response.status).toBe(200);
});
test('should reject invalid CSRF token', async () => {
const response = await request(app)
.post('/api/data')
.set('Cookie', ['connect.sid=test'])
.set('X-CSRF-Token', 'invalid')
.send({ data: 'test' });
expect(response.status).toBe(403);
});
});