App Permissions System
SSO Version: 5.24.0
Overview
The SSO service implements a two-level access control system:
- SSO-Level Permissions: Managed by SSO admins (who can access which apps)
- 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:
| Status | Meaning | User Action |
|---|---|---|
approved | User has been granted access by SSO admin | ✅ Can use the application |
pending | User requested access, awaiting admin approval | ⏳ Show "Access Pending" message |
revoked | User'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
permissionStatusbefore granting access - ☑️ Handle
pendingandrevokedstates gracefully - ☑️ Use
rolefield to determine user's capabilities - ☑️ Implement backend middleware for permission checks
- ☑️ Provide clear UI feedback for permission states