Skip to content

OAuth2 and OIDC Compatibility

This document describes UAAA's compliance with standard OAuth 2.0 and OpenID Connect protocols, as well as its extensions.

Standards Compliance

OAuth 2.0 (RFC 6749)

UAAA implements the following OAuth 2.0 specifications:

Authorization Code Grant (RFC 6749 Section 4.1)

  • Full implementation with standard parameters
  • response_type=code support
  • State parameter for CSRF protection

Refresh Token Grant (RFC 6749 Section 6)

  • Refresh token rotation
  • Automatic expiration handling
  • Confidential client support

Client Authentication (RFC 6749 Section 2.3)

  • HTTP Basic Authentication
  • Client credentials in request body
  • Support for public clients (no secret required)

Error Responses (RFC 6749 Section 5.2)

  • Standard error codes: invalid_request, invalid_client, invalid_grant, etc.
  • JSON error responses with error and error_description

PKCE (RFC 7636)

Proof Key for Code Exchange

  • Required for public clients
  • Optional for confidential clients
  • Support for S256 (SHA-256) and plain challenge methods
  • Standard code_challenge and code_verifier parameters

Device Authorization Grant (RFC 8628)

Device Flow Implementation

  • /oauth/device/code endpoint
  • device_code and user_code generation
  • Polling with interval and slow-down handling
  • authorization_pending and slow_down errors

OpenID Connect 1.0

Core Specification

  • /.well-known/openid-configuration discovery endpoint
  • ID Token generation with standard claims
  • /oauth/userinfo endpoint
  • openid scope requirement

Standard Claims

  • sub: Subject identifier (user ID)
  • name: Full name
  • email: Email address
  • email_verified: Email verification status
  • phone_number: Phone number
  • phone_number_verified: Phone verification status

RP-Initiated Logout (OpenID Connect RP-Initiated Logout 1.0)

  • /oauth/logout endpoint
  • id_token_hint parameter support
  • post_logout_redirect_uri parameter

UAAA Extensions to Standard OAuth2/OIDC

UAAA extends standard OAuth 2.0 and OpenID Connect with additional features for fine-grained access control and security levels.

1. Non-Standard JWT Claims

UAAA access tokens include three non-standard claims:

perm Claim (Permission Array)

Type: string[]Standard OAuth2 Equivalent: scope (but more granular)

Format:

  • Array of scoped permission strings
  • Each permission starts with /
  • Uses wildcard patterns (* and **)

Purpose:

  • Fine-grained resource access control
  • Hierarchical permission matching
  • Application-specific permissions

Example:

json
{
  "perm": ["/api/read", "/api/write", "/admin/**"]
}

Comparison with OAuth2 scope:

AspectOAuth2 scopeUAAA perm
FormatSpace-separated stringJSON array of strings
GranularityApplication-levelResource-level
WildcardsNoYes (* and **)
HierarchyNoYes (path-based)
LocationQuery parameterJWT claim

level Claim (Security Level)

Type: number (0-4) No Standard OAuth2 Equivalent

Values:

  • 0: HINT - Session lasting, user presence undetermined
  • 1: LOW - User likely present, identity not ensured
  • 2: MEDIUM - User present, weak credentials (password, email OTP)
  • 3: HIGH - Identity ensured (WebAuthn, TOTP, SMS OTP)
  • 4: MAX - Trusted admin credentials

Purpose:

  • Authentication strength indication
  • Step-up authentication requirements
  • Operation-specific security requirements

Example:

json
{
  "level": 2
}

Use Cases:

  • Require HIGH level for financial transactions
  • Allow LOW level for read-only operations
  • Enforce MAX level for admin console access

sid Claim (Session ID)

Type: stringNo Standard OAuth2 Equivalent

Purpose:

  • Session tracking across tokens
  • Token family management
  • Logout and revocation

Example:

json
{
  "sid": "session_abc123xyz"
}

2. Permission Scopes

UAAA extends the OAuth2 scope parameter with special formats:

Required Permissions

scope=uperm://app-id/resource/action

Example:

scope=openid profile uperm://myapp/api/read uperm://myapp/api/write

These permissions MUST be granted for the authorization to succeed.

Optional Permissions

scope=uperm+optional://app-id/resource/action

Example:

scope=uperm://myapp/api/read uperm+optional://myapp/admin/**

Optional permissions are granted if available but don't cause authorization failure.

Custom Parameters

scope=uaaa+param://base64(json_params)

Example:

scope=uaaa+param://eyJ0eXBlIjoiYm9vbCJ9

Allows passing custom parameters through the OAuth flow.

3. Security Level Requirements

Non-standard parameter in authorization request:

GET /oauth/authorize?
  client_id=myapp
  &response_type=code
  &redirect_uri=https://myapp.com/callback
  &scope=openid profile
  &security_level=3

Purpose: Request a minimum security level for the token.

Values: 0-4 (HINT/LOW/MEDIUM/HIGH/MAX)

Behavior:

  • If user's current session level is insufficient, they'll be prompted to upgrade
  • App tokens will be issued with the requested level (or current level if higher)
  • Falls back to application's default baseSecurityLevel if not specified

4. Upgradable Tokens

Non-standard scope value:

scope=openid profile upgradable

Purpose: Request a session token instead of an app token.

Difference:

  • App Token: Fixed security level, cannot be upgraded
  • Session Token: Security level can be upgraded during the session

Use Case: Long-lived sessions where users might need to elevate privileges (e.g., admin actions in a dashboard).

JWT Token Structure Comparison

Standard OAuth2 Access Token

json
{
  "iss": "https://auth.example.com",
  "sub": "user_123",
  "aud": "myapp.com",
  "iat": 1699999000,
  "exp": 1700000000,
  "scope": "read write"
}

UAAA Access Token (App Token)

json
{
  "iss": "https://auth.example.com",
  "sub": "user_123",
  "aud": "myapp.com",
  "client_id": "myapp.com",
  "sid": "session_abc123",
  "jti": "token_xyz789",
  "perm": ["/api/read", "/api/write"],
  "level": 2,
  "iat": 1699999000,
  "exp": 1700000000
}

Additional Claims:

  • client_id: OAuth2 client identifier
  • sid: Session ID (UAAA-specific)
  • jti: JWT ID for revocation
  • perm: Permission array (UAAA-specific, replaces scope)
  • level: Security level (UAAA-specific)

UAAA ID Token (OIDC)

json
{
  "iss": "https://auth.example.com",
  "sub": "user_123",
  "aud": "myapp.com",
  "iat": 1699999000,
  "exp": 1700000000,
  "nonce": "random_nonce",
  "name": "John Doe",
  "email": "john@example.com",
  "email_verified": true,
  "phone_number": "+1234567890",
  "phone_number_verified": true
}

Note: ID tokens are standard OIDC compliant and do NOT include perm, level, or sid claims.

Permission System

Storage Format (Database)

Permissions are stored in database as compact strings:

<app_id><path><query_params>

Examples:

  • "uaaa/session"
  • "myapp/api/read"
  • "myapp/admin/**"

JWT Format (Access Tokens)

Permissions in JWTs are scoped strings (app ID prefix removed):

/<path><query_params>

Examples:

  • "/session"
  • "/api/read"
  • "/admin/**"

Transformation

Database: "myapp/api/read"
    ↓ Permission.fromCompactString()
Parse: Permission { appId: "myapp", path: "/api/read" }
    ↓ Filter by targetAppId
    ↓ permission.toScopedString()
JWT: "/api/read"

Wildcard Matching

UAAA supports two wildcard types:

  1. Single-level wildcard (*): Matches one path segment

    /api/*/read matches:
    ✓ /api/users/read
    ✓ /api/posts/read
    ✗ /api/users/posts/read (too many segments)
  2. Multi-level wildcard (**): Matches any number of path segments (must be at end)

    /admin/** matches:
    ✓ /admin/users
    ✓ /admin/users/delete
    ✓ /admin/settings/security/2fa

Permission Inheritance

Permissions are hierarchical:

User has: ["/api/**"]

Can access:
✓ /api/read
✓ /api/write
✓ /api/admin/delete
✗ /other/resource (different path)

Client Authentication

UAAA supports both public and confidential clients:

Public Clients

  • No client secret required
  • PKCE is mandatory
  • Typical use: SPAs, mobile apps, native applications

Example:

bash
curl -X POST https://auth.example.com/oauth/token \
  -d "grant_type=authorization_code" \
  -d "code=auth_code_123" \
  -d "client_id=myapp" \
  -d "code_verifier=random_verifier" \
  -d "redirect_uri=https://myapp.com/callback"

Confidential Clients

  • Client secret required
  • PKCE is optional (but recommended)
  • Typical use: Backend services, server-side applications

Example with Basic Auth:

bash
curl -X POST https://auth.example.com/oauth/token \
  -H "Authorization: Basic base64(client_id:client_secret)" \
  -d "grant_type=authorization_code" \
  -d "code=auth_code_123" \
  -d "redirect_uri=https://myapp.com/callback"

Example with Body Parameters:

bash
curl -X POST https://auth.example.com/oauth/token \
  -d "grant_type=authorization_code" \
  -d "code=auth_code_123" \
  -d "client_id=myapp" \
  -d "client_secret=secret_123" \
  -d "redirect_uri=https://myapp.com/callback"

Discovery Endpoint

UAAA provides a standard OpenID Connect discovery endpoint:

GET /.well-known/openid-configuration

Response:

json
{
  "issuer": "https://auth.example.com",
  "authorization_endpoint": "https://auth.example.com/oauth/authorize",
  "token_endpoint": "https://auth.example.com/oauth/token",
  "userinfo_endpoint": "https://auth.example.com/oauth/userinfo",
  "end_session_endpoint": "https://auth.example.com/oauth/logout",
  "jwks_uri": "https://auth.example.com/api/public/jwks",
  "response_types_supported": ["code", "id_token"],
  "grant_types_supported": [
    "authorization_code",
    "refresh_token",
    "urn:ietf:params:oauth:grant-type:device_code"
  ],
  "subject_types_supported": ["public"],
  "id_token_signing_alg_values_supported": ["RS256"],
  "code_challenge_methods_supported": ["S256", "plain"]
}

Token Validation

Standard OAuth2 Token Introspection

UAAA access tokens are self-contained JWTs and can be validated without introspection endpoint:

  1. Verify JWT Signature using JWKS from jwks_uri

  2. Validate Standard Claims:

    • exp: Token not expired
    • iss: Matches UAAA issuer
    • aud: Matches your application ID
    • iat: Token issued time is valid
  3. Validate UAAA-Specific Claims:

    • client_id: Matches expected client
    • perm: Contains required permissions
    • level: Meets minimum security level requirement

Example validation (Node.js with jose):

javascript
import { jwtVerify, createRemoteJWKSet } from 'jose'

const JWKS = createRemoteJWKSet(
  new URL('https://auth.example.com/api/public/jwks')
)

async function validateToken(token) {
  const { payload } = await jwtVerify(token, JWKS, {
    issuer: 'https://auth.example.com',
    audience: 'myapp.com'
  })

  // Validate UAAA-specific claims
  if (!payload.perm || !Array.isArray(payload.perm)) {
    throw new Error('Invalid perm claim')
  }

  if (typeof payload.level !== 'number' || payload.level < 0 || payload.level > 4) {
    throw new Error('Invalid security level')
  }

  // Check permission
  if (!payload.perm.includes('/api/read')) {
    throw new Error('Insufficient permissions')
  }

  // Check security level
  if (payload.level < 2) {
    throw new Error('Security level too low')
  }

  return payload
}

Migration from Standard OAuth2

If you're migrating from a standard OAuth2 provider to UAAA:

1. Standard OAuth2 Flow

Your existing code should work without modification:

javascript
// Standard OAuth2 - Works with UAAA
const authUrl = `https://auth.example.com/oauth/authorize?` +
  `client_id=myapp&response_type=code&redirect_uri=${redirectUri}&scope=openid profile`

// Token exchange - Standard
const tokenResponse = await fetch('https://auth.example.com/oauth/token', {
  method: 'POST',
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: authCode,
    redirect_uri: redirectUri,
    client_id: 'myapp',
    client_secret: 'secret'
  })
})

2. Accessing UAAA Extensions

To use UAAA-specific features, add permission scopes:

javascript
const authUrl = `https://auth.example.com/oauth/authorize?` +
  `client_id=myapp&response_type=code&redirect_uri=${redirectUri}` +
  `&scope=openid profile uperm://myapp/api/read uperm://myapp/api/write` +
  `&security_level=2`

3. Token Validation

Update your validation to handle perm and level claims:

javascript
// Old: Check scope string
if (!token.scope.includes('read')) { ... }

// New: Check perm array
if (!token.perm.includes('/api/read')) { ... }

// New: Check security level
if (token.level < 2) { ... }

Limitations and Differences

1. No scope in Access Tokens

Unlike standard OAuth2, UAAA access tokens use perm claim instead of scope:

Not Included: scope claim in access tokens ✅ Included: perm array in access tokens

2. Client ID in Access Tokens

UAAA includes client_id in access tokens (not standard but common practice):

Included: client_id claim ℹ️ Purpose: Client identification and multi-tenant support

3. Session ID in Tokens

UAAA includes session tracking (not standard):

Included: sid claim ℹ️ Purpose: Token family management and revocation

4. Fixed Security Level in App Tokens

App tokens cannot be upgraded:

Cannot: Increase security level of existing token ✅ Can: Request new token with higher level via refresh

Best Practices

1. Use Standard OAuth2 When Possible

For maximum compatibility, use standard OAuth2 scopes when UAAA-specific features aren't needed:

javascript
// Good: Standard OAuth2
scope=openid profile email

// Good: When you need fine-grained control
scope=openid profile uperm://myapp/api/read uperm://myapp/api/write

2. Validate Both Standard and UAAA Claims

javascript
// Validate standard claims
if (!token.iss || token.iss !== 'https://auth.example.com') {
  throw new Error('Invalid issuer')
}

// Validate UAAA-specific claims
if (!Array.isArray(token.perm)) {
  throw new Error('Invalid permissions')
}

if (typeof token.level !== 'number') {
  throw new Error('Invalid security level')
}

3. Document UAAA Extensions in API

If your API requires specific security levels or permissions, document them:

POST /api/admin/delete-user
Authorization: Bearer <token>

Requirements:
- Security Level: HIGH (3) or above
- Permission: /admin/users/delete

4. Graceful Fallback

Handle missing UAAA claims gracefully:

javascript
const securityLevel = token.level ?? 1  // Default to LOW if missing
const permissions = token.perm ?? []     // Default to empty array

Resources

Next Steps

Released under the MIT License.