Skip to content

Apple provider: nonce verification always fails ("Nonces mismatch") — hex vs base64url encoding #2378

@qocotomi

Description

@qocotomi

Summary

When using the id_token grant for Sign in with Apple (native iOS) with signInWithIdToken({ provider: 'apple', token, nonce }), GoTrue returns "Nonces mismatch" even when the client sends the correct raw nonce. The cause is an encoding mismatch in nonce verification: GoTrue compares a hex-encoded hash to Apple's base64url-encoded hash.

Environment

  • Flow: Native Sign in with Apple → Apple returns ID token (JWT) → client sends id_token + raw nonce to POST /token?grant_type=id_token.
  • Provider: Apple.
  • GoTrue: Hosted Supabase Auth (and/or self-hosted from supabase/auth).
  • Error: invalid nonce / Nonces mismatch from the id_token grant.

Root cause

In internal/api/token_oidc.go, nonce verification does:

hash := fmt.Sprintf("%x", sha256.Sum256([]byte(params.Nonce)))
if hash != idToken.Nonce {
  return apierrors.NewOAuthError("invalid nonce", "Nonces mismatch")
}
  • GoTrue: hash is the SHA-256 of the request nonce, encoded as lowercase hex (64 characters).
  • Apple ID token: The nonce claim is the SHA-256 of the nonce, encoded as base64url without padding (43 characters), per Apple OIDC and common OIDC practice.

So the comparison is hex string vs base64url string and can never succeed.

Expected behavior

For the Apple provider, nonce verification should compare like-for-like:

  1. Compute nonce_hash = SHA-256(params.Nonce).
  2. Encode nonce_hash as base64url (no padding) to match Apple’s nonce claim.
  3. Compare base64url(nonce_hash) to idToken.Nonce.

Proposed fix (conceptual)

Use base64url for the computed hash when the provider is Apple, and keep existing behavior for other providers:

// In token_oidc.go, where nonce is verified:
sum := sha256.Sum256([]byte(params.Nonce))
var computedNonce string
if providerType == "apple" {
  // Apple's ID token nonce claim is base64url (no padding) per OIDC
  computedNonce = base64.RawURLEncoding.EncodeToString(sum[:])
} else {
  computedNonce = fmt.Sprintf("%x", sum)
}
if computedNonce != idToken.Nonce {
  return apierrors.NewOAuthError("invalid nonce", "Nonces mismatch")
}

(Exact branch and provider detection to match your codebase; other providers may need to stay hex or be normalized per their spec.)

Minimal repro

  1. Native iOS app: Sign in with Apple → obtain id_token (JWT) and the raw nonce that was hashed and sent to Apple.
  2. POST /auth/v1/token with grant_type=id_token, provider=apple, id_token=<jwt>, nonce=<raw_nonce>.
  3. Result: 400 with "Nonces mismatch" even though the raw nonce is correct and the token’s nonce claim is base64url(SHA256(raw_nonce)).

References

  • Apple: Sign in with Apple REST API — identity token includes nonce as hashed value.
  • OIDC / JWT: nonce in ID token is typically base64url-encoded hash when sent as hash to the provider.
  • Existing issue about nonce/encoding: e.g. #1829 (Google); same class of issue for Apple.

Suggested next steps: Implement provider-specific nonce encoding (base64url for Apple) in the id_token grant so native Sign in with Apple works without disabling nonce verification.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions