Common integration pitfalls
The 10 most frequent mistakes when integrating a Wakapp: symptom, cause and solution.
Common integration pitfalls
This chapter documents errors observed in practice when integrating third-party Wakapps. For each pitfall: the observable symptom, the root cause, and the solution.
1. "No realm is associated with this subdomain"
Symptom: start-login returns 404 not_found.
| Cause | Diagnosis | Solution |
|---|---|---|
| Incorrect subdomain extraction | Discovery logs: host="app" instead of "app.test" | Implement extractSubdomain with a strip of the platform suffix |
network.subdomain NULL or different in the DB | SELECT subdomain FROM networks WHERE wid='...' | Update the subdomain in the DB |
Hardcoded appId doesn't match app.client_id (auto-generated from the WID) | Discovery logs: appId="wakatest-app" but the DB has "7OWWAA" | Do not send appId — Discovery resolves it from the host |
Local URL (localhost) with no suffix | Logs: host="" | Implement a manual subdomain input field in dev mode |
Correct extractSubdomain function:
typescriptfunction 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.app" (suffix="wakastart.app") → "app.test" // "localhost" → ""
2. "Invalid client or Invalid client credentials" on callback
Symptom: The /api/auth/callback POST handler returns 401 with this Keycloak message.
| Cause | Diagnosis | Solution |
|---|---|---|
OIDC client in Client authentication: ON mode | Keycloak console → Client → Capability config | Switch it OFF (public client + PKCE) |
Incorrect client_id in the token exchange | sessionStorage login_client_id holds a fallback value ("wakastart") | Extract client_id from the keycloakUrl BEFORE the redirect |
redirect_uri doesn't match exactly | Keycloak console → Client → Access settings | Add the exact URI (sensitive to trailing slash) |
code_verifier recomputed after the redirect | The verifier in sessionStorage was cleared | Never clear pkce_verifier before the callback |
Correct storage pattern:
typescript// Store BEFORE 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 already stored right after generation window.location.assign(keycloakUrl);
3. "Missing enriched token" on every API call
Symptom: The user is logged in (cookies exist, /me responds), but every proxied call to downstream services returns 401 Missing or invalid enriched token.
| Cause | Diagnosis | Solution |
|---|---|---|
wakastart_token cookie absent | DevTools → Application → Cookies | The /api/auth/enrich call during the callback failed — check the HTTP response in Network |
organization claim absent from the Keycloak JWT | Decode the JWT — look for "organization" or "kc_org" | Enable Organizations on the realm + attach the organization scope to the client |
x-enriched-token header not forwarded on proxied calls | Capture the request: is the header present? | Read the wakastart_token cookie and add it as x-enriched-token |
keycloak_id in the DB doesn't match the Keycloak sub | WakaStart auth service logs: user not found for sub | Synchronize keycloak_id values in the DB (via backfill or re-provisioning) |
| WakaStart auth service failing with a Prisma error | Token pod logs: Unknown field 'keycloakOrgId' | Regenerate the Prisma client from the WakaStart config service (npx prisma generate) |
4. organization claim absent — empty dashboard
Symptom: The user logs in without error, but the dashboard has no rights and /me returns appRights: [].
Cause: The Keycloak token does not contain the organization claim required by the WakaStart auth service. Enrichment returns a minimal token with no business rights.
Solution — 3 mandatory steps:
- Keycloak console → realm → Realm settings → General → enable "Organizations enabled"
- Keycloak console → realm → Clients →
{client_id}→ Client scopes → addorganizationas Default - Run
npx ts-node prisma/backfill-keycloak-orgs.tsin the WakaStart config service to create the Keycloak organizations and attach users to them.
Verification: Decode the Keycloak token after login. It must contain:
json{ "organization": { "ACM001": {} } }
5. sameSite: strict cookie blocks the session on return from Keycloak
Symptom: After the Keycloak redirect to /api/auth/callback, cookies are not transmitted. The handler cannot read pkce_verifier or the session cookies. The user is redirected in a loop back to login.
Cause: Cookies with sameSite: strict are not sent on a cross-site navigation (the Keycloak → your app redirect is considered cross-site).
Solution: Use sameSite: lax for all cookies in the auth flow.
typescriptcookieStore.set("keycloak_token", token, { httpOnly: true, secure: true, sameSite: "lax", // ← not "strict" ... });
6. Container Next.js build fails — EACCES on .next/cache/images
Symptom:
Error: EACCES: permission denied, mkdir '/app/.next/cache/images'
Cause: The COPY .next ./.next in the Dockerfile doesn't transfer the correct permissions to the container's non-root user.
Solution:
dockerfile# In the Wakapp Dockerfile's production stage FROM node:22-alpine AS runner RUN addgroup --system --gid 1001 nodejs && \ adduser --system --uid 1001 nextjs WORKDIR /app COPY /app/.next/standalone ./ COPY /app/.next/static ./.next/static # Create the image cache folder with the right permissions RUN mkdir -p /app/.next/cache/images && chown -R nextjs:nodejs /app/.next/cache USER nextjs
7. Rate limit triggered on Discovery during initial integration
Symptom: After a few quick tests, the Discovery service responds 429 to every request.
Cause: During integration, developers call start-login in a loop to debug. The 5 req/s limit is hit quickly.
Solution:
- Don't call
start-loginon every page refresh — cache thekeycloakUrlif the user isn't logged in. - In local integration, use a mock of the Discovery service.
- Wait for the window to expire (
Retry-After) before retrying.
8. redirect_uri with a trailing slash
Symptom: 400 invalid_redirect_uri during the token exchange with Keycloak.
Cause: Keycloak compares the callback URI exactly (sensitive to the trailing / and to casing).
| Configured in Keycloak | Sent to the token endpoint | Result |
|---|---|---|
https://app.test/callback | https://app.test/callback/ | invalid_redirect_uri |
https://app.test/callback/ | https://app.test/callback | invalid_redirect_uri |
https://app.test/callback | https://app.test/callback | OK |
Solution: Normalize the callback URI (remove the trailing slash) and make sure the value is identical in start-login, in the callback handler, and in the Keycloak console.
typescriptconst REDIRECT_URI = process.env.AUTH_CALLBACK_URL!.replace(/\/$/, ""); // Trim trailing slash
9. Empty appRights despite an assigned profile
Symptom: GET /me returns appRights: [] but the user does have a profile assigned, visible in the admin UI.
Causes and diagnostics:
| Cause | Diagnosis | Solution |
|---|---|---|
The profile exists in the DB but no AppRight is associated with it | Admin → Profiles → check the profile's rights | Assign rights to the profile |
| The profile is assigned on a different Customer | Check profile.customer_id vs user.customer_id | Profiles are scoped per customer — create a profile in the correct customer |
Stale /me cache on the front end | The React context wasn't invalidated after the change | Call auth.refresh() or re-log the user in |
10. Email with uppercase letters causes a 404 not_found on Discovery
Symptom: Lookup by email returns { users: [] } for John@Acme.com even though the user exists with john@acme.com.
Cause: Discovery normalizes emails to lowercase before searching. The email sent must already be normalized on the client side.
Solution:
typescript// Normalize the email BEFORE calling Discovery const normalizedEmail = email.toLowerCase().trim(); const result = await discoverByEmail(normalizedEmail);
Summary — Debugging checklist
When things aren't working, in order:
start-loginreturns404→ checkextractSubdomain, checknetwork.subdomainin the DBInvalid clienton callback →Client authentication: OFFin Keycloak?client_idextracted fromkeycloakUrl?wakastart_tokenabsent → didenrichsucceed? Decode the Keycloak JWT: is theorganizationclaim present?401 Missing enriched token→ is thex-enriched-tokenheader forwarded by the proxy?appRights: []→ profile assigned AND rights configured on the profile?403→ exact right matching the operation? Feature enabled in the cascade?429→ respectRetry-After, implement exponential backoff
Go further
- Common errors — HTTP: exact HTTP codes and messages
- Authentication — implementation: full flow with code
- Endpoint reference: full table of routes