Sécurité
Bonnes pratiques — Sécurité
Tokens, secrets, HTTPS, CSP, logs : les règles de sécurité non négociables pour une Wakapp en production.
Version v1.03 min de lecture
Bonnes pratiques — Sécurité
Ce chapitre compile les patterns de sécurité éprouvés et les anti-patterns observés sur des intégrations réelles. À lire avant de commencer, à relire avant de merger.
1. Tokens en cookies HttpOnly uniquement
typescript// BIEN : cookie HttpOnly, jamais accessible depuis JavaScript cookieStore.set("keycloak_token", token, { httpOnly: true, secure: true }); // MAL : accessible depuis JS, vulnérable aux XSS localStorage.setItem("token", token); sessionStorage.setItem("token", token); // acceptable UNIQUEMENT pour pkce_verifier pendant le flow
2. PKCE obligatoire — pas de flow implicite
Le flow implicite (response_type=token) est déprécié et interdit. PKCE S256 est l'unique flow supporté.
3. Pas de secrets côté client
typescript// BIEN : API key dans une variable d'env côté serveur const apiKey = process.env.WAKASTART_API_KEY; // server-only // MAL : exposé dans le bundle frontend const apiKey = "sk_live_..."; // visible dans les DevTools
4. Backend vérifie toujours, frontend masque
typescript// BIEN côté backend NestJS Wakapp @UseGuards(AppRightGuard) @RequireAppRight("users.delete") async deleteUser(@Param("id") id: string) { ... } // BIEN côté frontend (masquage UI uniquement) <Protected right="users.delete"> <DeleteButton /> </Protected> // MAL : vérification uniquement côté frontend if (user?.appRights.includes("users.delete")) { await deleteUser(id); // l'API l'autorisera de toute façon si le backend ne vérifie pas }
5. Messages d'erreur génériques côté login
typescript// BIEN : ne révèle pas si l'email est connu catch (err) { setError("Impossible de se connecter. Vérifiez vos identifiants et réessayez."); } // MAL : révèle l'existence du compte catch (err) { if (err.status === 404) setError("Cet email n'existe pas dans notre système."); }
6. HTTPS en production, CSP renforcée
typescript// next.config.ts const securityHeaders = [ { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" }, { key: "X-Content-Type-Options", value: "nosniff" }, { key: "X-Frame-Options", value: "DENY" }, { key: "Content-Security-Policy", value: "default-src 'self'; script-src 'self' 'unsafe-inline'; ..." }, { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" }, ];
7. Ne jamais logger les tokens
typescript// BIEN : redacter le header Authorization dans les logs logger.info("API call", { url: apiUrl, headers: { ...headers, Authorization: "[REDACTED]", "x-enriched-token": "[REDACTED]" }, }); // MAL : token complet dans les logs logger.debug("Request headers", { headers }); // expose le Bearer
8. Ne jamais détenir INTERNAL_API_SECRET
Ce secret n'est utilisé qu'entre services internes Wakastellar. Aucune Wakapp tierce n'en a besoin et n'en aura jamais besoin.
Gestion des erreurs 401, 403, 429
typescriptasync function apiFetch(url: string, options: RequestInit) { const res = await fetch(url, options); switch (res.status) { case 401: // Token expiré → tenter refresh, puis redirect login const refreshed = await tryRefreshTokens(); if (!refreshed) window.location.assign("/login"); break; case 403: // Droits insuffisants → afficher message explicite, ne pas retenter throw new ForbiddenError(await res.json()); case 429: // Rate limit → backoff exponentiel const retryAfter = parseInt(res.headers.get("Retry-After") ?? "2", 10); await sleep(retryAfter * 1000); return apiFetch(url, options); // 1 seul retry case 402: // Crédits insuffisants → rediriger vers achat router.push("/billing/credits"); break; default: if (!res.ok) throw new ApiError(res.status, await res.json()); } return res.json(); }
Tests de sécurité
21. Ne jamais tester contre la production
Utilisez un Customer de test dédié (avec CUSTOMER_WID=TST001) et une App de test.
22. Mocker les réponses Discovery et enrich en tests unitaires
typescript// jest.setup.ts jest.mock("@/lib/api/discovery", () => ({ startLogin: jest.fn().mockResolvedValue({ keycloakUrl: "https://auth.test/realms/test/protocol/openid-connect/auth?client_id=TEST", }), }));
23. Tests d'intégration avec un compte service
Pour les tests e2e, utilisez une clé API dédiée (sk_test_...) avec des droits minimaux.
Checklist sécurité pré-production
- Tokens stockés en cookies HttpOnly (jamais localStorage)
- Proxy serveur implémenté (browser ne contacte pas directement l'API)
- PKCE S256 (pas de flow implicite)
- HTTPS avec HSTS et CSP
- Logs sans tokens (Bearer et x-enriched-token redactés)
- 401 → refresh → login (pas de boucle infinie)
- 403 → message explicite à l'utilisateur
- 429 → backoff + Retry-After respecté
- Pas d'
INTERNAL_API_SECRETdans le pod - Test avec différents niveaux admin (WakaAdmin, CustomerAdmin, User)
Aller plus loin
- Performance & Observabilité : cache, retry, métriques, tracing
- Pièges classiques : diagnostic des erreurs fréquentes