-
-
Notifications
You must be signed in to change notification settings - Fork 118
OAuth Provider
Daptin can act as an OAuth 2.0 and OpenID Connect provider. Client applications can send users to Daptin, receive an authorization code, exchange it for access and refresh tokens, and call Daptin's userinfo/introspection endpoints.
This page covers Daptin issuing tokens. For Daptin consuming tokens from Google, GitHub, Dropbox, or another upstream provider, see OAuth-Authentication and Daptin-Google-OAuth-Complete-Flow.
When another Daptin instance or app uses this provider for browser login, that app is the OAuth client. Register the redirect URI for the client origin that the user's browser will actually visit. If an app frontend proxies to a Daptin OAuth client backend, register the frontend callback URL, such as https://app.example.com/oauth/response?authenticator=daptin-login, not an unrelated admin dashboard or backend-only host.
| Capability | Supported |
|---|---|
| OAuth authorization code flow | Yes |
| Refresh token flow | Yes |
| PKCE S256 | Yes, required |
| PKCE plain | Yes, compatibility only |
| OpenID Connect discovery | Yes |
| RS256 ID tokens | Yes |
| JWKS | Yes |
| UserInfo | Yes |
| Token introspection | Yes |
| Token revocation | Yes |
| Implicit flow | No |
| Client credentials flow | No |
The provider uses compact system table names.
| Table | Purpose |
|---|---|
oauth_app |
Registered OAuth clients |
oauth_code |
Authorization codes, stored as hashes |
oauth_access |
Access tokens, stored as hashes |
oauth_refresh |
Refresh tokens, stored as hashes |
oauth_grant |
Reserved for explicit user grants and consent records |
oauth_key |
RS256 signing keys for ID tokens and JWKS |
Do not confuse these with the existing OAuth consumer tables:
| Table | Purpose |
|---|---|
oauth_connect |
External provider connection config |
oauth_token |
Tokens received from external providers |
| Endpoint | Method | Purpose |
|---|---|---|
/.well-known/oauth-authorization-server |
GET | OAuth metadata |
/.well-known/openid-configuration |
GET | OIDC discovery |
/oauth/authorize |
GET | Create an authorization code for a signed-in Daptin user |
/oauth/token |
POST | Exchange authorization codes and refresh tokens |
/oauth/revoke |
POST | Revoke access or refresh token |
/oauth/introspect |
POST | Check whether an access token is active |
/oauth/userinfo |
GET, POST | Return claims for the access token user |
/oauth/jwks |
GET | Public signing keys |
Register clients through the Daptin action API. oauth_app is the internal storage table and is not the public management API.
curl -X POST http://localhost:6336/action/oauth_app/register_client \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"attributes": {
"name": "Example Client",
"redirect_uris": "https://client.example.com/callback",
"scopes": "openid profile email",
"grants": "authorization_code,refresh_token",
"is_confidential": true
}
}'The action returns generated client_id and client_secret. Store the secret immediately; it is returned only on registration and rotation.
| Endpoint | Purpose |
|---|---|
POST /action/oauth_app/register_client |
Register a new client |
POST /action/oauth_app/update_client |
Update client metadata |
POST /action/oauth_app/rotate_client_secret |
Generate and return a new secret |
POST /action/oauth_app/disable_client |
Disable a client |
POST /action/oauth_app/enable_client |
Enable a client |
POST /action/oauth_app/revoke_client_tokens |
Revoke access and refresh tokens for the client |
Instance-bound actions pass the app reference ID:
{
"attributes": {
"oauth_app_id": "OAUTH_APP_REFERENCE_ID"
}
}| Field | Notes |
|---|---|
client_id |
Public OAuth identifier generated by Daptin |
client_secret |
Required for confidential clients; generated by Daptin and returned only once |
redirect_uris |
Whitespace or comma separated exact redirect URI allow-list |
scopes |
Whitespace or comma separated allowed scopes |
grants |
Usually authorization_code,refresh_token
|
is_confidential |
If true, /oauth/token, /oauth/revoke, and /oauth/introspect require client secret auth |
is_enabled |
Disable a client without deleting it |
The client creates a random code_verifier, then sends:
code_challenge = BASE64URL(SHA256(code_verifier))
code_challenge_method = S256
PKCE is required. Requests without code_challenge fail before an authorization code is issued.
GET /oauth/authorize?
response_type=code&
client_id=example-client&
redirect_uri=https%3A%2F%2Fclient.example.com%2Fcallback&
scope=openid%20profile%20email&
state=random-csrf-state&
code_challenge=BASE64URL_SHA256_VERIFIER&
code_challenge_method=S256&
nonce=random-oidc-nonce
The user must be signed in to Daptin. If not, Daptin redirects to oauth.login_url from backend config, or /auth/signin by default. The built-in /auth/signin page accepts email/password, sets the normal Daptin token cookie, and returns to the original /oauth/authorize request through the same-origin return_to parameter.
Daptin validates:
response_type=code- client exists and is enabled
- exact
redirect_urimatch - requested grant is allowed
- requested scopes are allowed
- PKCE challenge is present
- signed-in Daptin user exists
Invalid redirect URIs return HTTP 400 directly and are not redirected.
The redirect_uri must exactly match one of the registered client redirect URIs. For Daptin OAuth consumer callbacks, include the authenticator query parameter in the provider registration because the Daptin consumer appends it when creating the authorization URL.
curl -X POST http://localhost:6336/oauth/token \
-u "example-client:replace-with-a-long-random-secret" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=$CODE" \
-d "redirect_uri=https://client.example.com/callback" \
-d "code_verifier=$CODE_VERIFIER"Response:
{
"access_token": "opaque-access-token",
"refresh_token": "opaque-refresh-token",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid profile email",
"id_token": "eyJ..."
}The authorization code is single-use. Replaying it returns invalid_grant.
Daptin includes a minimal provider login page for browser OAuth flows:
| Endpoint | Method | Purpose |
|---|---|---|
/auth/signin |
GET | Render the Daptin login form |
/auth/signin |
POST | Validate email/password, set the Daptin session cookie, and redirect to return_to
|
return_to must be a same-origin absolute path such as /oauth/authorize?.... Absolute external URLs and protocol-relative URLs are rejected and replaced with /.
This page is separate from the admin SPA /sign-in route. OAuth provider redirects use /auth/signin so Daptin can complete the server-side login and continue the authorization request without depending on frontend application routing.
curl -X POST http://localhost:6336/oauth/token \
-u "example-client:replace-with-a-long-random-secret" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=$REFRESH_TOKEN"Refresh tokens rotate on use. The old refresh token is revoked and replay fails.
curl http://localhost:6336/oauth/userinfo \
-H "Authorization: Bearer $ACCESS_TOKEN"Typical response:
{
"sub": "user-reference-id",
"email": "user@example.com",
"name": "User Name"
}curl -X POST http://localhost:6336/oauth/introspect \
-u "example-client:replace-with-a-long-random-secret" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=$ACCESS_TOKEN"Active response:
{
"active": true,
"client_id": "example-client",
"scope": "openid profile email",
"token_type": "Bearer",
"exp": 1777445590,
"sub": "user-reference-id"
}curl -X POST http://localhost:6336/oauth/revoke \
-u "example-client:replace-with-a-long-random-secret" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=$ACCESS_TOKEN"Discovery:
GET /.well-known/openid-configuration
JWKS:
GET /oauth/jwks
ID tokens use RS256 and include the active oauth_key key ID in the JWT header.
| Key | Default | Purpose |
|---|---|---|
oauth.issuer |
request scheme and host | Issuer used in metadata and ID tokens |
oauth.login_url |
/auth/signin |
Login redirect when /oauth/authorize has no Daptin user session |
encryption.secret |
instance default | Encrypts the OAuth signing private key |
Set an issuer:
curl -X POST http://localhost:6336/_config/backend/oauth.issuer \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d "https://auth.example.com"- Use HTTPS in production.
- Use exact redirect URIs. Wildcards are not supported.
- Use S256 PKCE for all clients.
- Authorization codes expire after 10 minutes and are single-use.
- Access tokens expire after 1 hour.
- Refresh tokens expire after 30 days and rotate on refresh.
- Bearer tokens and authorization codes are stored as SHA-256 hashes.
- Signing private keys are encrypted at rest in
oauth_key. - Token responses set
Cache-Control: no-storeandPragma: no-cache.
- There is no separate consent screen yet. A signed-in Daptin user authorizes the app directly when
/oauth/authorizeis called. -
oauth_grantexists for future explicit grant/consent records, but the current authorization handler does not depend on it. - OAuth client management is exposed through Daptin actions. Dynamic standards-based client registration is not implemented.
- Home
- Installation
- First-Admin-Setup ⭐ NEW
- Common-Errors 🔧 NEW
- Getting-Started-Guide
- Configuration
- Database-Setup
- Walkthrough-Product-Catalog
- Walkthrough-WebSocket-Real-Time ✨ NEW
- Walkthrough-YJS-Collaborative-Editing ✨ NEW
- Actions-Overview
- Action-Permission-Schema-Sync-Technical-KT
- User-Actions
- Admin-Actions
- Data-Actions
- Cloud-Actions
- Email-Actions
- Certificate-Actions
- Custom-Actions
- GraphQL-API ✓ NEW
- State-Machines
- Task-Scheduling ✓ NEW
- Template-Rendering ✓ NEW
- Data-Exchange
- Integrations