Skip to content

fix: add client-side auth guard to tenant pages (defense-in-depth)#1977

Open
amikofalvy wants to merge 1 commit intomainfrom
prd-6033/client-auth-guard
Open

fix: add client-side auth guard to tenant pages (defense-in-depth)#1977
amikofalvy wants to merge 1 commit intomainfrom
prd-6033/client-auth-guard

Conversation

@amikofalvy
Copy link
Collaborator

Summary

  • Introduces a useRequireAuth() hook that monitors the client-side session and redirects to /login if the session is absent or expires
  • Wires the hook into all 8 client-component pages under /[tenantId] that previously had no auth protection: Work Apps (overview, Slack, GitHub, GitHub installation detail), Stats (overview, AI calls, tool calls), and Settings
  • Complements the server-side layout cookie check in PR fix: add server-side auth gate to tenant layout #1976 as a defense-in-depth layer

Why Both Layers?

Scenario Server-side layout check (PR #1976) Client-side useRequireAuth (this PR)
User navigates to page while logged out Blocks at server, redirects to /login N/A (page never renders)
User's session expires while on page Won't catch (only runs on initial render) Detects session loss, redirects to /login

Changes

New file: agents-manage-ui/src/hooks/use-require-auth.ts

  • Uses existing useAuthSession() hook to monitor session state
  • Uses existing buildLoginUrlWithCurrentPath() to preserve return URL
  • Redirects via router.replace() when session is absent after loading completes

Modified pages (added useRequireAuth() call):

  • [tenantId]/work-apps/page.tsx
  • [tenantId]/work-apps/slack/page.tsx
  • [tenantId]/work-apps/github/page.tsx
  • [tenantId]/work-apps/github/[installationId]/page.tsx
  • [tenantId]/stats/page.tsx
  • [tenantId]/stats/ai-calls/page.tsx
  • [tenantId]/stats/tool-calls/page.tsx
  • [tenantId]/settings/page.tsx

Test Plan

  • Visit /default/work-apps while logged out → should redirect to /login
  • Visit /default/stats while logged out → should redirect to /login
  • Visit /default/settings while logged out → should redirect to /login
  • Log in, navigate to Work Apps, then clear better-auth.session_token cookie manually → should redirect to /login
  • All pages render normally when logged in (no redirect flicker)

Resolves PRD-6033

Made with Cursor

Adds a `useRequireAuth` hook that monitors the client-side session
state and redirects to /login if the session becomes invalid. This
handles the case where a user's session expires while they are
already on a page (the server-side layout cookie check only runs
on initial render).

Applied to all client-component pages under /[tenantId] that
previously had no auth protection:
- Work Apps (overview, Slack, GitHub, GitHub installation detail)
- Stats (overview, AI calls, tool calls)
- Settings

Resolves PRD-6033

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel
Copy link

vercel bot commented Feb 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agents-api Ready Ready Preview, Comment Feb 13, 2026 2:25am
agents-manage-ui Ready Ready Preview, Comment Feb 13, 2026 2:25am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
agents-docs Skipped Skipped Feb 13, 2026 2:25am

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Feb 13, 2026

⚠️ No Changeset found

Latest commit: 836bb0f

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review Summary

(0) Total Issues | Risk: Low

🔴❗ Critical (0) ❗🔴

None

🟠⚠️ Major (0) 🟠⚠️

None

🟡 Minor (0) 🟡

None

💭 Consider (1) 💭

Inline Comments:

  • 💭 Consider: use-require-auth.ts:20-24 Align redirect condition with isAuthenticated instead of !user for consistency

✅ APPROVE

Summary: Clean, well-designed implementation of a defense-in-depth authentication guard. The hook correctly waits for loading to complete before checking auth status, uses router.replace() to avoid polluting browser history, and leverages existing utilities with proper open redirect protection. The security architecture is sound—server-side layout check blocks unauthenticated initial access, while this client-side hook handles the session expiry scenario during active page usage. Ship it! 🚀


Discarded (4)
Location Issue Reason Discarded
use-require-auth.ts:20 Flash of protected content before redirect Defense-in-depth design: server-side check blocks unauthenticated users on initial load; this hook handles session expiry. API calls would fail with 401 anyway and FullPageError handles that reactively. UX polish rather than security issue.
multi-file Other tenant pages don't use the hook Intentional partial rollout per PRD-6033 scope. PR explicitly targets Work Apps, Stats, and Settings. Server-side layout check protects all pages.
work-apps/page.tsx:9 Returned values from hook not used Intentional flexible design—hook can be used fire-and-forget or with return values. No correctness issue.
use-require-auth.ts:8-15 Existing FullPageError pattern for 401 handling Informational only. The JSDoc already documents the defense-in-depth purpose.
Reviewers (4)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
pr-review-standards 0 0 0 0 0 0 0
pr-review-security-iam 0 0 0 0 0 0 0
pr-review-frontend 3 0 0 0 1 0 2
pr-review-consistency 3 0 0 0 0 0 3
Total 6 0 0 0 1 0 5

Note: Frontend and Consistency reviewers flagged overlapping issues (isAuthenticated vs user) which were merged. Consistency's "incomplete application" finding was discarded as out of scope per PRD-6033.

Comment on lines +20 to +24
useEffect(() => {
if (!isLoading && !user) {
router.replace(buildLoginUrlWithCurrentPath());
}
}, [isLoading, user, router]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 Consider: Align redirect condition with return value

Issue: The redirect condition checks !user but the hook returns isAuthenticated (which is !!session). These could theoretically diverge if session exists but session.user is null.

Why: While unlikely in practice (Better Auth's contract should guarantee both), using consistent conditions makes the code clearer and avoids potential edge cases.

Fix: Either use !isAuthenticated for the redirect check, or document why !user is preferred:

Suggested change
useEffect(() => {
if (!isLoading && !user) {
router.replace(buildLoginUrlWithCurrentPath());
}
}, [isLoading, user, router]);
useEffect(() => {
if (!isLoading && !isAuthenticated) {
router.replace(buildLoginUrlWithCurrentPath());
}
}, [isLoading, isAuthenticated, router]);

Refs:

@github-actions github-actions bot deleted a comment from claude bot Feb 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant