Authentication Guide

API Version: 5.24.0

Overview

DoneIsBetter SSO implements OAuth 2.0 Authorization Code Flow with PKCE support. This is a standard, secure authentication protocol used by major providers (Google, GitHub, etc.).

Key Features:

  • Standard OAuth 2.0 - No proprietary client library needed
  • JWT-based tokens (access_token, refresh_token, id_token)
  • App-level permission control (pending → approved → revoked)
  • Role-based access within apps (user vs admin)
  • PKCE support for public clients (mobile apps, SPAs)
  • OpenID Connect compliance (/.well-known endpoints)

OAuth 2.0 Flow Diagram

┌─────────────┐                                    ┌──────────────┐
│  Your App   │                                    │  SSO Server  │
│  (Client)   │                                    │              │
└──────┬──────┘                                    └──────┬───────┘
       │                                                  │
       │ 1. Redirect to /api/oauth/authorize              │
       │    with client_id, redirect_uri, scope           │
       ├─────────────────────────────────────────────────>│
       │                                                  │
       │                                      2. User logs in
       │                                      3. Check app permissions
       │                                         - approved? → continue
       │                                         - pending? → show message
       │                                         - revoked? → deny
       │                                                  │
       │ 4. Redirect to your redirect_uri with code       │
       │<─────────────────────────────────────────────────┤
       │                                                  │
       │ 5. POST /api/oauth/token                         │
       │    with code, client_id, client_secret           │
       ├─────────────────────────────────────────────────>│
       │                                                  │
       │ 6. Return tokens                                 │
       │    { access_token, refresh_token, id_token }     │
       │<─────────────────────────────────────────────────┤
       │                                                  │
       │ 7. Use access_token for API calls               │
       │    Authorization: Bearer <access_token>          │
       ├─────────────────────────────────────────────────>│
       │                                                  │
       │ 8. Token expires after 1 hour                    │
       │                                                  │
       │ 9. POST /api/oauth/token with refresh_token      │
       ├─────────────────────────────────────────────────>│
       │                                                  │
       │ 10. Return new access_token                      │
       │<─────────────────────────────────────────────────┤

OAuth 2.0 Flow Steps

Step 1: Authorization Request

Redirect the user to the SSO authorization endpoint:

GET https://sso.doneisbetter.com/api/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=https://yourapp.com/auth/callback&response_type=code&scope=openid%20profile%20email&state=RANDOM_STATE_STRING

Required Parameters:

  • client_id - Your OAuth client ID (UUID)
  • redirect_uri - Where SSO redirects after authentication (must be registered)
  • response_type - Always code for authorization code flow
  • scope - Space-separated scopes (e.g., openid profile email)

Optional Parameters:

  • state - Random string to prevent CSRF attacks (recommended)
  • code_challenge - For PKCE (required for public clients)
  • code_challenge_method - Should be S256

Step 2: User Authentication

SSO presents a login page. Users can authenticate via:

  • Email + Password - Traditional login
  • Magic Link - Passwordless email link (15 min expiry)
  • PIN Code - 6-digit time-based code

Step 3: Permission Check

After authentication, SSO checks if user has permission to access your app:

// SSO checks appPermissions collection:
{
  userId: "user-uuid",
  clientId: "your-client-id",
  status: "approved",  // Can be: approved, pending, revoked, none
  role: "admin"        // App-level role: user or admin
}

Status Outcomes:

  • approved - User can access (proceed to step 4)
  • pending - SSO shows "Access Pending Approval" message
  • revoked or none - SSO shows "Access Denied"

🔒 Security Note: Only SSO admins can approve/revoke app access. Apps cannot grant themselves permission. See Admin Approval Guide.

Step 4: Authorization Code Redirect

If approved, SSO redirects back to your app with an authorization code:

https://yourapp.com/auth/callback?code=AUTH_CODE&state=RANDOM_STATE_STRING

Important: Authorization codes are:

  • Single-use only
  • Expire after 10 minutes
  • Must be exchanged server-side (never in browser)

Step 5: Token Exchange (Server-Side Only!)

Exchange the authorization code for tokens:

POST https://sso.doneisbetter.com/api/oauth/token
Content-Type: application/json

{
  "grant_type": "authorization_code",
  "code": "AUTH_CODE",
  "client_id": "YOUR_CLIENT_ID",
  "client_secret": "YOUR_CLIENT_SECRET",
  "redirect_uri": "https://yourapp.com/auth/callback"
}

Response:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Step 6: Decode ID Token

The id_token contains user identity and app-level role:

// Decode JWT (no verification needed if received from /token directly)
const jwt = require('jsonwebtoken');
const userInfo = jwt.decode(id_token);

// userInfo payload:
{
  "sub": "550e8400-e29b-41d4-a716-446655440000",  // User UUID
  "email": "user@example.com",
  "name": "John Doe",
  "role": "admin",  // App-level role: 'user' or 'admin'
  "iat": 1234567890,
  "exp": 1234571490,
  "iss": "https://sso.doneisbetter.com",
  "aud": "YOUR_CLIENT_ID"
}

Step 7: Use Access Token

Include access_token in API requests:

GET https://sso.doneisbetter.com/api/public/session
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

// Response:
{
  "userId": "550e8400-e29b-41d4-a716-446655440000",
  "email": "user@example.com",
  "name": "John Doe",
  "role": "admin"
}

Step 8: Token Refresh

Access tokens expire after 1 hour. Refresh without re-login:

POST https://sso.doneisbetter.com/api/oauth/token
Content-Type: application/json

{
  "grant_type": "refresh_token",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "client_id": "YOUR_CLIENT_ID",
  "client_secret": "YOUR_CLIENT_SECRET"
}

// Response:
{
  "access_token": "new_access_token",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "new_refresh_token"  // May rotate
}

Step 9: Token Revocation (Logout)

Revoke tokens when user logs out:

POST https://sso.doneisbetter.com/api/oauth/revoke
Content-Type: application/json

{
  "token": "access_token_or_refresh_token",
  "client_id": "YOUR_CLIENT_ID",
  "client_secret": "YOUR_CLIENT_SECRET"
}

Security Considerations

  • HTTPS Only

    All OAuth endpoints require HTTPS in production. HTTP only allowed in development.

  • Client Secret Protection

    NEVER expose client_secret in browser code. Token exchange MUST happen server-side.

  • State Parameter

    Always use state parameter to prevent CSRF attacks. Validate it matches after redirect.

  • PKCE for Public Clients

    If your app is a SPA or mobile app (can't securely store client_secret), use PKCE.

  • Token Storage

    Store tokens in HttpOnly cookies (backend) or secure memory (frontend - never localStorage for refresh tokens).

  • Token Expiration

    Access tokens expire after 1 hour. Refresh tokens expire after 30 days of inactivity.

  • Redirect URI Validation

    SSO validates redirect_uri exactly matches registered URIs. No wildcards or partial matches.

  • Permission Revocation

    If SSO admin revokes app access, user's existing tokens remain valid until expiry. Implement periodic session validation.

Error Handling

Common OAuth errors and how to handle them:

Authorization Errors (Step 1-3)

  • access_pending - User account exists but access not yet approved

    Show: "Your access request is pending admin approval."

  • access_denied - User denied authorization or doesn't have permission

    Show: "You don't have permission to access this application."

  • invalid_client - client_id not found or client suspended

    Action: Check client_id, contact SSO admins

  • unauthorized_client - Client not allowed to use this grant type

    Action: Verify OAuth client configuration

  • invalid_scope - Requested scope not allowed for this client

    Action: Request only registered scopes

Token Errors (Step 5, 8)

  • invalid_grant - Authorization code invalid, expired, or already used

    Action: Start OAuth flow again from step 1

  • invalid_client_secret - Client credentials wrong

    Action: Verify environment variables

  • redirect_uri_mismatch - redirect_uri doesn't match registered URI

    Action: Use exact registered URI (including protocol, port)

API Errors (Step 7)

  • 401 Unauthorized - Access token invalid, expired, or revoked

    Action: Try refresh token, then re-authenticate if that fails

  • 403 Forbidden - User lost app permission

    Action: Show "Access revoked" message, clear session

Implementation Best Practices

  1. ✅ Always use state parameter to prevent CSRF
  2. ✅ Exchange authorization code server-side (never in browser)
  3. ✅ Store client_secret in environment variables, never in code
  4. ✅ Implement token refresh before access token expires
  5. ✅ Handle permission errors gracefully (pending/revoked states)
  6. ✅ Validate state parameter on callback
  7. ✅ Use HTTPS in production (required by SSO)
  8. ✅ Clear tokens on logout (call /api/oauth/revoke)
  9. ✅ Periodically validate session (detect revoked access)
  10. ✅ Log OAuth errors for debugging (but never log secrets!)

OpenID Connect Support

DoneIsBetter SSO is OpenID Connect compliant. Discovery endpoints:

// Discovery document
GET https://sso.doneisbetter.com/.well-known/openid-configuration

// JWKS (public keys for token verification)
GET https://sso.doneisbetter.com/.well-known/jwks.json

Use these endpoints to automatically configure OAuth libraries (e.g., Passport.js, NextAuth.js).

Related Documentation