Skip to content

Add structured error handling with AuthErrorCode#284

Draft
erquhart wants to merge 22 commits into
mainfrom
structured-error-handling
Draft

Add structured error handling with AuthErrorCode#284
erquhart wants to merge 22 commits into
mainfrom
structured-error-handling

Conversation

@erquhart

@erquhart erquhart commented Feb 19, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds AuthErrorCode vocabulary (12 structured error codes) and AuthError class for internal error propagation
  • Adds handleError callback in convexAuth({ callbacks: { ... } }) that controls what error info reaches the client
    • Return AuthErrorCode → included in response as { tokens: null, error: code }
    • Return void → silent { tokens: null }
    • Throw → error propagates (backwards compat via ConvexError, or plain Error stripped in production)
  • Default is legacyOnAuthError preserving exact backwards-compatible throw behavior
  • Provides defaultOnAuthError as the recommended handler: returns codes with user enumeration prevention (ACCOUNT_NOT_FOUND/ACCOUNT_DELETEDINVALID_CREDENTIALS)
  • Surfaces error field through React client signIn() return and Next.js proxy
  • Exports AuthErrorCode from @convex-dev/auth/react for client-side comparison

Test plan

  • All 52 existing tests pass (backwards compat confirmed)
  • structuredErrors.test.ts: unit tests for defaultOnAuthError return values, enumeration mapping, silent refresh failures
  • errorHandling.test.ts: integration tests for legacy throw behavior, OAuth redirect without error param
  • Build passes for all targets (server, react, nextjs)
  • Manual testing with test demo app

Issues

Closes #124 — structured error codes for password/credential flows
Closes #229 — preserve ConvexError through Next.js proxy

Related #54 — adds structured error return path; doesn't cover all auth error surfaces (e.g. OAuth redirect URI mismatches)
Related #65 — standard auth failures now return codes instead of throwing; custom errors thrown from authorize() still go through the old throw path
Related #165 — user-thrown ConvexError from createOrUpdateUser now preserved through Next.js proxy; non-Next.js passthrough unchanged

erquhart and others added 11 commits February 18, 2026 13:17
Cover all error paths with exact message/behavior assertions:
- Credentials: InvalidSecret, InvalidAccountId, TooManyFailedAttempts
- OTP: Could not verify code (wrong code, expired, rate limited)
- Refresh: silent tokens null (expired token, expired session)
- Code exchange: silent tokens null (invalid magic link code)
- Credentials authorize null: silent tokens null
- OAuth callback failure: 302 redirect without code param
- Password validation: Invalid password

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces the OAuth callback test with a realistic token exchange
failure flow, and adds tests for duplicate sign-up, missing password
params, and invalid provider name.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace silent null returns and generic throws with structured error
codes throughout the auth flow. Mutations return { error: AuthErrorCode }
instead of null, the signIn action propagates errors to clients via an
optional error field, and OAuth callback failures redirect with
?error=OAUTH_FAILED. Adds onError server callback for observability
and surfaces errors in React/Next.js clients.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…changes

Revert client-side error state (React, Next.js) and keep error handling
server-side only. handleEmailAndPhoneProvider now throws with structured
error codes instead of returning them. The signIn action wraps signInImpl
in try/catch: when onError is configured, auth errors are caught and
{tokens: null} is returned; without onError, errors re-throw (backwards
compatible). Return-based errors (refresh, code-exchange) call onError
observationally.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All auth errors now route through onError (defaulting to
legacyOnAuthError for backwards compat). The `thrown` flag lets
handlers distinguish errors that originally threw from those that
were silently returned. defaultOnAuthError throws ConvexError with
structured data for all errors except refresh failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… handling

Replaces the boolean `thrown` property in onError args with `legacyMessage:
string | null`. The call sites that invoke onError already know the original
legacy error string, so they set it directly. legacyOnAuthError simply throws
when non-null, resolving the RATE_LIMITED ambiguity without needing separate
error codes.

- Add AuthError class carrying both structured code and legacyMessage
- Replace extractAuthErrorCode with extractAuthError returning both fields
- handleEmailAndPhoneProvider throws AuthError with "Could not verify code"
- retrieveAccount throws AuthError with original credential strings
- legacyMessage is @deprecated from day one; falls off without API impact

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
defaultOnAuthError now throws plain Error (stripped in production) instead
of ConvexError. Developers must explicitly opt in to exposing structured
error data to clients via ConvexError in their own onError callback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests for AuthErrorCode vocabulary, legacyOnAuthError behavior
(throws on non-null legacyMessage, silent on null), and
defaultOnAuthError behavior (opaque Error throws, silent for
refresh/session, ignores legacyMessage).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move onError resolution from inside the signIn action to the top of
convexAuth() so it's shared by both the signIn action and OAuth callback.
The OAuth callback now always calls onError instead of conditionally
checking config.callbacks?.onError.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rename onError → handleAuthError (with deprecation warning for old name).
The callback now returns void | AuthErrorCode instead of only void:
returning a code includes it in the response as { tokens: null, error },
returning void gives a silent { tokens: null }, and throwing preserves
backwards-compatible behavior. defaultOnAuthError returns codes with
enumeration prevention (ACCOUNT_NOT_FOUND/ACCOUNT_DELETED → INVALID_CREDENTIALS).
Surface error field through React client signIn return and Next.js proxy.
Export AuthErrorCode from @convex-dev/auth/react.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…warning

The auth context is implicit from convexAuth() callbacks. onError was
never released so no deprecation warning is needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Feb 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch structured-error-handling

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.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented Feb 19, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
convex-auth-docs Ready Ready Preview Feb 22, 2026 9:29pm

Request Review

@pkg-pr-new

pkg-pr-new Bot commented Feb 19, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/get-convex/convex-auth/@convex-dev/auth@284

commit: 3705040

erquhart and others added 3 commits February 20, 2026 22:34
The test triggers a structured auth error (INVALID_CREDENTIALS) through
the Next.js proxy and asserts the client receives the clean error code.
This test is expected to fail — the proxy extracts (error as Error).message
from a ConvexError, which yields a JSON string instead of the structured code.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The proxy was extracting (error as Error).message from ConvexErrors,
which yields a JSON string instead of structured data. Now the proxy
detects ConvexError and serializes its .data with an isConvexError
flag, and the client reconstructs the ConvexError on the other side.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Create dedicated error-handling.mdx page documenting structured error
codes, the handleError callback, and OAuth error handling. Fix inaccuracy
in passwords.mdx (signIn returns an object, not a boolean). Add
cross-reference callouts to provider pages and sections in advanced.mdx
and debugging.mdx.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Vercel blocks deployments using next-mdx-remote 4.4.1. Add npm override
to use v6.0.0 while remaining on nextra 2.x.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add JSDoc descriptions to all AuthErrorCode members
- Add @param tags to legacyOnAuthError and defaultOnAuthError
- Remove duplicate AuthErrorCode section (type alias) via post-processing
- Fix AuthErrorCode anchor references after duplicate removal

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add prerequisite blockquote and user-perspective intro to error-handling page
- Wrap setup flow in <Steps> component to match config page patterns
- Normalize callout wording across config pages (passwords, email, otps, oauth)
- Split long compound sentence in passwords.mdx verification section
- Use active voice in debugging.mdx error codes section
- Tighten API reference JSDoc: standalone import examples, compact descriptions
- Redirect non-basePath URLs to /auth/* in dev for convenience

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
erquhart and others added 3 commits February 21, 2026 21:35
Covers both handleError paths: throwing ConvexError (existing) and
returning an error code via result.error (new).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a second convexAuth() instance configured with defaultOnAuthError
and tests that the signIn pipeline correctly returns error codes instead
of throwing: wrong password returns INVALID_CREDENTIALS, non-existent
account maps ACCOUNT_NOT_FOUND to INVALID_CREDENTIALS, and successful
sign-in has no error field.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

Next.js SSA doesn't forward ConvexError Cleanly handling expected errors with Password provider

1 participant