Ce que vous saurez faire à la fin
- Sécuriser l’auth avec Argon2id + JWT rotatifs
- Implémenter OAuth 2.1 + PKCE
- Protéger contre OWASP Top 10
- Rate-limit et audit log immuable
- Scan secrets automatique en CI
Étape 1 — Argon2id
import argon2 from "argon2";
const hash = await argon2.hash(pwd, {
type: argon2.argon2id,
memoryCost: 65536,
timeCost: 3,
parallelism: 2,
});
if (!(await argon2.verify(hash, pwdFourni))) {
await new Promise(r => setTimeout(r, 300));
throw new Error("identifiants invalides");
}
Étape 2 — JWT avec refresh rotatif
import jwt from "jsonwebtoken";
import crypto from "crypto";
function issueTokens(user: { id: number }) {
const access = jwt.sign({ sub: user.id }, process.env.ACCESS_SECRET!,
{ expiresIn: "15m" });
const jti = crypto.randomUUID();
const refresh = jwt.sign({ sub: user.id, jti }, process.env.REFRESH_SECRET!,
{ expiresIn: "7d" });
db.query("INSERT INTO refresh_tokens(jti,user_id,expires_at) VALUES($1,$2,now()+interval '7 days')",
[jti, user.id]);
return { access, refresh };
}
Étape 3 — Validation Zod
import { z } from "zod";
const CreerUser = z.object({
email: z.string().email().max(255),
telephone: z.string().regex(/^\+221 ?[0-9]{9}$/),
});
app.post("/users", (req, res) => {
const r = CreerUser.safeParse(req.body);
if (!r.success) return res.status(422).json({ errors: r.error.errors });
});
Étape 4 — Rate limiting
import { rateLimit } from "express-rate-limit";
const loginLimiter = rateLimit({
windowMs: 15 * 60_000,
max: 5,
keyGenerator: req => req.ip + ":" + (req.body.email || ""),
});
app.post("/auth/login", loginLimiter, loginHandler);
Étape 5 — SQL paramétré
// JAMAIS
db.query(`SELECT * FROM users WHERE email='${req.body.email}'`);
// TOUJOURS
db.query("SELECT * FROM users WHERE email = $1", [req.body.email]);
Étape 6 — Anti-SSRF
const BLOQUES = [/^10\./, /^192\.168\./, /^127\./, /^169\.254\./];
async function fetchExterne(url: string) {
const ip = await dns.lookup(new URL(url).hostname);
if (BLOQUES.some(re => re.test(ip.address))) throw new Error("IP interne");
return fetch(url, { redirect: "error" });
}
Étape 7 — CORS strict
import cors from "cors";
app.use(cors({
origin: ["https://app.example.sn"],
credentials: true,
}));
Étape 8 — Audit log immuable
CREATE TABLE audit_log (
id BIGSERIAL PRIMARY KEY,
ts TIMESTAMPTZ DEFAULT now(),
user_id BIGINT,
action TEXT NOT NULL,
ressource TEXT NOT NULL,
ip INET,
result TEXT
);
REVOKE UPDATE, DELETE ON audit_log FROM PUBLIC;
Étape 9 — Webhooks signés
function verifyWebhook(req, secret: string) {
const ts = parseInt(req.headers["x-timestamp"]);
if (Math.abs(Date.now() - ts) > 5 * 60_000) throw new Error("stale");
const expected = crypto.createHmac("sha256", secret)
.update(`${ts}.${req.rawBody}`).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(expected),
Buffer.from(req.headers["x-signature"])))
throw new Error("invalid signature");
}
Étape 10 — Scan secrets gitleaks
brew install gitleaks
gitleaks detect --source . --verbose
# Pre-commit hook
echo "gitleaks protect --staged --verbose" > .husky/pre-commit
Checklist
✓ Argon2id
✓ JWT 15 min + refresh 7j révocable
✓ Zod sur 100% des endpoints
✓ Rate limit login + global
✓ SQL paramétré uniquement
✓ CORS whitelist
✓ Audit log append-only
✓ gitleaks pre-commit
✓ Secrets dans vault
Besoin d'un site web ?
Confiez-nous la Création de Votre Site Web
Site vitrine, e-commerce ou application web — nous transformons votre vision en réalité digitale. Accompagnement personnalisé de A à Z.
À partir de 250.000 FCFA
Parlons de Votre Projet
Publicité