OAuth And OIDC¶
Use this reference after Add SigID Login when you need to move from a first working login to a correct production integration. It explains OAuth behavior, token safety, grant selection, scopes, consent, tenant isolation, and the common mistakes that break real deployments.
If you are preparing the tenant, application, users, and handoff values for a development team, start with Launch Your Workspace.
Who this page is for¶
This page is for application developers and backend API owners who already have or are about to receive a SigID application configuration.
By the end of this page, you should know how to:
- run Authorization Code with PKCE without leaking secrets
- choose the right grant type for your app
- exchange authorization codes with the registered client auth method
- validate access tokens before serving protected resources
- use tenant-scoped subjects instead of email addresses as user keys
- request scopes and refresh tokens deliberately
- troubleshoot OAuth error responses without guessing
On This Page¶
- Before you start
- Choose the right OAuth path
- Authorization Code with PKCE
- Token validation
- Tenant-scoped subjects and claims
- OAuth and OIDC endpoint reference
- Choose a grant type
- Scopes and consent
- Refresh tokens and revocation
- Device authorization, CIBA, and token exchange
- Common integration mistakes
Before you start¶
Get these values from your tenant administrator or platform team. They should match the handoff packet from Launch Your Workspace.
| Value | Used for |
|---|---|
SIGID_ISSUER_URL |
Discovery, hosted auth redirects, token exchange, JWKS, and /userinfo |
| Tenant ID or tenant slug | Support, logs, tenant isolation, and environment troubleshooting |
SIGID_CLIENT_ID |
Authorization requests and public-client token exchange |
SIGID_REDIRECT_URI |
Callback allowlist and authorization-code token exchange |
SIGID_SCOPES |
Consent, UserInfo claims, and API authorization checks |
| Token endpoint auth method | Whether token exchange uses none, client_secret_post, client_secret_basic, or configured private_key_jwt |
| Client secret | Confidential backend clients only; never expose this to browsers or mobile apps |
| API audience | Required when your backend validates access tokens for an API |
| Refresh-token policy | Whether the app may request offline_access and use the refresh_token grant |
| Test user | A non-admin user that can exercise the expected tenant, organization, role, and policy state |
Keep development, staging, and production clients separate. Redirect URIs, secrets, scopes, and audiences should not drift between environments by accident.
Choose the right OAuth path¶
| Use case | Recommended path | Notes |
|---|---|---|
| Web, mobile, desktop, or browser login | Authorization Code with PKCE | Default path for interactive user login |
| Web app with a trusted backend | Authorization Code with PKCE plus a confidential client auth method | Keep the client secret server-side only |
| Browser-only, mobile, desktop, or CLI app without a trusted secret store | Authorization Code with PKCE as a public client | Use token endpoint auth none; still use PKCE |
| Backend service calling another backend | Client Credentials | Confidential clients only; no refresh token is issued |
| CLI, TV, terminal, or input-constrained device | Device Authorization | Requires the device-code grant and platform support |
| Agent acting for a user | Token Exchange | Validate subject_type, act, audience, scopes, and delegation policy |
| Backchannel approval without browser redirect | CIBA | Use only when the app has a real backchannel-auth use case |
| Long-lived user session | Authorization Code with PKCE plus offline_access |
Request only when the app needs refresh tokens |
Most applications should start with Authorization Code with PKCE and add other grants only when there is a concrete product workflow.
Authorization Code with PKCE¶
Authorization Code with PKCE is the recommended flow for user login. SigID signs
the user in on hosted auth, redirects back with an authorization code, and your
app exchanges that code at /oauth/token.
1. Create state and PKCE values¶
Create a random state value for CSRF protection and a random PKCE
code_verifier. Send only the derived code_challenge in the authorization
request. Store the original state and code_verifier in a server-side session
or another storage location that is bound to the browser interaction.
import crypto from "node:crypto";
function base64url(bytes) {
return Buffer.from(bytes).toString("base64url");
}
export function createPkceRequestState() {
const state = base64url(crypto.randomBytes(32));
const codeVerifier = base64url(crypto.randomBytes(32));
const codeChallenge = base64url(
crypto.createHash("sha256").update(codeVerifier).digest(),
);
return { state, codeVerifier, codeChallenge };
}
SigID supports the S256 PKCE method. Do not send the verifier in the
authorization request.
2. Redirect to hosted auth¶
Build an authorization URL on the issuer:
GET /oauth/authorize
?response_type=code
&client_id=CLIENT_ID
&redirect_uri=https%3A%2F%2Fapp.example.com%2Fauth%2Fcallback
&scope=openid%20profile%20email
&code_challenge=BASE64URL_SHA256_VERIFIER
&code_challenge_method=S256
&state=OPAQUE_CSRF_VALUE
Use the exact registered redirect URI. These are different values:
https://app.example.com/auth/callback
https://app.example.com/auth/callback/
http://app.example.com/auth/callback
3. Handle the callback¶
Successful callback:
Error callback:
Callback handler checklist:
- verify the returned
state - handle
erroras a user-visible retry path - exchange each
codeonly once - use the same
redirect_uriused in the authorization request - use the original
code_verifier - do not log codes, tokens, client secrets, or refresh tokens
4. Exchange the authorization code¶
Use the token endpoint auth method registered on the application. Do not infer the method from the runtime. Check the application settings or dynamic registration response and use the exact registered value.
Public PKCE client:
curl -sS "$SIGID_ISSUER_URL/oauth/token" \
-H "content-type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "client_id=$SIGID_CLIENT_ID" \
--data-urlencode "code=$AUTHORIZATION_CODE" \
--data-urlencode "redirect_uri=$SIGID_REDIRECT_URI" \
--data-urlencode "code_verifier=$CODE_VERIFIER"
Confidential client using client_secret_post:
curl -sS "$SIGID_ISSUER_URL/oauth/token" \
-H "content-type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "client_id=$SIGID_CLIENT_ID" \
--data-urlencode "client_secret=$SIGID_CLIENT_SECRET" \
--data-urlencode "code=$AUTHORIZATION_CODE" \
--data-urlencode "redirect_uri=$SIGID_REDIRECT_URI" \
--data-urlencode "code_verifier=$CODE_VERIFIER"
Confidential client using client_secret_basic:
curl -sS "$SIGID_ISSUER_URL/oauth/token" \
-u "$SIGID_CLIENT_ID:$SIGID_CLIENT_SECRET" \
-H "content-type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "code=$AUTHORIZATION_CODE" \
--data-urlencode "redirect_uri=$SIGID_REDIRECT_URI" \
--data-urlencode "code_verifier=$CODE_VERIFIER"
Typical response without refresh-token access:
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 900,
"scope": "openid profile email",
"id_token": "eyJ..."
}
For user login flows such as Authorization Code, Device Authorization, and CIBA,
SigID returns a refresh_token only when offline_access was granted. Delegated
token exchange can issue a new refresh-token family for the actor according to
delegation policy.
5. Create your application session¶
After token exchange, your backend should either validate access tokens on every API request or create a trusted server-side application session after validation. Do not authorize protected API requests from frontend login state alone.
Token validation¶
Access tokens protect APIs. ID tokens describe the authentication result for the client, and UserInfo returns consented profile claims. Do not use an ID token, UserInfo response, or decoded JWT payload as a substitute for access-token validation.
Use the checklist below as the minimum validation contract for protected APIs. Every protected API request should fail closed unless these checks pass:
| Check | What to verify |
|---|---|
| Discovery and JWKS | Load issuer metadata from /.well-known/openid-configuration and signing keys from the discovered JWKS URI |
| Token type and signature | Verify the JWT signature with the expected algorithm and key; do not only decode the payload |
| Issuer | iss matches SIGID_ISSUER_URL |
| Audience | aud matches your API audience, not some other client or service |
| Lifetime | exp and nbf allow the token at request time |
| Tenant | tenant_id matches the tenant that owns the requested resource |
| Scope | scope contains the permission required for the action |
| Subject type | subject_type is acceptable for the route, such as human, agent, system, or anonymous |
| Delegation | If act is present, enforce the delegated actor policy and reduced scopes |
| Sender constraint | If DPoP is used, verify the DPoP proof and cnf binding |
A minimal backend authorization pattern is:
- Read
Authorization: Bearer <access_token>. - Verify the access token with the discovered JWKS and expected issuer.
- Require your API audience.
- Match
tenant_idto the resource tenant before checking scopes. - Require the route's scope or policy.
- Reject unexpected subject types, agent claims, or delegation chains.
- Build your application principal from validated claims only.
Tenant-scoped subjects and claims¶
SigID can present pairwise subject identifiers for tenant-scoped third-party
user or agent tokens. First-party targets, system principals, and some
client-credentials application principals may receive non-pairwise subjects.
Use the validated sub claim together with the tenant context as your durable
application user key.
Do not use email as a primary key. Email can change, can be unverified in some contexts, and may not uniquely identify the authorization subject.
Do not assume the same human has the same sub across tenants or unrelated
client sectors. If your product intentionally shares identity across multiple
applications, make that mapping explicit in your own data model.
Important access-token claims:
| Claim | Use |
|---|---|
sub |
Tenant-application subject identifier for the caller |
tenant_id |
Tenant isolation boundary |
client_id |
OAuth application that received the token |
aud |
API or resource server audience |
scope |
Space-delimited permissions |
subject_type |
Caller category, such as human, agent, system, or anonymous |
act |
Delegated actor chain for token-exchange flows |
org |
Active organization context, when present |
cnf |
DPoP confirmation claim, when sender-constrained |
Call /userinfo only for consented human OIDC profile claims:
Your API should authorize from validated access-token claims, not from the UserInfo response.
OAuth and OIDC endpoint reference¶
Use discovery instead of hard-coding endpoint URLs:
Common fields:
{
"issuer": "https://auth.example.com",
"authorization_endpoint": "https://auth.example.com/oauth/authorize",
"token_endpoint": "https://auth.example.com/oauth/token",
"jwks_uri": "https://auth.example.com/.well-known/jwks.json",
"userinfo_endpoint": "https://auth.example.com/userinfo"
}
Endpoint availability can vary by deployment and application policy. Discovery metadata and the target deployment's OpenAPI document are the source of truth for enabled protocol surfaces.
| Endpoint | Use |
|---|---|
GET /.well-known/openid-configuration |
Discover OAuth and OIDC metadata for the issuer |
GET /.well-known/jwks.json |
Fetch signing keys for JWT verification |
GET /oauth/authorize |
Start hosted login, consent, and authorization-code issuance |
POST /oauth/token |
Exchange authorization codes, refresh tokens, device codes, CIBA grants, client credentials, and token-exchange requests |
GET /userinfo |
Read consented OIDC profile claims for a human user |
POST /oauth/revoke |
Revoke refresh tokens or access tokens during logout, disconnect, or compromise handling |
POST /oauth/introspect |
Let trusted backends inspect token state when local JWT validation is not enough |
POST /oauth/par |
Push large or sensitive authorization request parameters before redirecting |
POST /oauth/device/code |
Start Device Authorization for input-constrained clients |
GET /oauth/device/verify |
User-facing device verification page |
POST /bc-authorize |
Start CIBA backchannel authentication |
GET /api/v1/identity/ciba/requests |
List pending CIBA requests for the authenticated human user |
POST /api/v1/identity/ciba/requests/{id}/approve |
Approve a pending CIBA request |
POST /api/v1/identity/ciba/requests/{id}/deny |
Deny a pending CIBA request |
GET or POST /oauth/end-session |
OIDC RP-initiated logout, when enabled |
POST /oauth/register |
Dynamic Client Registration, when enabled |
For the full product surface, see Product Reference.
Choose a grant type¶
| Grant | Use it for | Avoid when |
|---|---|---|
authorization_code |
Interactive user login with PKCE | The caller is a backend service with no user |
refresh_token |
Renewing a user session after offline_access was granted |
The app only needs a short browser session |
client_credentials |
Machine-to-machine backend access | Public clients or user-delegated actions |
urn:ietf:params:oauth:grant-type:device_code |
CLI, TV, terminal, and other input-constrained clients | A normal browser redirect is available |
urn:ietf:params:oauth:grant-type:token-exchange |
Agent or delegated access after policy checks | You are only logging in a human user |
urn:openid:params:grant-type:ciba |
Backchannel user approval | Your app can use a normal redirect flow |
Every grant must be allowed on the application. If a request uses a known grant
that the application does not allow, the token endpoint returns
unauthorized_client. If the grant_type value itself is unsupported or
malformed, the token endpoint returns unsupported_grant_type.
Scopes and consent¶
Start with the smallest scope set that proves the login flow. Add broader scopes only when the user takes a feature-specific action that needs them.
Common scopes:
| Scope | Meaning |
|---|---|
openid |
Request OIDC login semantics and an ID token |
profile |
Allow profile claims such as name, when available |
email |
Allow email claims, when available |
offline_access |
Allow refresh-token issuance when policy and grant configuration permit it |
API scopes such as projects:read |
Authorize your own protected API actions |
Consent text should describe the user-visible result, not only the internal scope name. For delegated agent access, explain what the agent can do, whose authority it uses, and when that access ends.
Advanced authorization parameters:
| Need | Parameter or endpoint |
|---|---|
| Force login | prompt=login |
| Force consent | prompt=consent |
| Silent check with an existing session | prompt=none |
| Require recent authentication | max_age=300 |
| Request step-up assurance | acr_values=urn:sigid:aal2 |
| Request specific OIDC claims | claims |
| Request rich authorization details | authorization_details |
| Push request parameters before redirect | POST /oauth/par, then authorize with request_uri |
Advanced parameters and endpoints must be enabled by the deployment and allowed for the application before clients depend on them.
Refresh tokens and revocation¶
Request offline_access only when your application needs to renew a user session
without another browser redirect.
Refresh-token rules:
- for user login flows, no
offline_accessscope means norefresh_tokenin the token response - delegated token exchange may return a refresh token according to delegation policy
- store refresh tokens server-side or in platform-secure storage
- keep refresh tokens out of logs, analytics, screenshots, and support tickets
- persist the newest refresh token if rotation returns one
- do not use refresh tokens for
client_credentials - treat refresh-token reuse as a security event
- revoke tokens on logout, disconnect, account removal, or suspected compromise
Refresh with a public client:
curl -sS "$SIGID_ISSUER_URL/oauth/token" \
-H "content-type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=refresh_token" \
--data-urlencode "client_id=$SIGID_CLIENT_ID" \
--data-urlencode "refresh_token=$REFRESH_TOKEN"
For a confidential client, authenticate with the same token endpoint auth method registered on the application.
Device authorization, CIBA, and token exchange¶
Use Device Authorization for input-constrained clients:
curl -sS "$SIGID_ISSUER_URL/oauth/device/code" \
-H "content-type: application/x-www-form-urlencoded" \
--data-urlencode "client_id=$SIGID_CLIENT_ID" \
--data-urlencode "scope=openid profile"
The client shows the returned user code and polls /oauth/token according to
the returned interval. Respect authorization_pending, slow_down,
access_denied, and expired_token.
Use CIBA only when the client cannot redirect the user but can start a trusted
backchannel authorization request. SigID supports poll delivery mode with
confidential clients. POST /bc-authorize accepts scope, login_hint,
optional binding_message, optional requested_expiry, and optional
authorization_details/claims; it rejects login_hint_token,
id_token_hint, push/ping notification parameters, CIBA user_code, and
CIBA-specific acr_values. Unknown or inactive login hints return the same
successful start response as known users, but create a decoy request that never
appears in the user's approval list and never issues tokens. The authenticated
human lists pending requests at /api/v1/identity/ciba/requests, approves or
denies one by ID, and the client polls /oauth/token with grant type
urn:openid:params:grant-type:ciba and auth_req_id. Pollers must honor
authorization_pending, slow_down with Retry-After, access_denied, and
expired_token.
Use Token Exchange for delegated access, especially agent workflows. Do not
treat delegated agent tokens as ordinary human user tokens. Validate audience,
tenant, reduced scopes, subject_type, and act claims before allowing tool or
API actions. For agent-specific guidance, see
Agent And MCP Auth.
Hosted sign-in methods¶
Applications should redirect users to SigID hosted auth. Do not reimplement password, passkey, social login, SSO, MFA, recovery, or consent screens inside your application unless a product-specific integration explicitly requires it.
Tenant administrators decide which hosted sign-in methods are enabled and how Enterprise SSO domains route users. Developers should handle OAuth redirects, callbacks, token exchange, token validation, and application sessions.
Common integration mistakes¶
| Mistake | Fix |
|---|---|
Using SIGID_AUTH_ORIGIN in new code |
Use SIGID_ISSUER_URL |
| Redirect URI mismatch | Copy the exact registered URI, including scheme, host, port, path, and trailing slash |
| State mismatch | Store state server-side and reject mismatches |
| PKCE mismatch | Use the original code_verifier for token exchange |
| Token auth method mismatch | Use the registered method: none, client_secret_post, client_secret_basic, or configured private_key_jwt |
Expecting refresh_token from user login without offline_access |
Request offline_access only when the app needs refresh tokens |
| Application does not allow the grant | Enable the grant or use the correct app; the error is unauthorized_client |
Unsupported grant_type value |
Fix the literal grant value; the error is unsupported_grant_type |
| Accepting any audience | Require your API audience |
| Decoding JWTs without verifying them | Verify signature, issuer, audience, lifetime, and tenant |
| Trusting frontend-only login state | Validate access tokens on the backend or create a server-side session after validation |
| Using email as user ID | Store the tenant-scoped sub with tenant context |
| Treating agents as human users | Check subject_type, agent, and act claims |
| Mixing tenant or environment values | Keep issuer, tenant, client ID, redirect URI, and audience from the same environment |
| Ignoring callback errors | Show a retry path and log a request ID without logging secrets |
Next steps¶
- Token validation for backend authorization requirements
- Launch Your Workspace for administrator-owned application setup
- API And SDK Reference for API and SDK entry points
- Agent And MCP Auth for delegated agent and MCP boundaries
- Claims And Scopes for delegated token claims