Authentication Guide
This guide covers JWT-based authentication, role-based access control, and integration with external auth providers. For complete technical details, see .context/api/authentication.md.Overview
The CMS uses JWT (JSON Web Token) authentication with RS256 signature verification:- Token Format: JWT (Bearer token)
- Algorithm: RS256 (RSA Signature with SHA-256)
- Provider: Keycloak, Auth0, or custom OAuth2
- Roles:
ROLE_CMS_EDITOR,ROLE_CMS_ADMIN
Quick Start
Development Mode
Generate a development token (DEV ONLY, disabled in production):Using the Token
Include the token in all authenticated requests:JWT Token Structure
Token Example
Header
Payload (Claims)
suboremail- User identifierexp- Expiration timestampiat- Issued at timestamp
roles- Array of role stringstenant_id- Tenant ID (optional, can be from user table)
Role-Based Access Control (RBAC)
Available Roles
ROLE_CMS_EDITOR
Permissions:- ✅ Read all content (articles, pages, media)
- ✅ Create content
- ✅ Update own content
- ✅ Schedule and publish content
- ✅ Generate preview tokens
- ❌ Delete content (admin only)
- ❌ Manage users, menus, blocks
ROLE_CMS_ADMIN
Permissions:- ✅ All ROLE_CMS_EDITOR permissions
- ✅ Delete content
- ✅ Archive/unarchive content
- ✅ Manage users (CRUD, reset passwords)
- ✅ Manage menus, blocks, settings
- ✅ Access all admin endpoints
Site-Scoped Roles
Beyond JWT roles, users have site-scoped roles assigned per site within a tenant:| Role | Level | Key Permissions |
|---|---|---|
| Editor | 1 | Read and create content |
| Publisher | 2 | Editor permissions + publish, schedule, archive content |
| Site Manager | 3 | Publisher permissions + manage content assignments, user roles |
| Site Owner | 4 | Full administrative access within the site (including user management) |
SiteRoleResolver from the UserSiteRole table. ROLE_CMS_ADMIN (from JWT) bypasses all site-role checks.
Role Hierarchy
Endpoint Protection
Per-Resource ACL
Beyond role-based access, individual resources (pages, articles, media) can have access control lists for fine-grained permissions.How It Works
- ACL entries are created for specific resources, granting permissions to individual users or site roles
- If a resource has ACL entries, only users with matching ACL (or admins/managers) can access it
- If a resource has no ACL entries, it remains visible to anyone with site-level permissions (backward-compatible)
Permission Levels
| Permission | Grants |
|---|---|
view | Read access to the resource |
edit | Modify the resource (includes workflow transitions) |
delete | Delete the resource |
manage | Full control (implies view, edit, and delete) |
ACL Grant Types
- User grant (
grant_type: "user"): Targets a specific user by UUID - Role grant (
grant_type: "role"): Targets all users with a specific site role (e.g.,publisher)
ACL API Endpoints
Managing ACL entries requires the
SITE_CONTENT_ASSIGN permission (site managers and above).ACL Bypass Rules
The following users bypass all ACL checks and always have full access:- Users with
ROLE_CMS_ADMIN(JWT role) - Users with
site_managerorsite_ownersite role
Workflow Endpoint ACL
All workflow actions (schedule, publish, archive, assign, etc.) enforce per-resource ACL:| Action | Required ACL Permission |
|---|---|
| Schedule, Publish, Archive, Unarchive | edit |
| Assign, Unassign | manage |
| View History | view |
Validation Rules
grant_type: "user"requiresuser_uuidto be providedgrant_type: "role"requiressite_roleto be provided- Duplicate ACL entries (same resource + grant + permission) return 409 Conflict
- ACL entries are immutable: to change a permission, delete and recreate
Integration Examples
Keycloak Integration
1. Configure Keycloak:- Create realm:
cms - Create client:
cms-api(confidential) - Add roles:
cms_editor,cms_admin - Map roles to token claims
Auth0 Integration
1. Create Auth0 Application:- Type: Machine to Machine
- Authorized API: CMS API
- Permissions:
read:articles,write:articles, etc.
Frontend Integration
Nuxt 3 (Front-Office)
Vue 3 (Back-Office)
API Request Interceptor
Security Best Practices
Token Storage
✅ DO:- Store access token in memory (JavaScript variable)
- Store refresh token in httpOnly cookie (server-side)
- Use secure cookies in production (https only)
- Never store tokens in localStorage (XSS vulnerable)
- Never store tokens in sessionStorage
- Never log tokens to console in production
- Never include tokens in URLs
Token Expiration
Recommended Lifetimes:- Access token: 5-15 minutes (short-lived)
- Refresh token: 30 minutes - 1 hour
- Dev token: 1 hour (development only)
Automatic Refresh
Troubleshooting
”Invalid JWT Token”
Causes:- Token expired (
expclaim in past) - Invalid signature (wrong public key)
- Token format incorrect
- Check token expiration:
jwt.ioto decode - Verify JWKS URL is correct and accessible
- Clear JWKS cache:
php bin/console cache:clear
”User not found”
Causes:- User doesn’t exist in
cms_userstable - Email mismatch between token and database
- Create user with matching email
- Check
suboremailclaim in token matches DB
”Access Denied”
Causes:- Missing required role
- Tenant mismatch (user not in tenant)
- Check user roles in database
- Verify
X-Tenant-Idheader matches user’stenant_id - Check endpoint security requirements
Further Reading
Complete Auth Docs
Detailed authentication documentation
User Management
User CRUD operations
Security Config
Symfony Security component
JWT Specification
JWT RFC 7519