Skip to content

feat(auth): add S256 PKCE to device authorization flow - #3

Merged
Zingzy merged 1 commit into
mainfrom
feat/device-auth-pkce
Aug 16, 2026
Merged

feat(auth): add S256 PKCE to device authorization flow#3
Zingzy merged 1 commit into
mainfrom
feat/device-auth-pkce

Conversation

@Zingzy

@Zingzy Zingzy commented Jul 18, 2026

Copy link
Copy Markdown
Member

What changed

The "Sign in with Spoo" device flow had drifted from the current backend contract (live in spoo-me/spoo#260) and from how the CLI and Raycast clients do it. This brings it back in line.

Device login now sends PKCE

GET /auth/device/login requires code_challenge (43-char base64url) and code_challenge_method=S256. Before, the extension only sent app_id and state, so login would now fail with a 400. The flow now generates a 32-byte random verifier as unpadded base64url, derives the S256 challenge with Web Crypto, stores the verifier, and appends both challenge params to the login URL.

Token exchange now sends the verifier

POST /auth/device/token expects { code, code_verifier }. Before, the extension sent only { code }, which now returns a 422. The background exchange reads the stored verifier, sends it, and clears it on success.

Refresh cleanup is now scoped to revocation

Refresh previously cleared the session on any failed response, so a transient server or network blip logged the user out. It now clears the session only on a 401 (revoked grant or expired refresh token) and keeps the tokens on transient failures so a later attempt can retry. This matches how the Raycast client removes tokens on a 401 during refresh.

Dropped connected-app API key creation

Key creation is first-party (dashboard) only now, so creating a key over a connected-app token returns a 403. The Account tab no longer offers in-extension key creation and links to the dashboard instead. Listing and revoking keys still work. This mirrors the CLI, which kept list and revoke and points to the dashboard for creation.

Not changed / no drift found

  • The device flow does not request or negotiate scopes (scopes come from the app registry), so nothing to remove there.
  • Sign out clears local tokens only and does not call a server revoke endpoint, so there was no revoke call shape to fix.
  • The extension receives the one-time code through a content script on the callback page rather than a redirect, so it does not use a redirect_uri param.

Backend follow-up (not in this PR)

The spoo-snap entry in the backend app registry (config/apps.yaml) needs to exist and be active with the scopes this client should get. No redirect_uri allowlisting is needed because the extension does not use a redirect.

Verification

  • tsc --noEmit clean
  • biome check . clean (only pre-existing config-version infos)
  • wxt build and wxt build -b firefox both succeed
  • Verifier and challenge validated against the backend regexes (43-char verifier in the RFC 7636 charset, challenge matching ^[A-Za-z0-9_-]{43}$)

Summary by CodeRabbit

  • New Features

    • Added stronger security for device authentication with PKCE support.
    • Added improved session cleanup when authentication tokens are rejected.
    • Preserved stored sessions during temporary restoration failures for retry.
  • Changes

    • API keys can now be created from the dashboard; the app continues to support listing and revoking keys.
    • Improved handling of cancelled or failed sign-in flows.

Copilot AI review requested due to automatic review settings July 18, 2026 09:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: df5c876c-ee00-4218-9fc0-72b4bfe7f2ad

📥 Commits

Reviewing files that changed from the base of the PR and between 36168ed and c759543.

📒 Files selected for processing (12)
  • src/api/auth.ts
  • src/api/client.ts
  • src/api/keys.ts
  • src/api/types.ts
  • src/components/auth/AuthSection.tsx
  • src/components/sidepanel/AccountTab.tsx
  • src/entrypoints/background/index.ts
  • src/hooks/use-keys.ts
  • src/lib/pkce.ts
  • src/lib/storage.ts
  • src/schemas/api.ts
  • src/stores/auth.ts
💤 Files with no reviewable changes (2)
  • src/schemas/api.ts
  • src/api/types.ts

Included review availability: Your plan includes up to 1 review per rolling hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The PR adds PKCE to device authentication, centralizes JWT session cleanup for unauthorized refreshes, preserves sessions during transient failures, and removes API key creation from the extension in favor of dashboard links.

Changes

Authentication and API Key Updates

Layer / File(s) Summary
JWT session refresh handling
src/api/auth.ts, src/api/client.ts, src/lib/storage.ts, src/stores/auth.ts
JWT sessions are cleared for HTTP 401 refresh responses. Other refresh failures preserve stored credentials.
PKCE device authentication
src/lib/pkce.ts, src/lib/storage.ts, src/components/auth/AuthSection.tsx, src/entrypoints/background/index.ts
The login flow generates, stores, sends, and clears a PKCE verifier and challenge.
Dashboard-only API key creation
src/api/types.ts, src/schemas/api.ts, src/api/keys.ts, src/hooks/use-keys.ts, src/components/sidepanel/AccountTab.tsx
API key creation types, schemas, client calls, hooks, and forms are removed. The account view links to dashboard key creation.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Merge Risk: ⚪ Minimal · up to c7595

The PR updates device authentication to use PKCE, preserves sessions across transient refresh failures, and removes unsupported in-extension key creation. No actionable merge-blocking risk remains at the current head after normal checks and review.

Possibly related PRs

  • spoo-me/spoo-snap#2: Introduced the authentication, API-key, and device-auth code refined by this PR.

Sequence Diagram(s)

sequenceDiagram
  participant AuthSection
  participant LocalStorage
  participant AuthServer
  participant Background
  AuthSection->>LocalStorage: Store PKCE verifier
  AuthSection->>AuthServer: Open device-login URL with challenge
  AuthServer-->>Background: Return device code
  Background->>LocalStorage: Read verifier
  Background->>AuthServer: Exchange device code with verifier
  Background->>LocalStorage: Store tokens and clear verifier
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: adding S256 PKCE support to the device authorization flow.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/device-auth-pkce

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.

Device login now sends code_challenge + code_challenge_method=S256 and the
token exchange sends the matching code_verifier, matching the current backend
contract. Refresh now clears the session only on a 401 (revoked or expired
grant) and keeps it on transient failures. Drops the connected-app API key
creation flow, which is first-party only now, in favour of a dashboard link.
@Zingzy
Zingzy force-pushed the feat/device-auth-pkce branch from f999986 to c759543 Compare August 16, 2026 12:12
@Zingzy
Zingzy merged commit aa434a5 into main Aug 16, 2026
1 of 2 checks passed
@Zingzy
Zingzy deleted the feat/device-auth-pkce branch August 16, 2026 12:38
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.

2 participants