Authentication Behaviors
Overview
Authentication in Integreat uses Google OAuth 2.0 as the primary identity provider. Users authenticate via Google, receive a JWT token, and establish a server-side session. The system supports role-based access control (admin, user, read-only), client-scoped permissions, session versioning for SSR rollout, and admin impersonation via signed JWT tokens.
Testing Philosophy
- Prefer unit tests for pure business logic (JWT generation, compression, validation)
- Use integration tests for database interactions and cross-system flows (OAuth callback, session management)
- Use UI tests only for end-to-end happy paths that touch multiple pages
- Every behavior must be user-visible; no tests for implementation details
Testing Patterns
Pattern: OAuth Login Flow
The standard authentication flow follows these steps:
- Unauthenticated user navigates to a protected page
- User is redirected to
/login
- User clicks "Sign in with Google"
- Browser redirects to Google OAuth consent screen
- User consents and Google redirects to
/api/oauth?code=<code>&state=<state>
- Server exchanges code for token, fetches profile, finds/creates user
- Server redirects to original page (or
/) with ?jwt=<token>
- Client reads JWT and establishes session
Test implications: Integration test the OAuth callback handler. UI test only the happy path once.
Pattern: Middleware Stack
Every protected route passes through authentication middleware:
wrap-session-version — validates session version, redirects to login if outdated
wrap-secure — checks for authenticated session, redirects to login if missing
wrap-admin — checks for admin role, redirects to login if not admin
wrap-client-redirect-unauthenticated — converts 401 responses to HTMX redirects
Test implications: Integration test each middleware independently. UI tests only verify redirect behavior.
Pattern: JWT Claims
The JWT token contains user identity and permissions:
:user, :exp, :db/id, :user/role, :user/name for all users
:gz-clients (compressed) for admin and read-only users
:user/clients (plain) for regular users
Test implications: Unit test JWT generation for each role type.
Login
Display Behaviors
| # |
Behavior |
Test Strategy |
Status |
| 1.1 |
It should display a "Sign in with Google" button on the login page |
UI |
[ ] |
OAuth Behaviors
| # |
Behavior |
Test Strategy |
Status |
| 1.2 |
It should redirect to Google OAuth when the user clicks "Sign in with Google" |
UI |
[ ] |
| 1.3 |
It should exchange the authorization code for an access token on callback |
Integration |
[ ] |
| 1.4 |
It should fetch the user's Google profile using the access token |
Integration |
[ ] |
| 1.5 |
It should create a new user account when the user logs in for the first time |
Integration |
[ ] |
| 1.6 |
It should find the existing user account on subsequent logins |
Integration |
[ ] |
| 1.7 |
It should redirect to the original page after successful OAuth |
Integration |
[ ] |
| 1.8 |
It should redirect to the root page when no return URL is provided |
Integration |
[ ] |
| 1.9 |
It should establish a server-side session with user identity and version |
Integration |
[ ] |
| 1.10 |
It should pass the JWT token in the query string after successful OAuth |
Integration |
[ ] |
| 1.11 |
It should display the user's clients and data after successful login |
UI |
[ ] |
| 1.12 |
It should handle users without email via Google provider ID |
Integration |
[ ] |
| 1.13 |
It should return 401 with error message when the OAuth code is missing |
Integration |
[ ] |
| 1.14 |
It should return 401 when the OAuth code is invalid or expired |
Integration |
[ ] |
| 1.15 |
It should return 401 and log a warning when the Google network request fails |
Integration |
[ ] |
Logout
| # |
Behavior |
Test Strategy |
Status |
| 2.1 |
It should clear the session when the user navigates to /logout |
Integration |
[ ] |
| 2.2 |
It should redirect to the login page after logout |
Integration |
[ ] |
| 2.3 |
It should remain idempotent when logging out without an active session |
Integration |
[ ] |
Impersonation
| # |
Behavior |
Test Strategy |
Status |
| 3.1 |
It should allow admin users to assume another user's identity via signed JWT |
Integration |
[ ] |
| 3.2 |
It should validate the impersonation JWT signature with :jwt-secret and :hs512 |
Integration |
[ ] |
| 3.3 |
It should reject expired impersonation JWTs |
Integration |
[ ] |
| 3.4 |
It should block non-admin users from accessing /impersonate |
Integration |
[ ] |
| 3.5 |
It should block unauthenticated users from accessing /impersonate |
Integration |
[ ] |
| 3.6 |
It should replace the admin's session with the impersonated user's session |
Integration |
[ ] |
Session Management
Authentication Gate Behaviors
| # |
Behavior |
Test Strategy |
Status |
| 4.1 |
It should allow authenticated requests to proceed to protected routes |
Integration |
[ ] |
| 4.2 |
It should redirect unauthenticated users to /login with a redirect-to parameter |
Integration |
[ ] |
| 4.3 |
It should return hx-redirect: /login for unauthenticated HTMX requests |
Integration |
[ ] |
Admin Gate Behaviors
| # |
Behavior |
Test Strategy |
Status |
| 5.1 |
It should allow admin requests to proceed to admin-only routes |
Integration |
[ ] |
| 5.2 |
It should redirect non-admin users to /login when accessing admin routes |
Integration |
[ ] |
Session Version Behaviors
| # |
Behavior |
Test Strategy |
Status |
| 6.1 |
It should invalidate sessions with outdated version numbers |
Integration |
[ ] |
| 6.2 |
It should redirect to /login when an outdated session accesses normal routes |
Integration |
[ ] |
| 6.3 |
It should return hx-redirect: /login for outdated sessions on HTMX routes |
Integration |
[ ] |
| 6.4 |
It should return 401 for outdated sessions on GraphQL routes |
Integration |
[ ] |
| 6.5 |
It should treat sessions without a version as outdated |
Integration |
[ ] |
Cross-Cutting Behaviors
JWT Behaviors
| # |
Behavior |
Test Strategy |
Status |
| 7.1 |
It should generate a JWT containing the user's role and client access on login |
Unit |
[ ] |
| 7.2 |
It should compress the client list for admin users to fit in the JWT |
Unit |
[ ] |
| 7.3 |
It should compress the client list for read-only users to fit in the JWT |
Unit |
[ ] |
| 7.4 |
It should include a plain client list for regular users in the JWT |
Unit |
[ ] |
| 7.5 |
It should create API tokens with admin role and 1000-day expiration |
Unit |
[ ] |
Middleware Behaviors
| # |
Behavior |
Test Strategy |
Status |
| 8.1 |
It should convert 401 responses to HTMX redirects for unauthenticated users |
Integration |
[ ] |
Role-Based Behaviors
| # |
Behavior |
Test Strategy |
Status |
| 9.1 |
It should allow admin users to access all clients |
Integration |
[ ] |
| 9.2 |
It should allow regular users to access only their assigned clients |
Integration |
[ ] |
| 9.3 |
It should allow read-only users to access all clients with view-only permissions |
Integration |
[ ] |
| 9.4 |
It should handle admin users with no clients by providing an empty compressed list |
Unit |
[ ] |
| 9.5 |
It should handle regular users with no clients by providing an empty client vector |
Unit |
[ ] |
Security Behaviors
| # |
Behavior |
Test Strategy |
Status |
| 10.1 |
It should reject tampered JWTs during impersonation |
Integration |
[ ] |
| 10.2 |
It should treat sessions with nil identity as unauthenticated |
Integration |
[ ] |
Test Data Requirements
| Entity |
Requirements |
| Users |
Admin user with multiple clients; regular user with subset of clients; read-only user with multiple clients; new user (not in database) with Google provider details; existing user with Google provider details |
| Clients |
Multiple clients with :client/code, :client/name, :client/locations; client associations on users |
| OAuth Mock |
Mock Google token endpoint responses (success and failure); mock Google userinfo endpoint responses |
Existing Tests to Preserve
Dependencies
- Google OAuth 2.0 (authorization code exchange and userinfo retrieval)
- Buddy JWT (token signing/unsigning with HS512)
- Datomic (user lookup and creation)
- Legacy SPA (login and needs-activation pages, no SSR tests)