Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

Authenticate with ScaiKey

ScaiWave uses ScaiKey (the ScaiLabs OIDC provider) for identity. Every authenticated request to ScaiWave carries a Bearer token; the token is a ScaiKey-issued JWT whose claims tell ScaiWave who you are and what tenant you belong to.

The auth modes#

ScaiWave supports three:

  • mock — for dev. ScaiWave returns a hard-coded test user; every Bearer token is accepted as long as it's non-empty. Set SCAIWAVE_AUTH_MODE=mock.
  • scaikey — production. ScaiWave validates tokens against ScaiKey's JWKS and resolves the tenant from the tenant_id claim.
  • hybrid — accepts both. Used in some staging environments where ScaiKey isn't always available.

Sign in (browser flow)#

The OIDC authorization-code flow:

  1. Client redirects to https://scaikey.<your-host>/auth?…&redirect_uri=<scaiwave-host>/v1/auth/login.
  2. User authenticates with ScaiKey.
  3. ScaiKey redirects back to ScaiWave with a code.
  4. ScaiWave POSTs code to /v1/auth/login, which exchanges it for {access_token, refresh_token, expires_in}.
  5. The client stores the tokens (HttpOnly cookie or memory), includes the access token as Authorization: Bearer <jwt> on every subsequent request.

The POST /v1/auth/login endpoint is the only place the code is exchanged. Don't call ScaiKey directly from the browser.

Get a token programmatically#

For automation:

bash
1
2
3
4
5
curl -X POST "$BASE/v1/auth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password" \
  -d "username=$USER" \
  -d "password=$PASS"

Returns {access_token, refresh_token, expires_in, token_type}.

Not available in all deployments. Some tenants disable the password grant entirely and require browser OAuth. Check with your admin.

Refresh#

Access tokens have a short TTL (default 1 hour). When ~80% of TTL has elapsed, refresh proactively:

bash
1
2
3
curl -X POST "$BASE/v1/auth/refresh" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "refresh_token=$REFRESH"

Returns a new access token (and a rotated refresh token if your config has rotation enabled).

The ScaiWave web client refreshes automatically when it detects 80% of TTL has passed; you don't need to do anything in the browser.

Register#

A first-time user needs to be registered against ScaiWave:

bash
1
2
curl -X POST "$BASE/v1/auth/register" \
  -H "Authorization: Bearer $TOKEN"

This:

  1. Validates the token with ScaiKey.
  2. Creates a Participant row in ScaiWave's DB if one doesn't exist.
  3. Auto-creates a personal workspace for the new user.
  4. Returns the participant id.

The web client calls this once after first sign-in. Automation should call it idempotently before its first real request.

Token exchange (RFC 8693)#

Some plugins (ScaiDrive, ScaiVault, etc.) need a different token scope. ScaiWave uses RFC 8693 token exchange:

  1. ScaiWave POSTs to ScaiKey's /oauth/token with:
    • grant_type=urn:ietf:params:oauth:grant-type:token-exchange
    • subject_token=<user's ScaiWave token>
    • subject_token_type=urn:ietf:params:oauth:token-type:access_token
    • requested_token_type=urn:ietf:params:oauth:token-type:jwt
    • audience=scaidrive (or scaivault, etc.)
  2. ScaiKey returns a new JWT scoped to the requested audience.
  3. ScaiWave caches it in Redis (TTL = expires_in - 30s).

This happens automatically inside TokenExchangeService — you won't call it directly unless writing a new plugin.

Service tokens (background tasks)#

Background workers (indexers, scheduled summaries) have no end-user context, so they mint a service token via client_credentials grant. As of 2026-05-16, ScaiKey populates sub=client_id on these tokens so ScaiGrid accepts them — see docs/integrations/scaigrid/service-token-401-response.md in the source repo for the history.

You access these via:

python
1
token = await token_exchange.get_service_token(tenant_id)

What's in the JWT#

Decode any ScaiWave token (don't verify) to see the claims:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "sub": "<user-id-on-scaikey>",
  "tenant_id": "<scaikey-tenant-id>",
  "aud": "scaiwave",
  "preferred_username": "alice",
  "email": "alice@example.com",
  "roles": ["docs_internal", "tenant_admin"],
  "groups": ["group-eng", "group-sre"],
  "exp": 1778939467,
  "iat": 1778935867
}

ScaiWave's TenantMiddleware reads tenant_id on every request and resolves it to a Tenant row.

Where to go next#

Updated 2026-05-17 13:10:04 View source (.md) rev 1