Advanced OAuth 2.0 API
Auris supports several advanced OAuth 2.0 and OpenID Connect extensions beyond the standard Authorization Code + PKCE flow. These capabilities are designed for enterprise and specialized authentication scenarios:
- Device Authorization (RFC 8628) — for input-constrained devices like CLIs, smart TVs, and IoT
- Token Exchange (RFC 8693) — for impersonation and delegation between services
- DPoP (RFC 9449) — for sender-constrained tokens that are resistant to token theft
- CIBA (Client-Initiated Backchannel Authentication) — for authentication initiated by a backend service without browser interaction
- Adaptive MFA / Risk Assessment — for dynamic step-up authentication based on risk scoring
All advanced OAuth features must be enabled per-application in the Auris Console under Applications > [App] > Advanced OAuth.
Device Authorization (RFC 8628)
The Device Authorization Grant allows devices that cannot display a browser (CLIs, smart TVs, IoT devices, kiosks) to authenticate users. The device displays a short user code and a verification URL; the user visits the URL on a separate device (phone, laptop) and enters the code to approve.
/api/oauth/deviceRequest a device code and user code pair. The device displays the user_code and
verification_uri to the user, then polls the token endpoint until the user approves
or the code expires.
Request body
{
"client_id": "cli-app-client-id",
"scope": "openid profile email"
}| Field | Required | Description |
|---|---|---|
client_id | Yes | The application’s Client ID. The application must have Device Flow enabled. |
scope | No | Space-separated list of requested scopes. |
Success response
{
"ok": true,
"data": {
"device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
"user_code": "WDJB-MJHT",
"verification_uri": "https://api.altovar.net/hosted/device",
"verification_uri_complete": "https://api.altovar.net/hosted/device?user_code=WDJB-MJHT",
"expires_in": 1800,
"interval": 5
}
}| Field | Description |
|---|---|
device_code | A long, opaque code used by the device to poll the token endpoint. Never shown to the user. Stored as a SHA-256 hash on the server. |
user_code | A short, human-readable 8-character code (format: XXXX-XXXX) displayed to the user. |
verification_uri | The URL the user visits on a separate device to enter the code. |
verification_uri_complete | The URL with the code pre-filled. Useful for QR codes. |
expires_in | Seconds until the device code expires (default: 30 minutes). |
interval | Minimum seconds between polling requests. |
Error codes
| Code | HTTP | Description |
|---|---|---|
DEVICE_FLOW_DISABLED | 400 | Device Authorization is not enabled for this application |
VALIDATION_ERROR | 400 | Missing client_id |
Polling for Tokens
After displaying the user code, the device polls the token endpoint at the specified interval:
Request body
{
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
"device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
"client_id": "cli-app-client-id"
}Pending response (user has not yet approved)
{
"ok": false,
"error": {
"code": "AUTHORIZATION_PENDING",
"message": "The user has not yet approved the request. Continue polling."
}
}Slow down response (polling too frequently)
{
"ok": false,
"error": {
"code": "SLOW_DOWN",
"message": "Polling too frequently. Increase interval by 5 seconds."
}
}Success response (user approved)
{
"ok": true,
"data": {
"accessToken": "eyJhbGciOiJSUzI1NiJ9...",
"refreshToken": "rt_...",
"expiresIn": 900,
"tokenType": "Bearer"
}
}Error codes during polling
| Code | HTTP | Description |
|---|---|---|
AUTHORIZATION_PENDING | 400 | User has not yet approved — continue polling |
SLOW_DOWN | 400 | Polling too fast — increase interval |
EXPIRED_TOKEN | 400 | The device code has expired. Start a new flow. |
ACCESS_DENIED | 400 | The user denied the authorization request |
The public verification page at verification_uri shows the hosted login form pre-scoped to device approval. The user enters the code, authenticates (password, SSO, magic link, etc.), and approves. The device’s next poll returns tokens.
User Verification Page
Auris provides a hosted verification page at /hosted/device where the user:
- Enters the 8-character user code (or arrives via
verification_uri_completewith the code pre-filled) - Authenticates using any configured method (password, SSO, magic link, 2FA)
- Approves the device authorization request
- Sees a confirmation screen and can close the tab
Token Exchange (RFC 8693)
Token Exchange allows a service to exchange one token for another — either to impersonate a user (act as them) or to delegate access (act on behalf of them while retaining the original subject). This is essential for microservice architectures where an API gateway needs to call downstream services with different token scopes.
/api/auth/tokenRequires: impersonate:users or delegate:tokensExchange a token using the Token Exchange grant type. The caller must present a valid subject token and specify the exchange type.
Request body — Impersonation
Impersonation replaces the subject entirely. The resulting token has the target user as the sub claim. Use this when an admin needs to act as a user for debugging.
{
"grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
"subject_token": "eyJhbGciOiJSUzI1NiJ9...",
"subject_token_type": "urn:ietf:params:oauth:token-type:access_token",
"requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
"exchange_type": "impersonation",
"target_user_id": "usr_target123"
}Request body — Delegation
Delegation preserves the original subject and adds an act (actor) claim to the resulting token. The downstream service can see both who the token is for and who is acting on their behalf.
{
"grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
"subject_token": "eyJhbGciOiJSUzI1NiJ9...",
"subject_token_type": "urn:ietf:params:oauth:token-type:access_token",
"requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
"exchange_type": "delegation"
}| Field | Required | Description |
|---|---|---|
grant_type | Yes | Must be urn:ietf:params:oauth:grant-type:token-exchange |
subject_token | Yes | The existing access token to exchange |
subject_token_type | Yes | Must be urn:ietf:params:oauth:token-type:access_token |
requested_token_type | Yes | The desired output token type. Typically urn:ietf:params:oauth:token-type:access_token |
exchange_type | Yes | Either impersonation or delegation |
target_user_id | Yes* | Required for impersonation — the user ID to impersonate |
scope | No | Space-separated scopes for the new token. Must be a subset of the original. |
Success response — Impersonation
{
"ok": true,
"data": {
"accessToken": "eyJhbGciOiJSUzI1NiJ9...",
"issuedTokenType": "urn:ietf:params:oauth:token-type:access_token",
"tokenType": "Bearer",
"expiresIn": 900
}
}The impersonated token’s JWT payload will have "sub": "usr_target123" with the impersonating user’s original identity recorded in audit logs.
Success response — Delegation
{
"ok": true,
"data": {
"accessToken": "eyJhbGciOiJSUzI1NiJ9...",
"issuedTokenType": "urn:ietf:params:oauth:token-type:access_token",
"tokenType": "Bearer",
"expiresIn": 900
}
}The delegated token’s JWT payload includes an act claim:
{
"sub": "usr_original_user",
"act": {
"sub": "usr_acting_service"
},
"scope": "read:users"
}Error codes
| Code | HTTP | Description |
|---|---|---|
TOKEN_EXCHANGE_DISABLED | 400 | Token Exchange is not enabled for this application |
INVALID_SUBJECT_TOKEN | 400 | The subject token is invalid or expired |
TARGET_USER_NOT_FOUND | 404 | The target_user_id does not exist |
PERMISSION_DENIED | 403 | Caller lacks impersonate:users or delegate:tokens permission |
SCOPE_EXCEEDS_ORIGINAL | 400 | Requested scope is not a subset of the original token’s scope |
Impersonation is a highly privileged operation. The impersonate:users permission should be restricted to admin roles and M2M service accounts that require it. All impersonation events are recorded in the audit log with both the actor and the impersonated user.
DPoP — Sender-Constrained Tokens (RFC 9449)
Demonstration of Proof-of-Possession (DPoP) binds tokens to a specific client by requiring a cryptographic proof with each request. Even if a DPoP-bound token is stolen, it cannot be used without the corresponding private key.
How DPoP Works
- The client generates a key pair (typically EC P-256 or RSA) and keeps the private key secure.
- On each request, the client creates a signed DPoP proof JWT containing the HTTP method, URL, and a unique
jti. - The server validates the proof, extracts the JWK thumbprint, and binds the issued token to that key.
- Subsequent API calls must include both the DPoP token and a fresh DPoP proof signed with the same key.
Requesting DPoP-Bound Tokens
Include a DPoP header with any token request (login, refresh, authorization code exchange):
POST /api/auth/token HTTP/1.1
Content-Type: application/json
DPoP: eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0IiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiLi4uIiwieSI6Ii4uLiJ9fQ.eyJodG0iOiJQT1NUIiwiaHR1IjoiaHR0cHM6Ly95b3VyLWF1cmlzLWRvbWFpbi5jb20vYXBpL2F1dGgvdG9rZW4iLCJpYXQiOjE3MDg1MjEyMDAsImp0aSI6InVuaXF1ZS1pZC0xMjMifQ.signatureDPoP proof JWT structure
Header:
{
"alg": "ES256",
"typ": "dpop+jwt",
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "...",
"y": "..."
}
}Payload:
{
"htm": "POST",
"htu": "https://api.altovar.net/api/auth/token",
"iat": 1708521200,
"jti": "unique-id-123",
"nonce": "server-provided-nonce"
}| Field | Description |
|---|---|
htm | The HTTP method of the request (POST, GET, etc.) |
htu | The HTTP URI of the request (without query parameters) |
iat | Issued at timestamp. Must be within a short window (typically 60 seconds). |
jti | A unique identifier to prevent replay attacks |
nonce | Server-provided nonce (included if the server returned a DPoP-Nonce header) |
Token response with DPoP
When a DPoP proof is included, the response token is DPoP-bound:
{
"ok": true,
"data": {
"accessToken": "eyJhbGciOiJSUzI1NiJ9...",
"tokenType": "DPoP",
"expiresIn": 900
}
}Note that tokenType is "DPoP" instead of "Bearer". The access token’s JWT contains a cnf (confirmation) claim with the JWK thumbprint:
{
"sub": "usr_abc123",
"cnf": {
"jkt": "JWK_THUMBPRINT_BASE64URL"
}
}Nonce Management
The server may require a nonce for replay protection. When it does, the response includes:
DPoP-Nonce: eyJhbGciOiJIUzI1NiJ9...Include this nonce in subsequent DPoP proofs. If a request arrives without a required nonce, the server returns 400 with a use_dpop_nonce error and a fresh nonce in the header.
Error codes
| Code | HTTP | Description |
|---|---|---|
INVALID_DPOP_PROOF | 400 | DPoP proof JWT is malformed, expired, or has an invalid signature |
DPOP_NONCE_REQUIRED | 400 | Server requires a nonce — retry with the nonce from the DPoP-Nonce response header |
DPOP_JKT_MISMATCH | 401 | The DPoP proof was signed with a different key than the one the token is bound to |
DPOP_DISABLED | 400 | DPoP is not enabled for this application |
CIBA — Client-Initiated Backchannel Authentication
CIBA allows a backend service to initiate authentication for a user without requiring the user to interact with a browser redirect. Instead, the user receives a notification (push, SMS, or email) and approves the request on their device.
This is useful for scenarios like call center authentication (“I’m calling from your bank, please approve the login on your phone”) or point-of-sale transactions.
/api/oauth/backchannel/authorizeRequires: manage:ciba_configInitiate a CIBA authentication request. The server sends a notification to the identified user. The calling service then polls (or receives a callback) to obtain tokens once the user approves.
Request body
{
"client_id": "backend-service-id",
"client_secret": "backend-service-secret",
"scope": "openid profile",
"login_hint": "[email protected]",
"binding_message": "Approve login for Order #12345",
"requested_expiry": 300
}| Field | Required | Description |
|---|---|---|
client_id | Yes | The M2M application’s Client ID |
client_secret | Yes | The M2M application’s Client Secret |
scope | No | Requested scopes |
login_hint | Yes | Email address or user ID identifying the user to authenticate |
binding_message | No | Human-readable message displayed to the user in the notification (max 256 chars) |
requested_expiry | No | Seconds until the request expires (default: 300, max: 600) |
Success response
{
"ok": true,
"data": {
"auth_req_id": "ciba_req_abc123def456",
"expires_in": 300,
"interval": 5
}
}Notification Modes
CIBA supports three notification delivery modes, configured per-application:
| Mode | Description |
|---|---|
poll | The service polls the token endpoint using auth_req_id. Default mode. |
ping | Auris sends an HTTP callback to the application’s registered notification_endpoint when the user responds. The service then calls the token endpoint. |
push | Auris sends the tokens directly to the notification_endpoint in the callback payload. No polling needed. |
Polling for CIBA Tokens
For poll mode, the service polls the token endpoint:
{
"grant_type": "urn:openid:params:grant-type:ciba",
"auth_req_id": "ciba_req_abc123def456",
"client_id": "backend-service-id",
"client_secret": "backend-service-secret"
}The polling responses follow the same pattern as Device Authorization:
AUTHORIZATION_PENDINGwhile waiting for user approvalSLOW_DOWNif polling too frequentlyEXPIRED_TOKENif the request timed outACCESS_DENIEDif the user rejected the request- Full token response on approval
Error codes
| Code | HTTP | Description |
|---|---|---|
CIBA_DISABLED | 400 | CIBA is not enabled for this application |
USER_NOT_FOUND | 404 | login_hint does not match any user |
NOTIFICATION_FAILED | 500 | Failed to deliver the authentication notification to the user |
BINDING_MESSAGE_TOO_LONG | 400 | binding_message exceeds 256 characters |
Risk Assessment and Adaptive MFA
Auris performs automatic risk assessment on every authentication attempt. The risk score is computed from five weighted factors and determines whether additional authentication steps (step-up MFA) are required.
Risk Scoring Factors
| Factor | Weight | Description |
|---|---|---|
| IP Reputation | 20% | Known malicious IPs, VPNs, proxies, data centers |
| Device Trust | 20% | Whether the device fingerprint has been seen before |
| Geo Anomaly | 20% | Impossible travel detection (login from a distant location too quickly) |
| Behavior | 20% | Unusual login patterns (time of day, frequency) |
| Action Sensitivity | 20% | How sensitive the requested action is |
Risk levels: LOW (0-30), MEDIUM (31-60), HIGH (61-80), CRITICAL (81-100).
When the risk score exceeds configured thresholds, Auris automatically challenges the user with step-up MFA before issuing tokens. The acr (Authentication Context Class Reference) and amr (Authentication Methods References) claims in the JWT reflect the actual authentication level achieved.
/api/auth/risk/assessmentsRequires: view:securityList recent risk assessments across the tenant. Useful for monitoring suspicious login patterns and reviewing risk scoring decisions.
Query parameters
| Parameter | Type | Description |
|---|---|---|
page | integer | Page number (default: 1) |
limit | integer | Items per page (default: 20) |
userId | string | Filter by user ID |
level | LOW | MEDIUM | HIGH | CRITICAL | Filter by risk level |
dateFrom | ISO 8601 | Start of date range |
dateTo | ISO 8601 | End of date range |
Success response
{
"ok": true,
"data": {
"data": [
{
"id": "risk_abc123",
"userId": "usr_def456",
"score": 72,
"level": "HIGH",
"factors": {
"ipReputation": { "score": 85, "isVpn": true, "isProxy": false },
"deviceTrust": { "score": 50, "isNewDevice": true },
"geoAnomaly": { "score": 90, "distance": 5200, "timeSinceLastLogin": 1800 },
"behavior": { "score": 60, "unusualTime": true },
"actionSensitivity": { "score": 75 }
},
"actionTaken": "step_up_mfa",
"ipAddress": "203.0.113.42",
"country": "CN",
"city": "Beijing",
"createdAt": "2025-02-18T03:45:00Z"
}
],
"pagination": { "page": 1, "limit": 20, "total": 156, "totalPages": 8 }
}
}/api/auth/risk/rulesRequires: view:securityList all custom risk rules configured for the tenant. Risk rules allow overriding or augmenting the default scoring for specific conditions.
Success response
{
"ok": true,
"data": [
{
"id": "rule_abc123",
"name": "Block known VPN ranges",
"condition": {
"field": "ipReputation.isVpn",
"operator": "equals",
"value": true
},
"action": "block",
"scoreModifier": 40,
"isActive": true,
"createdAt": "2025-01-15T10:00:00Z"
}
]
}/api/auth/risk/rulesRequires: manage:securityCreate a custom risk rule. Rules are evaluated during login and can modify the risk score, require additional authentication, or block the login entirely.
Request body
{
"name": "Require MFA for new countries",
"condition": {
"field": "geoAnomaly.isNewCountry",
"operator": "equals",
"value": true
},
"action": "step_up_mfa",
"scoreModifier": 30,
"isActive": true
}| Field | Required | Description |
|---|---|---|
name | Yes | Human-readable name for the rule |
condition | Yes | Condition object with field, operator, and value |
action | Yes | One of: allow, step_up_mfa, block, log |
scoreModifier | No | Points to add to the risk score when the condition matches (0-100) |
isActive | No | Whether the rule is active (default: true) |
Available condition fields: ipReputation.isVpn, ipReputation.isProxy, ipReputation.isDatacenter, deviceTrust.isNewDevice, geoAnomaly.isNewCountry, geoAnomaly.distance, behavior.unusualTime, behavior.failedAttempts.
Available operators: equals, not_equals, greater_than, less_than, contains, in.
Success response
{
"ok": true,
"data": {
"id": "rule_def456",
"name": "Require MFA for new countries",
"condition": {
"field": "geoAnomaly.isNewCountry",
"operator": "equals",
"value": true
},
"action": "step_up_mfa",
"scoreModifier": 30,
"isActive": true,
"createdAt": "2025-02-18T10:00:00Z"
}
}/api/auth/risk/rules/[id]Requires: manage:risk_rulesUpdate a risk rule’s name, condition, action, score modifier, or active state.
Request body
{
"name": "Require MFA for new countries (updated)",
"scoreModifier": 50,
"isActive": true
}/api/auth/risk/rules/[id]Requires: manage:securityDelete a risk rule. Takes effect immediately on the next login attempt.
Success response
{
"ok": true,
"data": { "deleted": true }
}ACR and AMR in Tokens
When adaptive MFA triggers step-up authentication, the issued JWT includes acr and amr claims that downstream services can use to verify the authentication strength:
{
"sub": "usr_abc123",
"acr": "urn:auris:acr:mfa",
"amr": ["pwd", "otp"],
"iat": 1708521200,
"exp": 1708522100
}| Claim | Description |
|---|---|
acr | Authentication Context Class Reference. Values: urn:auris:acr:pwd (password only), urn:auris:acr:mfa (password + second factor), urn:auris:acr:strong (password + strong second factor like WebAuthn) |
amr | Authentication Methods References. Array of methods used: pwd, otp (TOTP), sms, webauthn, social, magic_link, sso |
Resource servers can require a minimum acr level for sensitive operations by checking the JWT claims before processing the request.
Permissions Reference
| Permission | Description |
|---|---|
manage:device_codes | Manage Device Authorization Flow configuration |
impersonate:users | Exchange tokens for impersonation (act as another user) |
delegate:tokens | Exchange tokens for delegation (act on behalf of another user) |
manage:dpop_config | Configure DPoP settings for applications |
manage:ciba_config | Configure CIBA settings and notification endpoints |
view:risk_assessments | View risk assessment logs and scoring details |
manage:risk_rules | Create, update, and delete custom risk rules |
manage:advanced_oauth | Full access to all advanced OAuth configuration |
All advanced OAuth features require explicit enablement on each application. The application’s settings in the Auris Console include toggle switches for Device Flow, Token Exchange, DPoP, and CIBA. The corresponding enable* flags on the Application model control availability.
Related
- DPoP (Proof of Possession) — How DPoP proof validation works
- Device Authorization Flow — Device flow protocol details
- CIBA (Backchannel Auth) — CIBA protocol architecture
- Token Exchange (RFC 8693) — Token exchange protocol details
- Implementing DPoP — Step-by-step DPoP integration
- Device Authorization Flow — Device flow integration guide
- CIBA Guide — CIBA integration walkthrough
- Token Exchange — Impersonation and delegation guide
- Advanced OAuth2 — Configure advanced OAuth2 features from the Console