Skip to content

Add useCliAuthConfirmation hook and customizable cliAuthConfirm URL target#1388

Merged
mantrakp04 merged 2 commits into
devfrom
cli-auth-confirm-hook
Apr 28, 2026
Merged

Add useCliAuthConfirmation hook and customizable cliAuthConfirm URL target#1388
mantrakp04 merged 2 commits into
devfrom
cli-auth-confirm-hook

Conversation

@mantrakp04
Copy link
Copy Markdown
Collaborator

@mantrakp04 mantrakp04 commented Apr 27, 2026

Summary

  • Extract CLI auth confirmation into a useCliAuthConfirmation() hook (status / error / isLoading / authorize / retry) so custom pages don't have to reimplement the protocol; CliAuthConfirmation now consumes the hook.
  • Make cliAuthConfirm a first-class handler URL target — resolved via resolveHandlerUrls, customizable per project, and used by promptCliLogin through a new buildCliAuthConfirmUrl() helper.
  • Move StackContext to its own module so the hook can be unit-tested with a test double without tripping the client-version sentinel; register cliAuthConfirm in custom-page prompts and the dev-tool components tab; export the hook + types from @stackframe/stack.

Test plan

  • pnpm typecheck
  • pnpm lint
  • pnpm --filter @stackframe/stack test cli-auth-confirm url-targets
  • Manually verify default /handler/cli-auth-confirm flow + a project with a custom cliAuthConfirm URL

Summary by CodeRabbit

  • New Features

    • Adds a CLI authentication confirmation page with clear states (invalid, authorizing, redirecting, success, error), retry action, and flows for signed-in and anonymous users.
    • CLI login URL generation now derives from the configured handler target and app base, improving reliability.
    • CLI confirmation page exposed in the components/dev UI for previewing.
  • Tests

    • End-to-end and unit tests covering confirmation behaviors and URL generation.

…arget

- Extract CLI auth confirmation logic into useCliAuthConfirmation() so users
  building custom pages can drive the flow via status/error/isLoading/
  authorize()/retry() instead of reimplementing the protocol.
- Treat cliAuthConfirm as a normal handler URL target: resolve it via
  resolveHandlerUrls, allow custom URLs, and build the CLI login URL with a
  new buildCliAuthConfirmUrl() helper so promptCliLogin honors the resolved
  target.
- Move StackContext to its own module so the hook can be unit-tested with a
  test double without dragging in the full client-app implementation (which
  trips the compile-time client-version sentinel).
- Register cliAuthConfirm in custom-page prompts and the dev tool components
  tab, and export the new hook + types from the template entry point.
Copilot AI review requested due to automatic review settings April 27, 2026 19:38
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 27, 2026

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

Project Deployment Actions Updated (UTC)
stack-auth-hosted-components Ready Ready Preview, Comment Apr 27, 2026 8:09pm
stack-backend Ready Ready Preview, Comment Apr 27, 2026 8:09pm
stack-dashboard Ready Ready Preview, Comment Apr 27, 2026 8:09pm
stack-demo Ready Ready Preview, Comment Apr 27, 2026 8:09pm
stack-docs Ready Ready Preview, Comment Apr 27, 2026 8:09pm
stack-preview-backend Ready Ready Preview, Comment Apr 27, 2026 8:09pm
stack-preview-dashboard Ready Ready Preview, Comment Apr 27, 2026 8:09pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

📝 Walkthrough

Walkthrough

This PR adds a CLI auth confirmation flow: a new cliAuthConfirm handler and SDK prompt, a useCliAuthConfirmation hook and component updates, URL-building utilities and resolver entries, client-app redirect API, tests, context/provider adjustments, and related exports and docs additions.

Changes

Cohort / File(s) Summary
Handler types & prompts
packages/stack-shared/src/interface/handler-urls.ts, packages/stack-shared/src/interface/page-component-versions.ts
Added cliAuthConfirm to handler URL types and introduced an SDK-managed custom page prompt describing UI states and example implementation.
CLI auth component & hook
packages/template/src/components-page/cli-auth-confirm.tsx, packages/template/src/components-page/cli-auth-confirm.test.tsx
Reworked component to use exported useCliAuthConfirmation() state machine (`idle
URL resolution & client-app API
packages/template/src/lib/stack-app/url-targets.ts, packages/template/src/lib/stack-app/url-targets.test.ts, packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts, sdks/spec/src/apps/client-app.spec.md
Resolve cliAuthConfirm to "cli-auth-confirm" path, add cliAuthConfirm URL in resolved URLs, export buildCliAuthConfirmUrl() to compose final URL with login_code, and add redirectToCliAuthConfirm() to client app; tests/specs updated.
Routing integration & dev tooling
packages/template/src/components-page/stack-handler-client.tsx, packages/template/src/dev-tool/dev-tool-core.ts, packages/template/src/index.ts
Invoke redirectIfNotHandler?.('cliAuthConfirm') for the handler case, add cliAuthConfirm to dev-tool pages, and export useCliAuthConfirmation plus related types from package index.
Context & hooks wiring
packages/template/src/providers/stack-context.tsx, packages/template/src/providers/stack-provider-client.tsx, packages/template/src/lib/hooks.tsx
Extracted StackContext to new module; updated provider to import it; changed hook imports to use the new context and mark some imports as type-only.
Docs / Knowledge base
claude/CLAUDE-KNOWLEDGE.md
Added FAQ-style guidance: recommended custom-page integration pattern (cliAuthConfirm + useCliAuthConfirmation) and test guidance to unit-test URL builder instead of importing concrete template client.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant Browser as Browser/Component
    participant Hook as useCliAuthConfirmation<br/>(Hook)
    participant ClientApp as ClientApp
    participant API as Stack API
    participant Auth as Auth System

    User->>Browser: Open cliAuthConfirm URL (with/without login_code)
    Browser->>Hook: initialize
    Hook->>Hook: parse query (login_code?)
    alt no login_code
        Hook->>Browser: status = "invalid"
        Browser->>User: show invalid message
    else login_code present
        Hook->>Hook: check current user session
        alt user signed in
            Hook->>API: POST /auth/cli/complete (login_code + refresh_token)
            API-->>Hook: success
            Hook->>Browser: status = "success"
            Browser->>User: show close-browser message
        else anonymous
            Hook->>API: GET /auth/cli/session/check (login_code)
            API-->>Hook: cli_session_state / tokens?
            Hook->>API: POST /auth/cli/session/claim-anon (login_code)
            API-->>Hook: access_token + refresh_token
            Hook->>Auth: signInWithTokens(access, refresh)
            Auth-->>Hook: signed in
            Hook->>ClientApp: redirectToSignUp({ replace: true }) (set confirmed=true)
            ClientApp->>Browser: navigate to sign-up
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • BilalG1
  • N2D4

Poem

🐰 I hopped in code to stitch a little thread,
A cliAuthConfirm path where tokens are led,
Hooks hum the tune, URLs guide the race,
Close the browser, friend — the CLI found its place! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main changes: extracting a hook and making cliAuthConfirm customizable, directly matching the core objectives.
Description check ✅ Passed The description is comprehensive, covering the summary, implementation details, and a detailed test plan with specific commands and manual verification steps.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cli-auth-confirm-hook

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 27, 2026

Greptile Summary

This PR extracts the CLI auth confirmation flow into a reusable useCliAuthConfirmation() hook and promotes cliAuthConfirm to a first-class handler URL target, making it customizable per project. The implementation is clean: the ref-based double-click guard (authorizeInProgressRef) addresses the previously flagged race condition, StackContext is correctly moved to its own module to avoid the client-version sentinel in tests, and buildCliAuthConfirmUrl handles both relative and absolute confirmation targets correctly via standard URL resolution. Test coverage is comprehensive, exercising the happy path, duplicate clicks, anonymous session claim, and the missing-login-code case.

Confidence Score: 5/5

Safe to merge — no functional bugs found; the double-click race condition from the previous review has been properly addressed with a ref guard.

All findings are P2 (minor API surface concern). The core logic — state machine transitions, ref-based de-duplication, StackContext extraction, and URL resolution — is correct. Tests cover the critical paths.

No files require special attention.

Important Files Changed

Filename Overview
packages/template/src/components-page/cli-auth-confirm.tsx Refactors inline component logic into useCliAuthConfirmation() hook with a well-structured state machine; ref-based guard (authorizeInProgressRef) correctly prevents double-invocation; loginCode is exposed in the public CliAuthConfirmationState type but unused by the component itself
packages/template/src/lib/stack-app/url-targets.ts Adds cliAuthConfirm to resolveHandlerUrls and introduces buildCliAuthConfirmUrl; relative vs absolute URL handling via new URL(relative, base) is correct JavaScript behavior; JSDoc comment already notes that appUrl is only used for relative targets
packages/template/src/providers/stack-context.tsx New file extracting StackContext into its own module; avoids tripping the client-version sentinel when the context is imported in tests
packages/template/src/components-page/cli-auth-confirm.test.tsx Comprehensive tests covering happy path, duplicate-click de-duplication, anonymous session claim, and invalid (missing login code) scenarios using a lightweight StackClientApp test double
packages/stack-shared/src/interface/page-component-versions.ts Adds cliAuthConfirm custom page prompt with detailed instructions and a complete reactExample; correctly documents hook API for AI-generated custom pages
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts Adds redirectToCliAuthConfirm and switches promptCliLogin to use buildCliAuthConfirmUrl instead of the hardcoded /handler/cli-auth-confirm path

Sequence Diagram

sequenceDiagram
    participant CLI as CLI Tool
    participant Browser
    participant Hook as useCliAuthConfirmation
    participant API as /auth/cli/complete

    CLI->>API: POST (init) → polling_code, login_code
    CLI->>Browser: Open buildCliAuthConfirmUrl(cliAuthConfirmUrl, appUrl, login_code)
    Browser->>Hook: mount — reads login_code from URL

    alt User not signed in
        Browser->>Hook: user clicks Authorize
        Hook->>API: POST {login_code, mode:"check"}
        API-->>Hook: {cli_session_state}
        alt anonymous session
            Hook->>API: POST {login_code, mode:"claim-anon-session"}
            API-->>Hook: {access_token, refresh_token}
            Hook->>Browser: signInWithTokens + markUrlConfirmed
            Hook->>Browser: redirectToSignUp (status→redirecting)
            Browser->>Hook: remount — confirmed=true, user present
            Hook->>Hook: autoCompleteRef guard fires
        else no session
            Hook->>Browser: markUrlConfirmed + redirectToSignIn (status→redirecting)
            Browser->>Hook: remount — confirmed=true, user present
        end
    end

    Hook->>Hook: auto-complete effect runs (confirmed & user)
    Hook->>API: POST {login_code, refresh_token}
    API-->>Hook: 200 OK
    Hook->>Browser: status → success
    CLI->>API: poll → session completed
Loading

Fix All in Claude Code Fix All in Cursor Fix All in Codex

Prompt To Fix All With AI
This is a comment left during a code review.
Path: packages/template/src/components-page/cli-auth-confirm.tsx
Line: 60-67

Comment:
**`loginCode` in public state type is unnecessary API surface**

`loginCode: string | null` is exposed on `CliAuthConfirmationState` but is never consumed by `CliAuthConfirmation` and is not needed by any of the custom-page prompt examples in `page-component-versions.ts`. The hook already handles all protocol details internally (`authorize()`, `retry()`, etc.), so exposing the raw code risks encouraging custom pages to re-implement protocol steps directly.

Consider dropping `loginCode` from the return type; it can always be added back later if a concrete use case arises.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (2): Last reviewed commit: "Enhance CLI auth confirmation handling a..." | Re-trigger Greptile

Comment thread packages/template/src/components-page/cli-auth-confirm.tsx Outdated
Comment thread packages/template/src/lib/stack-app/url-targets.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves the CLI auth UX/customizability in the template SDK by extracting the browser-side confirmation protocol into a reusable hook and by making the cliAuthConfirm page a first-class configurable handler URL target.

Changes:

  • Added useCliAuthConfirmation() (with exported types) and refactored CliAuthConfirmation to consume it.
  • Registered cliAuthConfirm as a configurable handler URL target and updated promptCliLogin() to build its login URL from the resolved target via buildCliAuthConfirmUrl().
  • Extracted StackContext into its own module to support unit testing hooks/components with a simple test double.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
sdks/spec/src/apps/client-app.spec.md Updates the CLI login URL construction spec to use resolved urls.cliAuthConfirm.
packages/template/src/providers/stack-provider-client.tsx Switches to importing StackContext from the new dedicated module.
packages/template/src/providers/stack-context.tsx New client module exporting StackContext for provider + tests.
packages/template/src/lib/stack-app/url-targets.ts Adds cliAuthConfirm handler resolution and buildCliAuthConfirmUrl() helper.
packages/template/src/lib/stack-app/url-targets.test.ts Adds coverage for cliAuthConfirm resolution + URL building helper.
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts Updates promptCliLogin() to use buildCliAuthConfirmUrl(); adds redirectToCliAuthConfirm().
packages/template/src/lib/hooks.tsx Updates StackContext import location and makes stack-app imports type-only.
packages/template/src/index.ts Exports useCliAuthConfirmation and related types.
packages/template/src/dev-tool/dev-tool-core.ts Adds cliAuthConfirm to the dev-tool components tab.
packages/template/src/components-page/stack-handler-client.tsx Ensures handler routing treats cliAuthConfirm as a handler route.
packages/template/src/components-page/cli-auth-confirm.tsx Introduces the hook + refactors component to use it; improves response parsing.
packages/template/src/components-page/cli-auth-confirm.test.tsx Adds unit tests for the new hook behavior.
packages/stack-shared/src/interface/page-component-versions.ts Registers cliAuthConfirm custom-page prompt and example using the new hook.
packages/stack-shared/src/interface/handler-urls.ts Adds cliAuthConfirm to handler URL target types.
claude/CLAUDE-KNOWLEDGE.md Documents the intended DX and testing approach for the new flow.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/template/src/components-page/cli-auth-confirm.test.tsx
- Introduced a mechanism to ignore duplicate authorization clicks before React re-renders, ensuring that only one request is sent when the authorize button is clicked multiple times.
- Added a new test case to validate this behavior, confirming that the request is sent only once despite multiple clicks.
- Updated the `useCliAuthConfirmation` hook to manage the authorization state more effectively, preventing re-authorization while a request is in progress.
- Improved error handling for missing login codes in the authorization process.
- Documented the app URL usage in the `buildCliAuthConfirmUrl` function for clarity on relative URL handling.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
packages/template/src/components-page/cli-auth-confirm.tsx (2)

41-50: Optional: drop the as keyof typeof data cast.

After narrowing to object and verifying fieldName in data, a single Record<string, unknown> cast reads cleaner than the keyof typeof data form (which is effectively a no-op cast since fieldName isn't statically known). Functionally identical, just more honest about what's happening.

♻️ Optional simplification
 function getObjectField(data: unknown, fieldName: string): unknown {
-  return typeof data === "object" && data !== null && fieldName in data
-    ? data[fieldName as keyof typeof data]
-    : undefined;
+  if (typeof data !== "object" || data === null) return undefined;
+  if (!(fieldName in data)) return undefined;
+  return (data as Record<string, unknown>)[fieldName];
 }

As per coding guidelines: "Do NOT use as/any/type casts or anything else like that to bypass the type system." — strictly speaking neither form fully avoids a cast here, so feel free to ignore if the current form is preferred.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/template/src/components-page/cli-auth-confirm.tsx` around lines 41 -
50, The code uses a verbose cast `fieldName as keyof typeof data` inside
getObjectField; replace that with an index on a clearer shape by casting data to
Record<string, unknown> after the runtime checks — e.g. use (data as
Record<string, unknown>)[fieldName] — and keep getStringField unchanged except
it should call the updated getObjectField; this removes the `keyof typeof data`
cast and makes the intent clearer while preserving the runtime `fieldName in
data` guard.

86-95: Prefer explicit == null checks over truthiness for these typed values.

Across the hook, several guards use truthiness on values whose types are T | null / string | undefined (!loginCode, !user, !refreshToken, !accessToken). Per the coding guideline, unless clearly equivalent from types, prefer explicit nullishness checks — the difference matters for, e.g., empty-string tokens that would currently be treated the same as missing tokens with a generic "did not return tokens" error rather than something more diagnostic.

♻️ Suggested tightening
   const completeWithCurrentUser = useCallback(async () => {
-    if (!loginCode) {
+    if (loginCode == null) {
       throw new Error("Missing login code in URL parameters");
     }
-    if (!user) {
+    if (user == null) {
       throw new Error("Cannot complete CLI authorization without a signed-in user");
     }
     const refreshToken = (await user.currentSession.getTokens()).refreshToken;
-    if (!refreshToken) {
+    if (refreshToken == null) {
       throw new Error("Could not retrieve session token");
     }
     await completeCliAuthWithRefreshToken(app, loginCode, refreshToken);
   }, [app, loginCode, user]);
@@
-    if (!confirmed || !user || autoCompleteRef.current) {
+    if (!confirmed || user == null || autoCompleteRef.current) {
       return;
     }
@@
-        if (!accessToken || !refreshToken) {
+        if (accessToken == null || refreshToken == null) {
           throw new Error("Anonymous CLI session claim did not return tokens");
         }

As per coding guidelines: "Unless very clearly equivalent from types, prefer explicit null/undefinedness checks over boolean checks, eg. foo == null instead of !foo".

Also applies to: 100-100, 154-156

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/template/src/components-page/cli-auth-confirm.tsx` around lines 86 -
95, Replace truthiness guards with explicit nullish checks: change conditions
like `if (!loginCode)`, `if (!user)`, `if (!refreshToken)`, and `if
(!accessToken)` to use `== null` (e.g., `if (loginCode == null)`) so you only
treat null/undefined as missing and allow empty strings to be distinguished;
update the error checks in the same block where `loginCode`, `user`,
`refreshToken`, and `accessToken` are validated (the guards around retrieving
`user.currentSession.getTokens()` and subsequent token usage) to use these `==
null` checks and keep the existing error messages.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/template/src/components-page/cli-auth-confirm.tsx`:
- Around line 41-50: The code uses a verbose cast `fieldName as keyof typeof
data` inside getObjectField; replace that with an index on a clearer shape by
casting data to Record<string, unknown> after the runtime checks — e.g. use
(data as Record<string, unknown>)[fieldName] — and keep getStringField unchanged
except it should call the updated getObjectField; this removes the `keyof typeof
data` cast and makes the intent clearer while preserving the runtime `fieldName
in data` guard.
- Around line 86-95: Replace truthiness guards with explicit nullish checks:
change conditions like `if (!loginCode)`, `if (!user)`, `if (!refreshToken)`,
and `if (!accessToken)` to use `== null` (e.g., `if (loginCode == null)`) so you
only treat null/undefined as missing and allow empty strings to be
distinguished; update the error checks in the same block where `loginCode`,
`user`, `refreshToken`, and `accessToken` are validated (the guards around
retrieving `user.currentSession.getTokens()` and subsequent token usage) to use
these `== null` checks and keep the existing error messages.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8259deea-f99f-4818-8435-0fabdb67224d

📥 Commits

Reviewing files that changed from the base of the PR and between a362f4d and c9f2a0f.

📒 Files selected for processing (3)
  • packages/template/src/components-page/cli-auth-confirm.test.tsx
  • packages/template/src/components-page/cli-auth-confirm.tsx
  • packages/template/src/lib/stack-app/url-targets.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/template/src/components-page/cli-auth-confirm.test.tsx

@mantrakp04 mantrakp04 requested review from BilalG1 and N2D4 April 27, 2026 21:51
@mantrakp04 mantrakp04 merged commit 9d1eee8 into dev Apr 28, 2026
38 checks passed
@mantrakp04 mantrakp04 deleted the cli-auth-confirm-hook branch April 28, 2026 22:29
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.

3 participants