6.6 KiB
Auth Access Control Implementation Plan
For agentic workers: Execute tasks in order and keep scope fixed to the approved spec. Use checkbox (
- [ ]) tracking and verify each task before moving on.
Goal: Implement login-first access control for both pages and APIs, with code-based allowlists, safe login redirect handling, and dual-mode homepage rendering support.
Architecture: Introduce centralized route rule utilities, a global page guard (auth.global), a server API guard middleware (10.auth-guard), and a unified auth session composable. Default policy is deny-by-default for pages and /api/**, with narrow allowlists.
Tech Stack: Nuxt 4/Nitro middleware, existing session-cookie auth service, TypeScript utilities, Bun dev/build workflow.
File Structure Map
- Create:
app/utils/auth-routes.ts - Create:
app/composables/useAuthSession.ts - Create:
app/middleware/auth.global.ts - Create:
server/utils/auth-api-routes.ts - Create:
server/middleware/10.auth-guard.ts - Modify:
app/pages/login/index.vue(handle redirect after login success) - Modify:
app/pages/index/index.vue(guest/authenticated split rendering) - Optional small adjustments: shared HTTP wrapper for uniform 401 handling if needed
Task 1: Build shared page-route auth rules and redirect sanitizer
Files:
-
Create:
app/utils/auth-routes.ts -
Test: utility behavior via unit-like local checks (or temporary assertions)
-
Step 1: Define route policy constants
Include:
-
public routes exact set (initial:
/,/login,/register) -
guest-only routes exact set (initial:
/login,/register) -
default authenticated landing path
-
Step 2: Implement safe
redirectparser
Implement helper like normalizeSafeRedirect(input, fallback):
-
allow only same-site relative paths
-
must start with
/ -
reject
//, protocol-like payloads (http:,https:,javascript:), empty strings -
return fallback when invalid
-
Step 3: Implement route match helpers
Provide concise helpers:
-
isPublicRoute(path) -
isGuestOnlyRoute(path) -
matching mode: exact + explicit prefix helper only (no broad regex)
-
Step 4: Verify sanitizer edge cases
Validate examples:
- accept:
/dashboard,/a?b=1 - reject:
http://evil.com,//evil.com,javascript:alert(1)
Task 2: Add unified auth session composable
Files:
-
Create:
app/composables/useAuthSession.ts -
Test: manual behavior from multiple pages without duplicated calls
-
Step 1: Implement canonical session state
Expose:
loggedInuserpendingrefresh()clear()
Use /api/auth/me as source of truth.
- Step 2: Handle unauthorized consistently
When /api/auth/me returns 401:
-
clear local state
-
return
loggedIn=falseinstead of throwing noisy UI errors -
Step 3: Ensure composable is reusable
Guarantee all pages/middleware consume this composable, not ad-hoc login checks.
Task 3: Implement global page guard middleware (default deny)
Files:
-
Create:
app/middleware/auth.global.ts -
Reuse:
app/utils/auth-routes.ts,app/composables/useAuthSession.ts -
Step 1: Enforce default login-required policy
For every route:
-
if route is not public and user not logged in, navigate to
/login?redirect=<fullPath> -
Step 2: Enforce guest-only behavior
If logged-in user visits guest-only routes:
-
resolve safe redirect target
-
prioritize validated
redirectquery -
fallback to default authenticated landing path
-
Step 3: Prevent redirect loops
Guarantee no self-loop on /login and /register.
- Step 4: Manual route-flow verification
Check:
- unauth -> protected page -> login redirect with query
- logged-in ->
/login-> redirected away - public routes remain reachable when unauthenticated
Task 4: Implement server API auth guard middleware (default deny for /api/**)
Files:
-
Create:
server/utils/auth-api-routes.ts -
Create:
server/middleware/10.auth-guard.ts -
Step 1: Define API allowlist rules
Initial allowlist:
/api/auth/login/api/auth/register- required public-read APIs (e.g.
GET /api/config/global)
Use exact + explicit prefix helpers only.
- Step 2: Guard
/api/**requests
Middleware logic:
-
non-API request -> skip
-
allowlisted API -> skip
-
others -> require valid session from
event.context.auth.getCurrent() -
Step 3: Return unified unauthorized response
On unauthorized:
-
return HTTP 401
-
use generic status message
-
do not leak auth internals
-
Step 4: Validate API behavior
Check with/without cookie:
- protected APIs return 401 when unauthenticated
- allowlisted APIs still work
Task 5: Wire login/home pages to new access-control behavior
Files:
-
Modify:
app/pages/login/index.vue -
Modify:
app/pages/index/index.vue -
Step 1: Login page post-success redirect
After successful login:
-
call
useAuthSession().refresh() -
read
redirectquery and sanitize -
navigate to safe target/fallback
-
Step 2: Homepage dual-mode rendering
On /:
-
unauthenticated: show guest-facing UI
-
authenticated: show signed-in UI
-
rely on
useAuthSessionstate -
Step 3: Session-loss UX consistency
If session expires during interaction:
- state clears
- protected route access re-routes to login
Task 6: Verification matrix and polish
Files:
-
Test-focused task; no intended new feature files
-
Step 1: Route verification matrix
Run through:
-
unauth + public route
-
unauth + protected route
-
auth + guest-only route
-
auth + protected route
-
Step 2: Redirect security matrix
Try login URL examples:
-
/login?redirect=/safe/path(accepted) -
/login?redirect=http://evil.com(rejected) -
/login?redirect=//evil.com(rejected) -
Step 3: API verification matrix
Confirm:
-
unauth -> protected
/api/**=> 401 -
allowlisted APIs accessible unauthenticated
-
expired session behaves as unauthorized
-
Step 4: Build/lint sanity
Run:
bun run build- lint/type checks used by the repo
Fix only issues introduced by this scope.
Delivery Constraints
- Keep allowlists in code (no remote config source for auth policy)
- No RBAC/role granularity in this iteration
- No OAuth/SSO/password reset in this iteration
- Avoid broad route regexes that can accidentally expand exposure
Done Criteria
All below must be true:
- Page policy is deny-by-default with explicit public routes
- API policy is deny-by-default under
/api/**with explicit allowlist - Redirect handling is safe against open-redirect payloads
- Login and homepage behavior matches agreed UX
- Verification matrix passes without known regressions