User Role Switching

Feature Owner: scorevi (Sean Patrick Caintic)
Module: Auth & Security
Priority: P1
Sprint #12: Fully Implemented (PR #406)
Date: 2026-06-29


EXECUTIVE SUMMARY

What is this feature?
The "View As" system allows privileged users (Admins, Creators, Reviewers) to temporarily preview lower-role dashboards without changing their actual role. A wyzquests_view_as_role cookie stores the preview role, and every auth check validates it against a strict hierarchy.

Why does it matter?
Admins need to verify what learners see. Creators need to preview the reviewer interface. Without this, testing role-specific UX requires creating multiple accounts.

What's the MVP scope?
VIEW_AS_HIERARCHY in lib/schemas/role-switch.schema.ts defines which roles each role can preview. getEffectiveRole() in authenticate.ts validates cookies against the hierarchy. RoleSwitcher UI component (290 lines). /api/user/switch-role and /api/user/available-roles endpoints.


1. USER PAIN POINT & SOLUTION

Current State (Without Feature)

To test the learner dashboard, an admin must sign out, create a learner account, sign in, test, sign out, and sign back in as admin. This is slow and disruptive.

Pain Point

Emotional: Frustration at the friction of switching contexts.
Functional: Multi-account management is tedious and error-prone. Cannot quickly verify how content appears to different roles.
Business Impact: Slower QA cycles. Admins and support staff waste time context-switching.

Future State (With Feature)

A dropdown in the sidebar lets privileged users instantly switch their view to any lower role. The cookie is secure (httpOnly, sameSite lax, 4h expiry) and validated server-side against a strict hierarchy to prevent privilege escalation.

Marketing Hook

"One account. Every perspective. Switch roles instantly."


2. 4D FRAMEWORK MAPPING

Diagnose

N/A — auth infrastructure.

Design

Creators can preview their quest as a learner would see it, verifying visual flow and branching.

Develop

Reviewers and admins can preview learner progress views and content rendering.

Deliver

N/A — infrastructure concern, but ensures admins can verify SCORM export behaves correctly for all roles.


3. USER FLOWS

Entry Point

Privileged user clicks the RoleSwitcher dropdown in their sidebar/dashboard header.

Success Criteria

Dashboard reloads showing the UI for the previewed role. A persistent indicator shows "Viewing as [Role]". User can reset to their actual role at any time.

Main Flow (Happy Path)

  1. Admin opens RoleSwitcher dropdown — sees available roles per VIEW_AS_HIERARCHY.

  2. Admin selects "Learner".

  3. Client calls POST /api/user/switch-role with { viewAsRole: "LEARNER" }.

  4. Server validates: Admin can view as Learner → sets wyzquests_view_as_role=LEARNER cookie (httpOnly, sameSite lax, maxAge 4h).

  5. Client reloads to /learner.

  6. Layout calls authenticateRole('LEARNER')getEffectiveRole() reads cookie, returns effectiveRole: "LEARNER".

  7. Dashboard renders as learner view.

Edge Cases

  • Invalid role in cookie: getEffectiveRole() logs warning, ignores cookie, uses actual role.

  • Privilege escalation attempt: Admin sets cookie to "ADMIN" while viewing as "LEARNER" → VIEW_AS_HIERARCHY["LEARNER"] is [] → cookie ignored.

  • Agency owner: AGENCY: [] — agency owners cannot view as any lower roles.

  • Reset: Client calls POST /api/user/switch-role with { viewAsRole: null } → cookie deleted.

  • Cookie expiration: After 4 hours, user automatically reverts to actual role.

Decision Points

  • IF user's actual role is in VIEW_AS_HIERARCHY and requested role is in the allowed list → set cookie.

  • ELSE → return 403 ApiResponseHelper.forbidden().


4. INFORMATION ARCHITECTURE

Primary Information (Always visible)

  • Current effective role (shown in RoleSwitcher badge).

  • "Viewing as [Role]" indicator when in preview mode.

Secondary Information

  • Available roles list (computed from VIEW_AS_HIERARCHY[actualRole]).

Actions

Primary CTA: Select role from dropdown → switch view.
Secondary Actions: Reset to actual role ("Exit View As" button).


5. WIREFRAMES

[Excluded — existing UI: components/ui/RoleSwitcher.tsx (290 lines)]

6. WIREFLOWS

Excluded.

7. PROTOTYPE

[Excluded — existing implementation]


8. BACKEND SCHEMA

Database Tables

No dedicated table. Cookie-based state only.

Cookie Configuration

Property

Value

Name

wyzquests_view_as_role

httpOnly

true

secure

true (production), false (dev)

sameSite

lax

maxAge

14400 (4 hours)

path

/


9. API ENDPOINTS

Method

Endpoint

Auth

File

Description

POST

/api/user/switch-role

Clerk session

app/api/user/switch-role/route.ts (145 lines)

Validates role against VIEW_AS_HIERARCHY, sets/deletes cookie, returns redirect URL

GET

/api/user/available-roles

Clerk session

app/api/user/available-roles/route.ts (81 lines)

Returns { actualRole, viewingAsRole, isViewingAsOther, canViewAs, hasAgencyMembership, isAgencyOwner }

Response shape (switch-role):

{ "success": true, "data": { "actualRole": "ADMIN", "viewingAsRole": "LEARNER", "isViewingAsOther": true, "redirectUrl": "/learner" } }

Response shape (available-roles):

{
"success": true,
"data": {
"actualRole": "ADMIN",
"viewingAsRole": null,
"isViewingAsOther": false,
"canViewAs": ["AGENCY", "CREATOR", "REVIEWER", "LEARNER"],
"hasAgencyMembership": false,
"isAgencyOwner": false
}
}

10. DATA REQUIREMENTS

Frontend Needs

  • VIEW_AS_HIERARCHY (imported from schema) for client-side role availability.

  • ROLE_DASHBOARD_PATHS for redirect URLs.

  • RoleSwitcher component state: current effective role, available roles, loading state.

API Calls Frontend Will Make

  • GET /api/user/available-roles on RoleSwitcher mount.

  • POST /api/user/switch-role on role selection.

Caching Strategy

Cookie is read on every request via getEffectiveRole(). No additional caching needed — cookie is the source of truth.


11. PERFORMANCE CONSIDERATIONSDatabase Optimization

N/A — cookie-based, no DB writes per switch.

API Response Time

  • switch-role: <50ms (sets cookie and returns).

  • available-roles: <100ms (reads user role + agency membership, no heavy queries).


12. SECURITY & AUTHORIZATION

Who can access this feature?

Any authenticated user with at least one role in VIEW_AS_HIERARCHY that has non-empty allowed roles.

Authorization Logic

  • VIEW_AS_HIERARCHY is strictly enforced server-side:

    • ADMIN["AGENCY","CREATOR","REVIEWER","LEARNER"]

    • AGENCY[] (cannot view as lower roles)

    • CREATOR["REVIEWER","LEARNER"]

    • REVIEWER["LEARNER"]

    • LEARNER[]

  • getEffectiveRole() validates cookie against hierarchy on every auth check.

  • Invalid/malicious cookie values are silently ignored (logged as security warning).

  • Cookie is httpOnly (inaccessible to JavaScript).

Data Validation

  • viewAsRoleSchema validates incoming role with platformRoleEnum.nullable().

  • Response schemas: viewAsRoleResponseSchema, availableRolesResponseSchema.


13. ERROR HANDLING

Error

Response

Invalid role requested (not in hierarchy)

403 ApiResponseHelper.forbidden("You are not authorized to view as this role")

Unauthenticated

401 AuthenticationError

Valid but harmless cookie manipulation

Silent — cookie ignored, actual role used


14. TESTING CHECKLIST

Happy Path
□ Admin switches to Learner → dashboard shows learner UI → indicator shows "Viewing as Learner".
□ Creator switches to Reviewer → reviewer dashboard renders correctly.
□ User resets to actual role → cookie deleted → dashboard reverts.
□ Cookie expires after 4 hours → user automatically returns to actual role.

Edge Cases
□ Admin viewing as Learner navigates to /admin → 403 (role mismatch).
□ Agency owner (AGENCY) attempts View As → dropdown shows no options.
□ Learner attempts View As → dropdown shows no options.
□ Malicious cookie value "SUPERADMIN" → ignored, actual role used.
□ Multiple tabs: switching role in one tab requires reload in others.


15. OPEN QUESTIONS

  • Should a user be allowed to remain in View As mode across sign-out/sign-in cycles?

  • Should the 4-hour cookie TTL be configurable?


16. OUT OF SCOPE (v1.1+)

  • "View As" for specific user (e.g., admin views a specific learner's dashboard).

  • Persistent View As mode across browser sessions.


17. SUCCESS METRICS

  • Zero privilege escalation incidents.

  • <100ms API response time for role switch.

  • 100% of layout guards validate against effective role (not actual role).


18. DEPENDENCIES

This feature depends on:

  • 0.1 Secure Login (Clerk session required).

  • lib/schemas/role-switch.schema.ts (90 lines): VIEW_AS_HIERARCHY, schemas.

  • lib/auth/authenticate.ts (885 lines): getEffectiveRole().

  • components/ui/RoleSwitcher.tsx (290 lines).

  • ROLE_DASHBOARD_PATHS for redirect URLs.

These features depend on this:

  • All layout auth guards indirectly (validate via effective role).

  • Admin/creator/reviewer testing workflows.


19. TIMELINE & OWNERSHIP

  • Implemented: PR #406.

  • Owner: scorevi (Sean Patrick Caintic)


Document Version

1.0 - Initial version - 2026-06-29 07:58 UTC

1.1 (Current Version) - Added Document Version section and update author to have full name - 2026-06-29 08:36 UTC


Was this article helpful?