WakaStart
Security

Common integration pitfalls

The 10 most frequent mistakes when integrating a Wakapp: symptom, cause and solution.

Version v1.07 min read

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.

CauseDiagnosisSolution
Incorrect subdomain extractionDiscovery logs: host="app" instead of "app.test"Implement extractSubdomain with a strip of the platform suffix
network.subdomain NULL or different in the DBSELECT 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 suffixLogs: host=""Implement a manual subdomain input field in dev mode

Correct extractSubdomain function:

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.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.

CauseDiagnosisSolution
OIDC client in Client authentication: ON modeKeycloak console → Client → Capability configSwitch it OFF (public client + PKCE)
Incorrect client_id in the token exchangesessionStorage login_client_id holds a fallback value ("wakastart")Extract client_id from the keycloakUrl BEFORE the redirect
redirect_uri doesn't match exactlyKeycloak console → Client → Access settingsAdd the exact URI (sensitive to trailing slash)
code_verifier recomputed after the redirectThe verifier in sessionStorage was clearedNever 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.

CauseDiagnosisSolution
wakastart_token cookie absentDevTools → Application → CookiesThe /api/auth/enrich call during the callback failed — check the HTTP response in Network
organization claim absent from the Keycloak JWTDecode 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 callsCapture 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 subWakaStart auth service logs: user not found for subSynchronize keycloak_id values in the DB (via backfill or re-provisioning)
WakaStart auth service failing with a Prisma errorToken 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:

  1. Keycloak console → realm → Realm settings → General → enable "Organizations enabled"
  2. Keycloak console → realm → Clients → {client_id} → Client scopes → add organization as Default
  3. Run npx ts-node prisma/backfill-keycloak-orgs.ts in 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": {} } }

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.

typescript
cookieStore.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 --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /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-login on every page refresh — cache the keycloakUrl if 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 KeycloakSent to the token endpointResult
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: 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.

typescript
const 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:

CauseDiagnosisSolution
The profile exists in the DB but no AppRight is associated with itAdmin → Profiles → check the profile's rightsAssign rights to the profile
The profile is assigned on a different CustomerCheck profile.customer_id vs user.customer_idProfiles are scoped per customer — create a profile in the correct customer
Stale /me cache on the front endThe React context wasn't invalidated after the changeCall 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:

  1. start-login returns 404 → check extractSubdomain, check network.subdomain in the DB
  2. Invalid client on callback → Client authentication: OFF in Keycloak? client_id extracted from keycloakUrl?
  3. wakastart_token absent → did enrich succeed? Decode the Keycloak JWT: is the organization claim present?
  4. 401 Missing enriched token → is the x-enriched-token header forwarded by the proxy?
  5. appRights: [] → profile assigned AND rights configured on the profile?
  6. 403 → exact right matching the operation? Feature enabled in the cascade?
  7. 429 → respect Retry-After, implement exponential backoff

Go further