OAuth and OIDC
ScaiKey is a standard OpenID Connect provider. If your client library handles OIDC correctly, you mostly need to know two things: which discovery URL to point at, and which grant type fits your use case.
Discovery URLs#
| Use case | Discovery URL |
|---|---|
GLOBAL-scoped app authenticating users across tenants |
$SCAIKEY/api/v1/platform/oauth/.well-known/openid-configuration |
TENANT-scoped app authenticating a single tenant's users |
$SCAIKEY/api/v1/auth/tenants/{slug}/.well-known/openid-configuration |
Point your OIDC library at the URL that matches your app's scope. The library reads it once at startup; from then on it knows every endpoint, scope, and signing key URL it needs.
Supported grants#
authorization_code — interactive user login#
The standard browser-based flow. User clicks "Log in with ScaiKey", gets redirected to ScaiKey's hosted login page, authenticates (with MFA if required by the tenant), is redirected back to your app with a code, and your backend swaps the code for tokens.
Required for SPAs and native apps: PKCE. Public clients (SPA, NATIVE application types) must use code_challenge + code_verifier. Confidential clients (WEB, SERVICE) can use PKCE too — recommended.
client_credentials — service-to-service, no user#
Your backend exchanges its client_id + client_secret for an access token. There's no user, no sub claim from a person — sub is set to the client_id. Use this for backend integrations, scheduled jobs, internal tools.
Only WEB and SERVICE application types can use this grant (they're confidential clients with a secret). SPA and NATIVE cannot.
refresh_token — extend a session without re-prompting#
Standard refresh token grant. ScaiKey rotates refresh tokens on every use (one-time-use refresh tokens). The previous refresh token is invalidated atomically when the new one is issued.
Refresh tokens are issued only when:
- Your app is
WEBorNATIVE(typically need long-lived sessions), or - The original authorization request included the
offline_accessscope.
urn:ietf:params:oauth:grant-type:token-exchange — RFC 8693#
Exchange a token issued by one ScaiKey-registered application for a token audience-restricted to another. Common pattern: a frontend gets a user token for "MyApp", which needs to call "BackendService" — MyApp's server exchanges the user token for a BackendService-audience token before calling.
Constraints:
- Subject token must be a ScaiKey-issued access token (we don't accept foreign tokens).
- The target application must opt in (
token_exchange_allowed = trueon its row). - Scopes can only narrow, never widen: granted =
subject_scopes ∩ target.allowed_scopes ∩ requested. - Self-exchange is rejected.
- The exchanged token's
expires_inis the target'stoken_lifetime, not the subject token's remaining lifetime — exchange effectively "refreshes" the wall clock for the new audience. - The new token carries an
actclaim recording who performed the exchange (delegation chain is preserved if subject already had anact).
See Reference → OAuth endpoints → Token endpoint for the exact form parameters.
urn:ietf:params:oauth:grant-type:device_code — RFC 8628#
The "TV login" flow. Your device shows a short user code, the user types it in on a separate device's browser to approve, your device polls until approval and gets tokens. Use this for CLI tools, IoT devices, headless setups.
When to use which#
| Situation | Grant |
|---|---|
| Web app with a backend, users log in via browser | authorization_code |
| Single-page app (no backend) | authorization_code + PKCE |
| Mobile / native desktop app | authorization_code + PKCE |
| Backend service calling another backend service | client_credentials |
| Backend needs a token audience-restricted to a different downstream | Token Exchange |
| CLI or headless device | device_code |
| You already have a session and just need a fresh access token | refresh_token |
What we don't support#
passwordgrant (Resource Owner Password Credentials, ROPC). Deprecated and dangerous. Don't ask. Useauthorization_codeand direct users to the hosted login page.implicitgrant. Deprecated in OAuth 2.1. Useauthorization_code+ PKCE instead.
Scopes#
openid is required for OIDC flows. profile, email, groups are the standard claims-bearing scopes. offline_access requests a refresh token. ScaiKey-specific admin scopes (admin:read, admin:write, users:read, groups:read, etc.) gate access to its own management API — see Concepts → Tokens and scopes.