WakaStart
Sécurité

Pièges classiques d'intégration

Les 10 erreurs les plus fréquentes lors de l'intégration d'une Wakapp : symptôme, cause et solution.

Version v1.07 min de lecture

Pièges classiques d'intégration

Ce chapitre documente les erreurs observées en pratique lors de l'intégration de Wakapps tierces. Pour chaque piège : le symptôme observable, la cause racine et la solution.


1. « Aucun realm n'est associé à ce sous-domaine »

Symptôme : start-login retourne 404 not_found.

CauseDiagnosticSolution
Extraction du subdomain incorrecteLogs Discovery : host="app" au lieu de "app.test"Implémenter extractSubdomain avec strip du suffix plateforme
network.subdomain NULL ou différent en DBSELECT subdomain FROM networks WHERE wid='...'Mettre à jour le subdomain en DB
appId hardcodé ne match pas app.client_id (auto-généré du WID)Logs Discovery : appId="wakatest-app" mais DB a "7OWWAA"Ne pas envoyer appId — Discovery résout depuis le host
URL local (localhost) sans suffixLogs : host=""Implémenter un champ de saisie manuelle du subdomain en mode dev

Fonction extractSubdomain correcte :

typescript
function extractSubdomain(hostname: string): string { const suffix = process.env.NEXT_PUBLIC_PLATFORM_DOMAIN_SUFFIX; if (suffix && hostname.endsWith(`.${suffix}`)) { return hostname.slice(0, -(suffix.length + 1)); } const labels = hostname.split("."); if (labels.length <= 2) return ""; return labels.slice(0, -2).join("."); } // "app.test.wakastart-dev.app" (suffix="wakastart-dev.app") → "app.test" // "localhost" → ""

2. « Invalid client or Invalid client credentials » au callback

Symptôme : Le handler /api/auth/callback POST retourne 401 avec ce message Keycloak.

CauseDiagnosticSolution
Client OIDC en mode Client authentication: ONConsole Keycloak → Client → Capability configBasculer OFF (public client + PKCE)
client_id incorrect dans l'échange tokensessionStorage login_client_id vaut un fallback ("wakastart")Extraire client_id depuis la keycloakUrl AVANT le redirect
redirect_uri ne correspond pas exactementConsole Keycloak → Client → Access settingsAjouter l'URI exacte (sensible au trailing slash)
code_verifier recalculé après redirectLe verifier en sessionStorage a été effacéNe jamais effacer pkce_verifier avant le callback

Pattern de stockage correct :

typescript
// Stocker AVANT window.location.assign() const { keycloakUrl } = await startLogin(payload); const parsed = new URL(keycloakUrl); sessionStorage.setItem("login_realm", parsed.pathname.match(/\/realms\/([^/]+)\//)?.[1] ?? ""); sessionStorage.setItem("login_client_id", parsed.searchParams.get("client_id") ?? ""); // pkce_verifier déjà stocké juste après génération window.location.assign(keycloakUrl);

3. « Token enrichi manquant » sur tous les appels API

Symptôme : L'utilisateur est connecté (cookies existants, /me répond), mais tous les appels proxifiés vers les services downstream retournent 401 Token enrichi manquant ou invalide.

CauseDiagnosticSolution
Cookie wakastart_token absentDevTools → Application → CookiesL'appel /api/auth/enrich au callback a échoué — vérifier la réponse HTTP dans Network
Claim organization absent du JWT KeycloakDécoder le JWT — chercher "organization" ou "kc_org"Activer Organizations sur le realm + attacher le scope organization au client
Header x-enriched-token pas forwardé sur les appels proxifiésCapturer la requête : header présent ?Lire le cookie wakastart_token et l'ajouter dans x-enriched-token
keycloak_id en DB ne correspond pas au sub KeycloakLogs ws-serv-token : user not found for subSynchroniser les keycloak_id en DB (via backfill ou re-provisioning)
ws-serv-token en erreur PrismaLogs du pod token : Unknown field 'keycloakOrgId'Regénérer le client Prisma depuis ws-serv-config (npx prisma generate)

4. Claim organization absent — dashboard vide

Symptôme : L'utilisateur se connecte sans erreur, mais le dashboard est vide de droits et /me retourne appRights: [], wakaRoles: [].

Cause : Le token Keycloak ne contient pas le claim organization requis par ws-serv-token. L'enrichissement retourne un token minimal sans droits métier.

Solution — 3 étapes obligatoires :

  1. Console Keycloak → realm → Realm settings → General → activer « Organizations enabled »
  2. Console Keycloak → realm → Clients → {client_id} → Client scopes → ajouter organization en Default
  3. Exécuter npx ts-node prisma/backfill-keycloak-orgs.ts dans ws-serv-config pour créer les organisations Keycloak et y rattacher les users.

Vérification : Décoder le token Keycloak après login. Il doit contenir :

json
{ "organization": { "ACM001": {} } }

Symptôme : Après le redirect Keycloak vers /api/auth/callback, les cookies ne sont pas transmis. Le handler ne peut pas lire pkce_verifier ou les cookies de session. L'utilisateur est redirigé en boucle vers le login.

Cause : Les cookies avec sameSite: strict ne sont pas transmis lors d'une navigation cross-site (le redirect Keycloak → votre app est considéré cross-site).

Solution : Utiliser sameSite: lax pour tous les cookies du flow auth.

typescript
cookieStore.set("keycloak_token", token, { httpOnly: true, secure: true, sameSite: "lax", // ← pas "strict" ... });

6. Build Next.js du container échoue — EACCES sur .next/cache/images

Symptôme :

Error: EACCES: permission denied, mkdir '/app/.next/cache/images'

Cause : Le COPY .next ./.next dans le Dockerfile ne transfère pas les permissions correctes au user non-root du container.

Solution :

dockerfile
# Dans le stage production du Dockerfile Wakapp FROM node:22-alpine AS runner RUN addgroup --system --gid 1001 nodejs && \ adduser --system --uid 1001 nextjs WORKDIR /app COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static # Créer le dossier de cache images avec les bonnes permissions RUN mkdir -p /app/.next/cache/images && chown -R nextjs:nodejs /app/.next/cache USER nextjs

7. Rate limit déclenché sur Discovery en intégration initiale

Symptôme : Après quelques tests rapides, le service Discovery répond 429 pour toutes les requêtes.

Cause : En phase d'intégration, les développeurs appellent start-login en boucle pour déboguer. La limite 5 req/s est atteinte rapidement.

Solution :

  • Ne pas appeler start-login à chaque refresh de page — mémoriser la keycloakUrl si l'utilisateur n'est pas connecté.
  • En intégration locale, utiliser un mock du service Discovery.
  • Attendre l'expiration de la fenêtre (Retry-After) avant de retenter.

8. redirect_uri avec trailing slash

Symptôme : 400 invalid_redirect_uri lors de l'échange token avec Keycloak.

Cause : Keycloak compare l'URI de callback exactement (sensible au / final et au casing).

Configuré dans KeycloakEnvoyé au token endpointRésultat
https://app.test/callbackhttps://app.test/callback/invalid_redirect_uri
https://app.test/callback/https://app.test/callbackinvalid_redirect_uri
https://app.test/callbackhttps://app.test/callbackOK

Solution : Normaliser l'URI de callback (supprimer le trailing slash) et s'assurer que la valeur est identique dans start-login, dans le handler callback, et dans la console Keycloak.

typescript
const REDIRECT_URI = process.env.AUTH_CALLBACK_URL!.replace(/\/$/, ""); // Trim trailing slash

9. appRights vides malgré un profil assigné

Symptôme : GET /me retourne appRights: [] mais l'utilisateur a bien un profil assigné visible dans l'UI admin.

Causes et diagnostics :

CauseDiagnosticSolution
Le profil existe en DB mais aucun AppRight n'y est associéAdmin → Profils → vérifier les droits du profilAssigner des droits au profil
Le profil est assigné sur un autre CustomerVérifier profile.customer_id vs user.customer_idLes profils sont scopés par customer — créer un profil dans le bon customer
Cache /me stale côté frontLe context React n'a pas été invalidé après modificationAppeler auth.refresh() ou re-logger l'utilisateur

10. Email avec majuscules cause un 404 not_found Discovery

Symptôme : Lookup par email retourne { users: [] } pour John@Acme.com alors que l'utilisateur existe avec john@acme.com.

Cause : Discovery normalise les emails en lowercase avant la recherche. L'email envoyé doit déjà être normalisé côté client.

Solution :

typescript
// Normaliser l'email AVANT d'appeler Discovery const normalizedEmail = email.toLowerCase().trim(); const result = await discoverByEmail(normalizedEmail);

Récapitulatif — Checklist de débogage

Quand ça ne fonctionne pas, dans l'ordre :

  1. start-login retourne 404 → vérifier extractSubdomain, vérifier network.subdomain en DB
  2. Invalid client au callbackClient authentication: OFF dans Keycloak ? client_id extrait depuis keycloakUrl ?
  3. wakastart_token absent → l'enrich a-t-il réussi ? Décoder le JWT Keycloak : claim organization présent ?
  4. 401 Token enrichi manquant → header x-enriched-token forwardé par le proxy ?
  5. appRights: [] → profil assigné ET droits configurés sur le profil ?
  6. 403 → droit exact correspondant à l'opération ? Feature activée en cascade ?
  7. 429 → respecter Retry-After, implémenter backoff exponentiel

Aller plus loin