Documentation Index
Fetch the complete documentation index at: https://mintlify.com/zitadel/zitadel/llms.txt
Use this file to discover all available pages before exploring further.
OpenID Connect (OIDC) is an identity layer built on top of OAuth 2.0 that enables clients to verify the identity of users and obtain basic profile information. ZITADEL provides full support for the OpenID Connect 1.0 standard, making it the recommended protocol for modern applications.
Why use OIDC?
OIDC offers several advantages over older protocols like SAML:
- Modern and flexible: Built for web, mobile, and single-page applications
- JSON-based: Uses JWT tokens instead of XML, making it easier to parse and debug
- Better mobile support: Designed with native apps and custom URL schemes in mind
- Simpler implementation: Less complex than SAML while providing the same security guarantees
- Industry standard: Widely adopted by modern identity providers and applications
OIDC Discovery
ZITADEL supports OpenID Connect Discovery, allowing applications to automatically configure themselves. The discovery endpoint is available at:
https://${CUSTOM_DOMAIN}/.well-known/openid-configuration
This endpoint returns metadata about ZITADEL’s OIDC implementation, including supported endpoints, grant types, response types, and more.
Authentication Flows
ZITADEL supports multiple OIDC authentication flows to accommodate different application types:
Authorization Code Flow
The Authorization Code Flow is recommended for server-side applications. It provides the highest level of security by keeping tokens away from the user agent.
Flow steps:
- Redirect user to the authorization endpoint
- User authenticates and grants consent
- Authorization code is returned to your application
- Exchange the code for tokens at the token endpoint
Authorization endpoint:
GET https://${CUSTOM_DOMAIN}/oauth/v2/authorize
Required parameters:
| Parameter | Description |
|---|
client_id | Your application’s client ID |
redirect_uri | Callback URI registered in ZITADEL |
response_type | Set to code for authorization code flow |
scope | Space-delimited scopes, must include openid |
Example authorization request:
GET /oauth/v2/authorize?
client_id=170086824411201793@yourapp&
redirect_uri=https://yourapp.example.com/auth/callback&
response_type=code&
scope=openid%20email%20profile&
code_challenge=9az09PjcfuENS7oDK7jUd2xAWRb-B3N7Sr3kDoWECOY&
code_challenge_method=S256&
state=random_state_value
Token endpoint:
POST https://${CUSTOM_DOMAIN}/oauth/v2/token
Token exchange request:
curl -X POST "https://${CUSTOM_DOMAIN}/oauth/v2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=YOUR_AUTHORIZATION_CODE" \
-d "redirect_uri=https://yourapp.example.com/auth/callback" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "code_verifier=YOUR_CODE_VERIFIER"
Authorization Code Flow with PKCE
Proof Key for Code Exchange (PKCE) is an extension to the authorization code flow designed for public clients (mobile and single-page applications) that cannot securely store a client secret.
Additional parameters:
| Parameter | Description |
|---|
code_challenge | SHA-256 hash of the code_verifier |
code_challenge_method | Must be S256 |
code_verifier | Random string used in token exchange |
Example with PKCE:
// Generate code verifier and challenge
const codeVerifier = generateRandomString(128);
const codeChallenge = await sha256(codeVerifier);
// Authorization request
window.location.href = `https://${CUSTOM_DOMAIN}/oauth/v2/authorize?
client_id=${CLIENT_ID}&
redirect_uri=${REDIRECT_URI}&
response_type=code&
scope=openid%20profile%20email&
code_challenge=${codeChallenge}&
code_challenge_method=S256&
state=${state}`;
// Token exchange (after receiving code)
const response = await fetch('https://${CUSTOM_DOMAIN}/oauth/v2/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code: authorizationCode,
redirect_uri: REDIRECT_URI,
client_id: CLIENT_ID,
code_verifier: codeVerifier
})
});
Implicit Flow
The implicit flow returns tokens directly from the authorization endpoint. This flow is deprecated and should only be used for legacy applications.
Response types:
id_token: Returns only an ID token
id_token token: Returns both ID token and access token
Refresh Token Flow
Refresh tokens allow applications to obtain new access tokens without requiring user interaction.
curl -X POST "https://${CUSTOM_DOMAIN}/oauth/v2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=YOUR_REFRESH_TOKEN" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"
Application Types
ZITADEL supports different OIDC application types, each with specific configurations:
Web Applications
- Type:
OIDC_APP_TYPE_WEB
- Auth method:
client_secret_basic, client_secret_post, or private_key_jwt
- Recommended flow: Authorization code flow
- Example: Server-side rendered applications, traditional web apps
User Agent Applications
- Type:
OIDC_APP_TYPE_USER_AGENT
- Auth method:
none (PKCE required)
- Recommended flow: Authorization code flow with PKCE
- Example: Single-page applications (React, Vue, Angular)
Native Applications
- Type:
OIDC_APP_TYPE_NATIVE
- Auth method:
none (PKCE required)
- Recommended flow: Authorization code flow with PKCE
- Example: Mobile apps (iOS, Android), desktop applications
Authentication Methods
ZITADEL supports multiple authentication methods for client authentication:
Client Secret Basic
curl -X POST "https://${CUSTOM_DOMAIN}/oauth/v2/token" \
-u "CLIENT_ID:CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=YOUR_CODE" \
-d "redirect_uri=YOUR_REDIRECT_URI"
Client Secret Post
curl -X POST "https://${CUSTOM_DOMAIN}/oauth/v2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=YOUR_CODE" \
-d "redirect_uri=YOUR_REDIRECT_URI" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"
Private Key JWT
Use asymmetric keys for client authentication, providing enhanced security.
const jwt = require('jsonwebtoken');
const privateKey = fs.readFileSync('private-key.pem');
const assertion = jwt.sign(
{
iss: CLIENT_ID,
sub: CLIENT_ID,
aud: `https://${CUSTOM_DOMAIN}/oauth/v2/token`,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600
},
privateKey,
{ algorithm: 'RS256' }
);
const response = await fetch(`https://${CUSTOM_DOMAIN}/oauth/v2/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code: authorizationCode,
redirect_uri: REDIRECT_URI,
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
client_assertion: assertion
})
});
None (PKCE)
No client authentication, relies on PKCE for security. See Authorization Code Flow with PKCE above.
Scopes and Claims
Standard Scopes
| Scope | Claims Returned |
|---|
openid | Required for OIDC, returns sub claim |
profile | name, family_name, given_name, middle_name, nickname, preferred_username, profile, picture, website, gender, birthdate, zoneinfo, locale, updated_at |
email | email, email_verified |
phone | phone_number, phone_number_verified |
address | address (structured claim) |
offline_access | Enables refresh token issuance |
ZITADEL-Specific Scopes
| Scope | Description |
|---|
urn:zitadel:iam:org:project:id:${PROJECT_ID}:aud | Adds project ID to audience |
urn:zitadel:iam:org:id:${ORG_ID} | Filters users by organization |
urn:zitadel:iam:user:metadata | Returns user metadata |
urn:zitadel:iam:user:resourceowner | Returns resource owner information |
Token Types
ZITADEL supports two access token types:
Bearer Token (Opaque)
- Type:
OIDC_TOKEN_TYPE_BEARER
- Format: Opaque reference token
- Use case: When you want to use the introspection endpoint
- Validation: Must be validated via introspection endpoint
JWT Token
- Type:
OIDC_TOKEN_TYPE_JWT
- Format: Self-contained JSON Web Token
- Use case: Stateless validation, microservices
- Validation: Can be validated locally using ZITADEL’s public keys
Key Endpoints
Authorization Endpoint
GET https://${CUSTOM_DOMAIN}/oauth/v2/authorize
Initiates the authentication flow.
Token Endpoint
POST https://${CUSTOM_DOMAIN}/oauth/v2/token
Exchanges authorization codes for tokens or refreshes access tokens.
UserInfo Endpoint
GET https://${CUSTOM_DOMAIN}/oidc/v1/userinfo
Returns claims about the authenticated user.
curl -X GET "https://${CUSTOM_DOMAIN}/oidc/v1/userinfo" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Introspection Endpoint
POST https://${CUSTOM_DOMAIN}/oauth/v2/introspect
Validates and retrieves information about a token.
curl -X POST "https://${CUSTOM_DOMAIN}/oauth/v2/introspect" \
-u "CLIENT_ID:CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=YOUR_TOKEN"
Revocation Endpoint
POST https://${CUSTOM_DOMAIN}/oauth/v2/revoke
Revokes access or refresh tokens.
curl -X POST "https://${CUSTOM_DOMAIN}/oauth/v2/revoke" \
-u "CLIENT_ID:CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=YOUR_TOKEN"
End Session Endpoint
GET https://${CUSTOM_DOMAIN}/oidc/v1/end_session
Logs out the user and ends their session.
GET /oidc/v1/end_session?
id_token_hint=YOUR_ID_TOKEN&
post_logout_redirect_uri=https://yourapp.example.com/logged-out
Advanced Features
Token Customization
ZITADEL provides several options to customize token behavior:
- ID Token Role Assertion: Include roles in ID token claims
- Access Token Role Assertion: Include roles in access token (JWT only)
- ID Token UserInfo Assertion: Include profile claims in ID token even when access token is issued
- Clock Skew: Compensate for time differences between servers
Back-Channel Logout
ZITADEL supports OIDC Back-Channel Logout, allowing applications to be notified when a user’s session is terminated.
Configuration:
Set the back_channel_logout_uri in your OIDC application configuration:
{
"back_channel_logout_uri": "https://yourapp.example.com/auth/backchannel-logout"
}
Response Modes
| Mode | Description |
|---|
query | Parameters in URL query string (default for code response type) |
fragment | Parameters in URL fragment (default for id_token response type) |
form_post | Parameters sent via HTTP POST |
Configuration Example
Here’s how to create an OIDC application in ZITADEL:
Best Practices
- Always use HTTPS: Never use OIDC over unencrypted connections
- Validate state parameter: Protect against CSRF attacks
- Use PKCE for public clients: Essential for mobile and SPA applications
- Implement proper token storage: Use secure storage mechanisms
- Validate tokens: Always verify JWT signatures and claims
- Use short-lived access tokens: Reduce the impact of token theft
- Implement token refresh: Use refresh tokens for long-lived sessions
- Handle errors gracefully: Provide clear error messages to users
- Use appropriate scopes: Request only the scopes you need
- Monitor token usage: Track and audit token issuance and usage
Troubleshooting
Common Issues
Invalid redirect_uri
- Ensure the redirect URI exactly matches one registered in ZITADEL
- Check for trailing slashes and URL encoding
Invalid client credentials
- Verify client_id and client_secret are correct
- Check authentication method matches application configuration
Token validation fails
- Ensure you’re using the correct public keys from the JWKS endpoint
- Verify the token hasn’t expired
- Check the audience claim matches your client_id
PKCE validation fails
- Ensure code_verifier matches the code_challenge
- Use S256 challenge method, not plain
Resources