Feature Owner: scorevi (Sean Patrick Caintic)
Module: Auth & Security
Priority: P0
Sprint #12: Fully Implemented
Date: 2026-06-29
EXECUTIVE SUMMARY
What is this feature?
Clerk-based authentication middleware protecting all routes except public pages (sign-in, sign-up, forgot-password, SCORM embeds, guest review URLs). Includes layout-level role guards for admin, agency, creator, and reviewer dashboards.
Why does it matter?
Without authentication, anyone could access the platform, view private quests, or modify data. Every protected route must verify the user's identity before serving content or processing requests.
What's the MVP scope?
Clerk middleware with public route matchers, layout-level role authentication via authenticateRole(), authenticateAgencyMember(), and authenticateAnyRole(). Embed-safe bypass for share/SCORM pages. iOS Safari bfcache guard. JWT clock skew tolerance of 30s.
1. USER PAIN POINT & SOLUTION
Current State (Without Feature)
No login page. All pages publicly accessible. No way to associate quests with creators, track learner progress, or enforce role-based access.
Pain Point
Emotional: Users have no privacy — their quests and progress are visible to anyone.
Functional: No user identity means no personalization, no role-based dashboards, no attribution.
Business Impact: Platform is unusable as a SaaS product. Cannot support multiple user roles (admin, agency, creator, reviewer, learner).
Future State (With Feature)
Users sign in via Clerk. Middleware enforces authentication on all protected routes. Layouts check role membership and redirect unauthorized users. Embed-safe public routes allow quest sharing without login.
Marketing Hook
"Your quests. Your identity. Your dashboard. One secure login."
2. 4D FRAMEWORK MAPPING
Diagnose
N/A — auth infrastructure.
Design
Only authenticated creators can design quests. Role-based access ensures the right people see the right editor.
Develop
Only authenticated creators/learners can develop (author content, enroll).
Deliver
SCORM export routes are public to allow LMS embedding without re-authentication.
3. USER FLOWS
Entry Point
User navigates to any route in the app.
Success Criteria
Authenticated users are routed to their role-appropriate dashboard. Unauthenticated users are redirected to sign-in.
Main Flow (Happy Path)
User visits any protected route without a Clerk session → Clerk middleware redirects to
/sign-in.User authenticates via Clerk (email/password, OAuth, etc.).
On success, user is redirected to
/redirect-check.redirect-check/page.tsxpolls/api/get-roleup to 15 times with exponential backoff (base delay 1000ms, doubling each retry).Once role is determined, user is forwarded to their dashboard (e.g.,
/admin,/creator,/learner).
Edge Cases
New user (no DB record):
authenticateUserWithRole()callssyncUserIfNotExists()and retriescurrentUser()up to 3 times with 500ms backoff.Database unavailable: Circuit breaker opens after 1 failure, returns 503 for 30s cooldown.
iOS Safari bfcache:
BfCacheGuardcomponent forces page reload onpageshowevent whenevent.persisted === true.Embed (share/SCORM):
x-wyzquests-disable-clerkheader bypasses Clerk provider for public embedded routes.
Decision Points
IF route is public (
/sign-in,/share,/scorm, etc.) → skip auth, proceed.IF route is embed-safe public → set
x-wyzquests-disable-clerkheader in request.IF API route without
userId→ return 401 JSON.ELSE → proceed to layout role guard.
4. INFORMATION ARCHITECTURE
Primary Information (Always visible)
Clerk session token (managed by
@clerk/nextjs).Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidateon all authenticated pages.
Secondary Information
x-wyzquests-disable-clerkheader (internal, for embed bypass).Pragma: no-cacheandExpires: 0headers.
Actions
Primary CTA: Sign In (Clerk component).
Secondary Actions: Sign Up, Forgot Password.
5. WIREFRAMES
[Excluded — existing UI]
6. WIREFLOWS
Excluded.
7. PROTOTYPE
[Excluded — existing implementation]
8. BACKEND SCHEMA
Database Tables
app_users table (referenced via Clerk userId lookup):
clerk_idTEXT (maps to Clerk's string ID)roleTEXT (one of: ADMIN, AGENCY, CREATOR, REVIEWER, LEARNER)emailTEXTnameTEXT
9. API ENDPOINTS
Method | Endpoint | Auth | File | Description |
|---|---|---|---|---|
GET |
| Clerk session |
| Returns |
* |
| Clerk |
| JWT clock skew 30000ms; public route matchers; embed bypass; cache headers |
10. DATA REQUIREMENTS
Frontend Needs
Clerk
useAuth(),useUser(),useClerk()hooks.ClerkProviderWrappercomponent (components/ClerkProviderWrapper.tsx, 26 lines) for embed bypass.BfCacheGuardcomponent (components/BfCacheGuard.tsx, 31 lines) for iOS Safari.
API Calls Frontend Will Make
GET /api/get-roleto resolve role after sign-in.
Caching Strategy
Cache-Control: no-storeon all authenticated pages.In-memory caches:
userIdCache(5 min TTL),roleCache(30s TTL) inauthenticate.ts.
11. PERFORMANCE CONSIDERATIONS
Database Optimization
Fast-path in
authenticateUserWithRole(): checksapp_usersdirectly withmaybeSingle()before callingcurrentUser()(avoids Clerk API round-trip).In-memory role cache with 30s TTL coalesces dashboard load cascades.
API Response Time
Circuit breaker prevents retry storms when Supabase is unavailable — fails fast with 503 after 1 failure.
redirect-checkuses exponential backoff:BASE_RETRY_DELAY_MS = 1000× 2^(attempt-1), max 15 retries.
Who can access this feature?
All users. Unauthenticated users see public pages only.
middleware.ts: Public route whitelist checked bycreateRouteMatcher. API routes return 401 if nouserId.Layout guards:
app/admin/layout.tsx→authenticateRole('ADMIN')app/agency/layout.tsx→authenticateAgencyMember()app/reviewer/layout.tsx→authenticateAnyRole(['REVIEWER','ADMIN','CREATOR','AGENCY'])
Data Validation
N/A — auth layer, not data layer.
13. ERROR HANDLING
Error | Response |
|---|---|
Unauthenticated (no Clerk session) | Clerk redirects to |
Unauthenticated API route | 401 |
User not in DB |
|
| Retry up to 3 times with 500ms×attempt backoff |
Database unavailable | Circuit breaker → 503 |
Invalid role in DB |
|
14. TESTING CHECKLIST
Happy Path
□ New user signs up → Clerk session created → redirect-check polls role → routes to dashboard.
□ Returning user signs in → cached role lookup returns immediately → dashboard loads.
□ Public routes (/share/*, /scorm/*) load without Clerk session.
□ Api routes return 401 when unauthenticated.
Edge Cases
□ iOS Safari bfcache restore after sign-out → BfCacheGuard forces reload.
□ Supabase down during auth → circuit breaker returns 503 within 30s.
□ currentUser() null after OAuth → retry logic succeeds within 3 attempts.
□ Embed page with x-wyzquests-disable-clerk header → Clerk provider skipped.
15. OPEN QUESTIONS
Should
userIdCacheTTL (5 min) be shorter to pick up role changes faster?Should the circuit breaker threshold (1 failure) be higher for transient network blips?
16. OUT OF SCOPE (v1.1+)
Multi-factor authentication (MFA).
Social login providers beyond Clerk defaults.
Session revocation from admin panel.
17. SUCCESS METRICS
0 unauthorized access to protected routes.
<500ms average dashboard load time (cached path).
<5s redirect-check polling completion.
18. DEPENDENCIES
This feature depends on:
Clerk (
@clerk/nextjs) for identity management.Supabase
app_userstable for role mapping.lib/syncUser.ts(40 lines) for webhook-based user creation.lib/auth/authenticate.ts(885 lines) for all auth utility functions.components/ClerkProviderWrapper.tsx(26 lines) for embed bypass.components/BfCacheGuard.tsx(31 lines) for iOS Safari bfcache.
These features depend on this:
All protected routes and dashboards.
Role switching (0.1.1).
Sensitive action re-auth (0.5).
All creator/learner/admin/agency/reviewer features.
19. TIMELINE & OWNERSHIP
Implemented: Sprint 0 (foundational).
Owner: scorevi.
Document Version
1.0 - Initial version - 2026-06-29 07:54 UTC
1.0.1 - Minor edits - 2026-06-29 07:54 UTC
1.0.2 - Minor edits - 2026-06-29 07:58 UTC
1.0.3 - Minor edits - 2026-06-29 07:59 UTC
1.1 - Added Document Version section and update author to have full name - 2026-06-29 08:33 UTC
1.1.1 (Current Version) - Minor edits - 2026-06-29 08:34 UTC