Authorization

App Permissions

Canonical permission states, role semantics, and lifecycle behavior for application access.

API Version

5.29.0

Overview

DoneIsBetter SSO implements app-level permission control to manage which users can access which applications. This is a centralized authorization layer separate from authentication.

Key Concepts:

  • Authentication - User proves their identity (login)
  • Authorization - SSO decides if user can access specific app
  • App Permission - Per-user, per-app access control record
  • Permission Status - Canonical states: pending, approved, revoked
  • No Record - Represented operationally as no permission record and surfaced as status: "none" in some read APIs
  • App Role - User's role within app: user or admin

Important

The app-permission contract lives in permission APIs, not in the OIDC id_token.

Permission Lifecycle

Permission lifecycle

text

           ┌──────────┐
           │   none   │  Initial state - no permission record exists
           └────┬─────┘
                │
                │ User attempts OAuth login
                ↓
           ┌──────────┐
           │ pending  │  Auto-created on first login attempt
           └────┬─────┘  Status: User waiting for admin approval
                │
                │ SSO Admin grants access
                ↓
           ┌──────────┐
           │ approved │  Status: User can access app
           └────┬─────┘  Role: 'user' or 'admin'
                │
                │ SSO Admin revokes access
                ↓
           ┌──────────┐
           │ revoked  │  Status: Access denied
           └──────────┘  Can be re-approved later

Permission Statuses

none (No Permission Record)

  • Meaning: User has never attempted to access this app
  • What happens during OAuth: SSO creates a pending record automatically
  • User experience: First login attempt shows "Access Pending Approval" message
  • Next step: SSO admin reviews and approves/denies

pending (Awaiting Admin Approval)

  • Meaning: User requested access, waiting for SSO admin decision
  • Database record:
    {
      userId: "550e8400-e29b-41d4-a716-446655440000",
      clientId: "your-app-client-id",
      status: "pending",
      role: "none",
      createdAt: "2025-10-15T12:00:00.000Z",
      updatedAt: "2025-10-15T12:00:00.000Z"
    }
  • User experience: SSO shows: "Your access request is pending approval. An admin will review your request shortly."
  • OAuth flow: Authorization stops at permission check, no code issued
  • How to approve: SSO admin uses Admin Panel → Users → App Permissions → Grant Access

approved (Access Granted)

  • Meaning: User has active permission to access app
  • Database record:
    {
      userId: "550e8400-e29b-41d4-a716-446655440000",
      clientId: "your-app-client-id",
      status: "approved",
      role: "admin",  // or "user"
      grantedAt: "2025-10-15T14:30:00.000Z",
      grantedBy: "admin-user-uuid",
      createdAt: "2025-10-15T12:00:00.000Z",
      updatedAt: "2025-10-15T14:30:00.000Z"
    }
  • User experience: OAuth completes successfully, user redirected to app
  • Token payload: OAuth tokens establish identity, but app-permission status and app role should still be read from permission APIs when authorization matters
  • Duration: Indefinite until revoked or role changed

revoked (Access Removed)

  • Meaning: User previously had access but it was removed
  • Database record:
    {
      userId: "550e8400-e29b-41d4-a716-446655440000",
      clientId: "your-app-client-id",
      status: "revoked",
      role: "none",  // Cleared when revoked
      revokedAt: "2025-10-15T16:00:00.000Z",
      revokedBy: "admin-user-uuid",
      createdAt: "2025-10-15T12:00:00.000Z",
      updatedAt: "2025-10-15T16:00:00.000Z"
    }
  • User experience: SSO shows: "Your access to this application has been revoked. Contact support if you believe this is an error."
  • Existing tokens: Remain valid until expiry (1 hour for access tokens)
  • Can be re-approved: Yes, SSO admin can grant access again

App-Level Roles

App permissions include a role field that defines the user's permissions within your app (not SSO itself).

Role: "user" (Standard Access)

  • Default role for most users
  • Can access app features but not admin functions
  • Your app should persist the validated permission response if it needs app-role decisions later in the request lifecycle
  • Example use: Regular users in Launchmass can view/edit their own pages

Role: "admin" (Elevated Access)

  • Granted by SSO admin for trusted users
  • Can access app's administrative features
  • Your app should treat this as an app-permission concept, not as a generic identity claim
  • Example use: Admins in Launchmass can manage all users and organizations

Important Distinction

SSO Admin manages the SSO service itself and grants app permissions.

App Admin is a user with role: "admin" for a specific app and manages app features.

These are separate. An SSO admin might not have any app permissions, and an app admin might not be an SSO admin.

Handling Permissions in Your App

1. During OAuth Callback

Extract identity from the ID token after token exchange, then validate app permission separately when your app needs authorization state:

// After POST /api/oauth/token
const { access_token, id_token } = await tokenResponse.json();
const jwt = require('jsonwebtoken');
const userInfo = jwt.decode(id_token);

// Store in your app's session
req.session.userId = userInfo.sub;
req.session.email = userInfo.email;
req.session.userType = userInfo.user_type;

// Validate app-level permission explicitly
const permissionResponse = await fetch(
  `https://sso.doneisbetter.com/api/users/${userInfo.sub}/apps/${process.env.SSO_CLIENT_ID}/permissions`,
  {
    headers: {
      Authorization: `Bearer ${access_token}`
    }
  }
);

const permission = await permissionResponse.json();
req.session.appRole = permission.role;
req.session.appStatus = permission.status;

2. Protecting Admin Routes

Use the validated app-permission role from your own session to guard admin-only features:

// Middleware for admin-only routes
function requireAppAdmin(req, res, next) {
  if (req.session.appRole !== 'admin' || req.session.appStatus !== 'approved') {
    return res.status(403).json({
      error: 'Admin access required'
    });
  }
  next();
}

// Use on admin routes
app.get('/admin/users', requireAppAdmin, (req, res) => {
  // Only app admins can access
});

3. Periodic Permission Validation

Check if user still has access (in case of revocation):

// Run periodically or on sensitive operations
async function validateAppAccess(req, res, next) {
  const accessToken = req.session.accessToken;
  
  // Validate permission with SSO
  const validation = await fetch(
    `https://sso.doneisbetter.com/api/users/${req.session.userId}/apps/${process.env.SSO_CLIENT_ID}/permissions`,
    {
    headers: {
      'Authorization': `Bearer ${accessToken}`
    }
    }
  );
  
  if (!validation.ok) {
    // Permission lookup failed, token expired, or caller is no longer authorized
    req.session.destroy();
    return res.redirect('/login');
  }

  const permission = await validation.json();
  if (permission.status !== 'approved' || permission.role === 'none') {
    req.session.destroy();
    return res.redirect('/access-pending');
  }
  
  next();
}

// Use on critical operations
app.post('/admin/delete-user', requireAppAdmin, validateAppAccess, async (req, res) => {
  // Double-check user still has admin access
});

Managing Permissions (SSO Admins)

SSO administrators manage app permissions through the Admin Panel at https://sso.doneisbetter.com/admin

Viewing User's App Permissions

  1. Login to SSO Admin Panel
  2. Navigate to Users list
  3. Click on a user to view details
  4. Scroll to "Application Access" section
  5. See all integrated apps and user's permission status for each

Granting Access

  1. In user's "Application Access" section
  2. Find app with status "revoked" or "pending"
  3. Select role: "user" or "admin"
  4. Click "Grant Access" or "Approve"
  5. User can now complete OAuth flow

Changing Role

  1. Find app with status "approved"
  2. Use role dropdown to change between "user" and "admin"
  3. Change applies immediately
  4. Note: Existing tokens still have old role until refresh

Revoking Access

  1. Find app with status "approved"
  2. Click "Revoke Access" button
  3. Confirm in dialog
  4. User's permission status set to "revoked"
  5. Note: Existing access tokens remain valid until expiry (max 1 hour)

App Permissions API

SSO provides REST APIs for programmatic permission management.Requires SSO admin authentication through the current admin session contract.

Get User's Permissions

GET https://sso.doneisbetter.com/api/admin/app-permissions/[userId]
Cookie: admin-session=... or public-session=...

// Response:
{
  userId: "550e8400-e29b-41d4-a716-446655440000",
  apps: [
    {
      clientId: "launchmass-client-id",
      name: "Launchmass",
      description: "Landing page builder",
      role: "admin",
      status: "approved",
      grantedAt: "2025-10-15T14:30:00.000Z",
      grantedBy: "admin-uuid"
    },
    {
      clientId: "messmass-client-id",
      name: "Messmass",
      description: "Messaging platform",
      role: "none",
      status: "pending",
      createdAt: "2025-10-15T16:00:00.000Z"
    }
  ]
}

Grant/Approve Access

POST https://sso.doneisbetter.com/api/admin/app-permissions/[userId]
Cookie: admin-session=... or public-session=...
Content-Type: application/json

{
  "clientId": "your-app-client-id",
  "role": "user",  // or "admin"
  "status": "approved"
}

// Response: 200 OK
{
  "userId": "550e8400-e29b-41d4-a716-446655440000",
  "clientId": "your-app-client-id",
  "appName": "Your App",
  "hasAccess": true,
  "status": "approved",
  "role": "user"
}

Update Role

PATCH https://sso.doneisbetter.com/api/admin/app-permissions/[userId]
Cookie: admin-session=... or public-session=...
Content-Type: application/json

{
  "clientId": "your-app-client-id",
  "role": "admin"  // Change from "user" to "admin"
}

// Response: 200 OK

Revoke Access

DELETE https://sso.doneisbetter.com/api/admin/app-permissions/[userId]
Cookie: admin-session=... or public-session=...
Content-Type: application/json

{
  "clientId": "your-app-client-id"
}

// Response: 200 OK

Security Considerations

  • Apps Cannot Grant Self-Access

    Only SSO admins can approve app permissions. Apps cannot call permission APIs with their OAuth credentials.

  • App Permission Is the Source of Truth

    Treat permission APIs as canonical for app access and app role. Do not assume the ID token alone reflects the latest app authorization state.

  • Revocation Not Immediate

    Revoking access doesn't invalidate existing tokens. They expire naturally (1 hour). Implement periodic validation for critical operations.

  • Validate Role Server-Side

    Don't trust client-side role checks. Always verify role from your backend session before granting admin features.

  • Audit Permission Changes

    All permission grants/revokes are logged with admin identity and timestamp in SSO database.

Related Documentation