Authentication
The Backoffice uses Google OAuth 2.0 exclusively — there are no passwords. Authentication is handled by NextAuth.js 5 on the frontend, which exchanges Google tokens with the Identity Service to create a session JWT.
Sign-in flow
- User navigates to the Backoffice and clicks Sign in with Google.
- Browser redirects to Google's OAuth consent screen.
- User grants permission. Google redirects back to
/api/auth/callback/google. - NextAuth exchanges the authorization code for a Google token.
- The frontend calls
POST /v1/auth/signinon the Identity Service with{ email, displayName, provider: "google", providerId }. - The Identity Service creates or updates the user record and returns a signed JWT.
- NextAuth stores the JWT in an encrypted session cookie.
JWT claims
Every API call to the Campaign Service includes the JWT and a set of forwarded headers derived from the session:
| Header | Content |
|---|---|
Authorization: Bearer {token} | Signed JWT |
X-Percus-Forwarded-User-Id | User GUID |
X-Percus-Forwarded-User-Name | Display name |
X-Percus-Forwarded-Active-Org | Active organization GUID |
X-Percus-Forwarded-Org-Role | Organization role (e.g. OrganizationAdmin) |
X-Percus-Forwarded-System-Role | System role (Owner), if assigned |
The JWT itself carries these claims:
{
"sub": "user-uuid",
"active_org": "org-uuid",
"org_role": "OrganizationAdmin",
"system_role": "Owner",
"exp": 1234567890
}
system_role is only present for platform-level Owner users.
Switching organizations
A user can belong to multiple organizations. Switching is done via:
POST /v1/auth/switch-org
{ "organizationId": "uuid" }
The Identity Service returns a new JWT with the updated active_org and org_role claims. The frontend re-establishes the session with the new token.
User states
| State | Meaning |
|---|---|
Pending | Invited but has not yet accepted |
Active | Can sign in and use the platform |
Deactivated | Access revoked by an admin; cannot sign in |
Suspended | Suspended by the platform; cannot sign in |
A Pending user becomes Active when they accept their invitation via POST /v1/users/{userId}/accept-invitation.
Invitation flow
- An
OrganizationAdmincallsPOST /v1/users/invitewith the invitee's email and desired role. - The system creates a
Pendinguser and (when email is configured) sends an invitation email. - The invitee accepts via the invitation link → user becomes
Active. - Admins can resend invitations with
POST /v1/users/{userId}/resend-invitation.