---
title: 'Troubleshooting: Auth and credentials'
path: troubleshooting/auth-and-credentials
status: published
---

# Troubleshooting: Auth and credentials

## "PKCE state mismatch — login flow is invalid or expired"

The browser stored a PKCE verifier in `sessionStorage` under one tab, the ScaiKey redirect landed in another tab (or after a refresh that lost session storage).

**Fix:** Sign in again. If it keeps happening, ensure the canvas is served from a single origin (no `localhost:5173` vs `127.0.0.1:5173` mismatch) and that `redirect_uri` exactly matches the one configured in ScaiKey.

## `503 ScaiKey OIDC not configured on the server`

The backend is missing one of `SCAIKEY_BASE_URL`, `SCAIFLOW_SCAIKEY_CLIENT_ID`, `SCAIFLOW_SCAIKEY_CLIENT_SECRET`.

**Fix:** Set all three in the backend's `.env` and restart. See [Configuration](../reference/configuration#scaikey-oidc).

## `400 invalid_grant: code already used`

The auth code returned from ScaiKey can only be exchanged once. The canvas probably triggered the exchange twice (double-click on the callback, dev hot-reload re-running the effect, etc.).

**Fix:** Refresh, sign in again. If it persists in dev, check whether your hot-reload setup re-mounts `<AuthCallback>` — it should consume the code on mount and clear the URL.

## `401 invalid token: Audience doesn't match`

ScaiGrid (or ScaiFlow's own JWT verifier) rejected the token because the `aud` claim doesn't match the configured audience.

**Fix:** ScaiKey issues `aud = client_id` by default. Make sure `SCAIKEY_AUDIENCE` on the backend is either unset (defaults to `SCAIFLOW_SCAIKEY_CLIENT_ID`) or explicitly equals the client_id ScaiKey is configured to use.

If you've set `SCAIKEY_AUDIENCE` to something other than client_id (e.g. `user-portal`), unset it — ScaiKey doesn't actually issue that audience.

## "Save requires ScaiKey login" but I am signed in

The canvas distinguishes two auth modes — `scaikey` (full backend access) and `dev_token` (legacy shared-bearer; only `POST /v1/dev/deploy` works). Save needs `scaikey`.

**Fix:** Ensure your backend has `SCAIKEY_BASE_URL` + client_id + secret configured. If they're set but the canvas is still in `dev_token` mode, check `GET /v1/auth/config` — it returns `auth_mode: "scaikey"` only when all three are present.

## `412 missing_credentials` on Lookup endpoints

Any `/v1/scaiqueue/*`, `/v1/scaibunker/*`, `/v1/scaigrid/*` proxy returning 412 means the backend has no ScaiGrid credentials for your tenant.

**Fix:** A tenant admin sets the tenant's API key via `POST /v1/tenants/me/scaigrid_credentials`. See [Deploy failures: 412 missing_scaigrid_credentials](./deploy-failures#412-missing_scaigrid_credentials).

## "Catalog requires ScaiKey login" on the Catalog button

Same root cause — Catalog endpoints are ScaiKey-only. Sign in via ScaiKey or wait for ScaiKey to be configured.

You can still open the Catalog modal in dev-token mode — the Examples tab works (loads from static files); the Browse + Publish tabs are hidden.

## ScaiKey-sync: `403 Platform token requires admin:read or admin:write scope`

The credentials the backend is using don't have the scopes needed to call ScaiKey's admin API (`applications.get_effective_users`, `groups.list`, etc.).

**Fix:** Re-issue the backend's ScaiKey credentials with `admin:read` and `admin:write` (or equivalent on your ScaiKey version). The `scaiflow scaikey-register` CLI does this in one shot for fresh tenants.

If your credentials are tenant-tier (no admin scope), the sync falls back to the member-walk path automatically — slower but works without admin scope. Look for `users_method: "member_walk"` in the sync stats to confirm the fallback kicked in.
