Secure Login

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)

  1. User visits any protected route without a Clerk session → Clerk middleware redirects to /sign-in.

  2. User authenticates via Clerk (email/password, OAuth, etc.).

  3. On success, user is redirected to /redirect-check.

  4. redirect-check/page.tsx polls /api/get-role up to 15 times with exponential backoff (base delay 1000ms, doubling each retry).

  5. Once role is determined, user is forwarded to their dashboard (e.g., /admin, /creator, /learner).

Edge Cases

  • New user (no DB record): authenticateUserWithRole() calls syncUserIfNotExists() and retries currentUser() up to 3 times with 500ms backoff.

  • Database unavailable: Circuit breaker opens after 1 failure, returns 503 for 30s cooldown.

  • iOS Safari bfcache: BfCacheGuard component forces page reload on pageshow event when event.persisted === true.

  • Embed (share/SCORM): x-wyzquests-disable-clerk header 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-clerk header 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-revalidate on all authenticated pages.

Secondary Information

  • x-wyzquests-disable-clerk header (internal, for embed bypass).

  • Pragma: no-cache and Expires: 0 headers.

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_id TEXT (maps to Clerk's string ID)

  • role TEXT (one of: ADMIN, AGENCY, CREATOR, REVIEWER, LEARNER)

  • email TEXT

  • name TEXT


9. API ENDPOINTS

Method

Endpoint

Auth

File

Description

GET

/api/get-role

Clerk session

app/api/get-role/route.ts (63 lines)

Returns ApiResponseHelper.success({ role, actualRole, isViewingAsOther, agency }) — reads wyzquests_view_as_role cookie

*

* (middleware)

Clerk

middleware.ts

JWT clock skew 30000ms; public route matchers; embed bypass; cache headers


10. DATA REQUIREMENTS

Frontend Needs

  • Clerk useAuth(), useUser(), useClerk() hooks.

  • ClerkProviderWrapper component (components/ClerkProviderWrapper.tsx, 26 lines) for embed bypass.

  • BfCacheGuard component (components/BfCacheGuard.tsx, 31 lines) for iOS Safari.

API Calls Frontend Will Make

  • GET /api/get-role to resolve role after sign-in.

Caching Strategy

  • Cache-Control: no-store on all authenticated pages.

  • In-memory caches: userIdCache (5 min TTL), roleCache (30s TTL) in authenticate.ts.


11. PERFORMANCE CONSIDERATIONS

Database Optimization

  • Fast-path in authenticateUserWithRole(): checks app_users directly with maybeSingle() before calling currentUser() (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-check uses exponential backoff: BASE_RETRY_DELAY_MS = 1000 × 2^(attempt-1), max 15 retries.


12. SECURITY & AUTHORIZATION

Who can access this feature?

All users. Unauthenticated users see public pages only.

Authorization Logic

  • middleware.ts: Public route whitelist checked by createRouteMatcher. API routes return 401 if no userId.

  • Layout guards:

    • app/admin/layout.tsxauthenticateRole('ADMIN')

    • app/agency/layout.tsxauthenticateAgencyMember()

    • app/reviewer/layout.tsxauthenticateAnyRole(['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 /sign-in

Unauthenticated API route

401 { error: "Unauthorized" } JSON

User not in DB

syncUserIfNotExists() fallback creates record

currentUser() returns null (race condition)

Retry up to 3 times with 500ms×attempt backoff

Database unavailable

Circuit breaker → 503 ServiceUnavailableError

Invalid role in DB

AuthenticationError "Invalid role in database"


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 userIdCache TTL (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_users table 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


Was this article helpful?