Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions src/server/cookies.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
import { isLocalHost } from "./utils.js";

// These options apply to every cookie Convex Auth sets during OAuth flows
// (PKCE code_verifier, state, nonce, redirectTo). They must be carried back
// from the OAuth provider callback to the Convex callback endpoint on the
// same convex.site origin.
//
// Why `sameSite: "lax"` and no `partitioned`:
// - `sameSite: "lax"` still sends the cookie on the top-level navigation
// initiated by the OAuth provider redirecting back to `/api/auth/callback/*`,
// which is what all of these cookies need.
// - `sameSite: "none" + partitioned: true` was failing under iOS
// `ASWebAuthenticationSession` (especially with `preferEphemeralSession:
// true`) — Safari would not replay the cookie on the return leg after
// Google, dropping the PKCE verifier, state, nonce, and redirectTo. That
// manifested as `checkOAuthBodyError` during the Google token exchange and
// as a fallthrough to `SITE_URL` in the redirect callback. See
// https://github.com/get-convex/convex-auth/issues/218.
export const SHARED_COOKIE_OPTIONS = {
httpOnly: true,
sameSite: "none" as const,
sameSite: "lax" as const,
secure: true,
path: "/",
partitioned: true,
};
Comment on lines 19 to 24

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Search for Apple provider configuration and form_post handling to assess impact

echo "=== Searching for Apple OAuth provider references ==="
rg -n -C3 'apple|Apple' --type=ts -g '!node_modules/**'

echo ""
echo "=== Searching for form_post or response_mode configuration ==="
rg -n -C3 'form_post|response_mode' --type=ts -g '!node_modules/**'

echo ""
echo "=== Checking how callback handles POST requests ==="
rg -n -C5 'application/x-www-form-urlencoded' --type=ts -g '!node_modules/**'

Repository: get-convex/convex-auth

Length of output: 12426


🏁 Script executed:

# Check how cookies are actually used in the callback and OAuth flow
echo "=== Examining cookie usage in implementation ==="
head -n 350 src/server/implementation/index.ts | tail -n 100

echo ""
echo "=== Search for cookie reading/setting in callback context ==="
rg -n 'cookies|Cookie' --type=ts src/server/implementation/index.ts -A2 -B2

echo ""
echo "=== Check oauth callback file for cookie handling ==="
rg -n 'cookies|state|code_verifier|nonce' --type=ts src/server/oauth/callback.ts | head -40

Repository: get-convex/convex-auth

Length of output: 6617


🏁 Script executed:

# Check `@auth/core` Apple provider documentation and behavior
echo "=== Check if Apple provider uses form_post ==="
find . -type f -name "*.ts" -o -name "*.json" | xargs grep -l "@auth/core\|auth/core" | head -5

echo ""
echo "=== Search for OAuth state/nonce/code_verifier handling ==="
rg -n 'state|code_verifier|nonce' --type=ts src/server/ | head -30

Repository: get-convex/convex-auth

Length of output: 2724


🏁 Script executed:

# Understand cookie storage and retrieval mechanism
echo "=== Check how cookies are handled in the auth flow ==="
rg -n 'getCookie|setCookie' --type=ts src/server/ -B2 -A2

echo ""
echo "=== Look at cookies.ts usage ==="
rg -n 'SHARED_COOKIE_OPTIONS' --type=ts src/server/

Repository: get-convex/convex-auth

Length of output: 1658


🏁 Script executed:

# Check the Apple provider test configuration to understand the actual flow
echo "=== Check Apple configuration in test setup ==="
rg -n -A20 "Apple\({" test/convex/auth.ts

echo ""
echo "=== Check if there are any tests for Apple callback ==="
fd -t f -e test.ts -e spec.ts | xargs rg -l "apple|Apple" 2>/dev/null | head -5

echo ""
echo "=== Check authorizationUrl.ts to understand how providers are configured ==="
head -n 100 src/server/oauth/authorizationUrl.ts

Repository: get-convex/convex-auth

Length of output: 4171


🌐 Web query:

@auth/core Apple provider response_mode form_post

💡 Result:

For Sign in with Apple, when you request name and/or email, Apple returns the authorization response by POSTing a form (application/x-www-form-urlencoded) to your redirect_uri, so you must set response_mode=form_post (instead of the usual query redirect). [1][2]

In @auth/core (Auth.js), configure the Apple provider like this:

import Apple from "@auth/core/providers/apple"

providers: [
  Apple({
    clientId: process.env.AUTH_APPLE_ID,
    clientSecret: process.env.AUTH_APPLE_SECRET,
    authorization: {
      params: {
        scope: "name email",
        response_mode: "form_post",
      },
    },
  }),
]

Also ensure your callback endpoint (e.g. /api/auth/callback/apple) accepts POST requests and can read form-urlencoded bodies, since Apple will form_post the response to it. [1][3]

Sources:
[1] Apple Developer Documentation (Sign in with Apple authorization request) (developer.apple.com)
[2] OpenAuth Apple provider docs (form_post requirement) (openauth.js.org)
[3] Arctic “Apple” provider notes (Apple posts callback) (v1.arcticjs.dev)

Citations:


🏁 Script executed:

# Read the full Apple configuration from test setup more carefully
cat test/convex/auth.ts | grep -A 30 "^import Apple"

echo ""
echo "=== Look for any response_mode or form_post in entire codebase ==="
rg -n 'response_mode|form_post' . --type=ts --type=json -g '!node_modules/**'

Repository: get-convex/convex-auth

Length of output: 1310


🏁 Script executed:

# Check the actual Apple provider implementation imported
echo "=== Find where Apple provider comes from ==="
cat test/convex/auth.ts | head -50

Repository: get-convex/convex-auth

Length of output: 1861


🏁 Script executed:

# Check default behavior of `@auth/core` Apple provider
echo "=== Check what `@auth/core` Apple provider does by default ==="
cat package.json | grep "@auth/core"

echo ""
echo "=== Look for any Apple-specific response_mode or form_post handling in convex-auth ==="
rg -n -B5 -A5 'Apple' src/server/ --type=ts

echo ""
echo "=== Check if there's integration test for Apple callback ==="
find . -type f \( -name "*.test.ts" -o -name "*.spec.ts" \) -exec grep -l "apple\|Apple" {} \;

Repository: get-convex/convex-auth

Length of output: 1956


sameSite: "lax" will break POST-based OAuth callbacks (e.g., Apple Sign In).

Per the sameSite cookie specification, "lax" cookies are not sent on cross-site POST requests. Apple Sign In uses response_mode=form_post by default (standard behavior from @auth/core), which means Apple POSTs the authorization response to your callback endpoint. With sameSite="lax", the PKCE code_verifier, state, and nonce cookies—which are required by handleOAuth to verify the callback—won't be present, causing the callback to fail.

The code in src/server/implementation/index.ts (lines 309–318) explicitly handles POST requests with a comment noting support for providers like Apple, but the cookie policy undermines this.

Consider one of these alternatives:

  1. Change sameSite to "none" and ensure secure: true is set (which it is)
  2. Make cookie options configurable per provider
  3. Investigate whether providers can be forced to use query-based response mode instead
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/cookies.ts` around lines 19 - 24, The SHARED_COOKIE_OPTIONS
currently sets sameSite: "lax", which breaks POST-based OAuth callbacks (e.g.,
Apple Sign In); update the SHARED_COOKIE_OPTIONS constant so sameSite is "none"
and keep secure: true to ensure cookies are sent on cross-site POSTs, or
alternately make the cookie options configurable per provider (e.g., allow
overriding SHARED_COOKIE_OPTIONS for Apple) or force providers to use
query-based response_mode—locate SHARED_COOKIE_OPTIONS in src/server/cookies.ts
and modify its sameSite value or add provider-specific configuration points.


const REDIRECT_MAX_AGE = 60 * 15; // 15 minutes in seconds
Expand Down