App Permissions System

SSO Version: 5.24.0

Overview

The SSO service implements a two-level access control system:

  1. SSO-Level Permissions: Managed by SSO admins (who can access which apps)
  2. App-Level Permissions: Managed by your application (what users can do inside your app)

This document focuses on SSO-level permissions that affect OAuth 2.0 authentication.

Permission Status (SSO-Level)

Every user has a permissionStatus for each application they attempt to access:

StatusMeaningUser Action
approvedUser has been granted access by SSO admin✅ Can use the application
pendingUser requested access, awaiting admin approval⏳ Show "Access Pending" message
revokedUser's access was revoked by SSO admin❌ Show "Access Denied" message

App Roles (SSO-Level)

For approved users, the SSO admin assigns an app-level role:

  • admin - Full application access (intended for app administrators)
  • user - Standard application access (intended for regular users)
📝 Note: These roles are suggestions from the SSO admin. Your application decides what features each role can access.

Extracting Permissions from ID Token

import jwt from 'jsonwebtoken';

// WHY: ID token contains user info and app permissions
const idToken = req.cookies.id_token;
const decoded = jwt.decode(idToken);

// Extract permission fields
const {
  sub: userId,             // User ID
  email,                   // User email
  name,                    // User name
  role,                    // App role: 'admin' or 'user'
  permissionStatus,        // Permission status: 'approved', 'pending', 'revoked'
  iat,                     // Issued at (timestamp)
  exp                      // Expires at (timestamp)
} = decoded;

console.log('User:', userId);
console.log('Role:', role);  // 'admin' or 'user'
console.log('Status:', permissionStatus);  // 'approved', 'pending', 'revoked'

Implementing Permission Checks

Backend Middleware (Node.js/Express)

// middleware/requireApproval.js
import jwt from 'jsonwebtoken';

export function requireApproval(req, res, next) {
  const idToken = req.cookies.id_token;

  if (!idToken) {
    return res.status(401).json({ error: 'Not authenticated' });
  }

  try {
    const decoded = jwt.decode(idToken);
    const { permissionStatus, role } = decoded;

    // WHY: Check if user is approved to access this app
    if (permissionStatus !== 'approved') {
      if (permissionStatus === 'pending') {
        return res.status(403).json({ 
          error: 'APP_ACCESS_PENDING',
          message: 'Your access request is pending approval' 
        });
      }
      if (permissionStatus === 'revoked') {
        return res.status(403).json({ 
          error: 'APP_ACCESS_REVOKED',
          message: 'Your access has been revoked' 
        });
      }
      return res.status(403).json({ error: 'APP_ACCESS_DENIED' });
    }

    // Attach user info to request
    req.user = decoded;
    next();
  } catch (error) {
    console.error('Permission check failed:', error);
    return res.status(500).json({ error: 'Permission validation failed' });
  }
}

// Usage in routes
app.get('/api/protected', requireApproval, (req, res) => {
  res.json({ message: 'You have access!', user: req.user });
});

// Require admin role
export function requireAdmin(req, res, next) {
  requireApproval(req, res, () => {
    if (req.user.role !== 'admin') {
      return res.status(403).json({ 
        error: 'INSUFFICIENT_ROLE',
        message: 'Admin role required' 
      });
    }
    next();
  });
}

app.delete('/api/admin/users/:id', requireAdmin, (req, res) => {
  // Admin-only endpoint
});

Frontend Permission Handling

// React example
import { useAuth } from './contexts/AuthContext';
import { useRouter } from 'next/router';
import { useEffect } from 'react';

export function ProtectedRoute({ children, requireAdmin = false }) {
  const { user, permissionStatus, loading } = useAuth();
  const router = useRouter();

  useEffect(() => {
    if (loading) return;

    // Not authenticated
    if (!user) {
      router.push('/login');
      return;
    }

    // WHY: Check permission status
    if (permissionStatus === 'pending') {
      router.push('/access-pending');
      return;
    }

    if (permissionStatus === 'revoked') {
      router.push('/access-denied');
      return;
    }

    if (permissionStatus !== 'approved') {
      router.push('/access-denied');
      return;
    }

    // WHY: Check role requirement
    if (requireAdmin && user.role !== 'admin') {
      router.push('/unauthorized');
      return;
    }
  }, [user, permissionStatus, loading, requireAdmin]);

  if (loading || !user || permissionStatus !== 'approved') {
    return <div>Loading...</div>;
  }

  return <>{children}</>;
}

// Usage
function AdminDashboard() {
  return (
    <ProtectedRoute requireAdmin={true}>
      <h1>Admin Dashboard</h1>
      {/* Admin-only content */}
    </ProtectedRoute>
  );
}

Permission Status UI Examples

Access Pending Page

// pages/access-pending.js
export default function AccessPending() {
  return (
    <div>
      <h1>⏳ Access Pending</h1>
      <p>
        Your request to access this application is pending approval.
        An SSO administrator will review your request shortly.
      </p>
      <p>
        You will receive an email notification once your access is approved.
      </p>
      <button onClick={() => window.location.href = '/logout'}>
        Sign Out
      </button>
    </div>
  );
}

Access Denied Page

// pages/access-denied.js
export default function AccessDenied() {
  return (
    <div>
      <h1>❌ Access Denied</h1>
      <p>
        Your access to this application has been revoked or denied.
      </p>
      <p>
        If you believe this is an error, please contact the SSO administrator.
      </p>
      <button onClick={() => window.location.href = '/logout'}>
        Sign Out
      </button>
    </div>
  );
}

Two-Level Access Control Architecture

Here's how SSO-level and app-level permissions work together:

// Level 1: SSO Admin controls WHO can access the app
// -------------------------------------------------------
// SSO Admin grants user@example.com access to "myapp"
// SSO Admin assigns role: "user" or "admin"

// Level 2: Your App controls WHAT users can do inside the app
// -------------------------------------------------------
// Your app receives ID token with:
// - permissionStatus: 'approved'
// - role: 'admin' (or 'user')

// Your app decides:
if (role === 'admin') {
  // Grant access to:
  // - User management
  // - Organization settings
  // - Billing
  // etc.
} else if (role === 'user') {
  // Grant access to:
  // - View pages
  // - Edit own profile
  // - etc.
}

// Your app can ALSO have internal permissions:
// - Which organizations can this user access?
// - Which pages can this user edit?
// - etc.
// (These are stored in YOUR database, not in SSO)

Summary

  • ☑️ Always check permissionStatus before granting access
  • ☑️ Handle pending and revoked states gracefully
  • ☑️ Use role field to determine user's capabilities
  • ☑️ Implement backend middleware for permission checks
  • ☑️ Provide clear UI feedback for permission states