Skip to content

fix: email-code sign-in never succeeds when the identifier differs in case - #444

Open
dbeattie71 wants to merge 2 commits into
clerk-community:mainfrom
bytefoo:fix/identifier-case-insensitive-comparison
Open

fix: email-code sign-in never succeeds when the identifier differs in case#444
dbeattie71 wants to merge 2 commits into
clerk-community:mainfrom
bytefoo:fix/identifier-case-insensitive-comparison

Conversation

@dbeattie71

Copy link
Copy Markdown

Fixes #443.

Email-code sign-in fails permanently when the submitted identifier differs from the stored one only by case. attemptSignIn compares them with !=, and because the back end normalises identifiers while the panel sends back what the user typed, the guard discards the in-flight SignIn and creates a new one on every submission — re-preparing the first factor, invalidating the code already emailed, and checking the user's code against a verification it does not belong to. Each retry repeats the cycle.

The issue has the full HTTP traces from a device: the failing run creates, prepares and attempts inside 544 ms, while the succeeding run (same build, same device, one character's case different) carries a single SignIn across the pause and completes.

Changes

clerk_auth — compare identifiers case-insensitively. Given the back end normalises them, a case-sensitive comparison cannot be correct.

identifier!.toLowerCase() != client.signIn!.identifier?.toLowerCase()

clerk_flutter — stop the platform rewriting the identifier. The field set no keyboardType, textCapitalization, autocorrect or autofillHints, so iOS treated an email address as prose and autocorrect capitalised correctly typed input as it was entered. In the report this came from, the user typed lower-case every time and iOS capitalised it every time.

ClerkTextFormField did not expose these at all, so no caller could opt in. They are now parameters defaulting to the previous behaviour — no other field changes — and set explicitly on the identifier input. The email branch already sits under Closeable(closed: identifierType.value.isPhoneNumber) with phone numbers handled separately, so an email keyboard here does not conflict with phone support.

Either change alone stops the reported failure. Together they fix the cause and the trigger.

Tests

  • sign_in_identifier_case_test.dart — asserts no second SignIn is created when the identifier differs only by case. Confirmed failing before the fix (Expected: empty, Actual: [MockHttpCall]) and passing after.
  • clerk_identifier_input_test.dart — asserts the identifier field's input attributes.

Suites green: 647 in clerk_auth, 784 in clerk_flutter.

Notes

The comparison is toLowerCase() rather than a locale-aware fold, matching how the identifier is normalised server-side. Phone identifiers are unaffected — they contain no cased characters, so the comparison is unchanged for them.

dbeattie71 and others added 2 commits August 25, 2026 16:09
The back end normalises an identifier — an email address is stored
lower-cased — while the sign-in UI sends back exactly what the user typed.
`attemptSignIn` compared the two with `!=`, so `Person@example.com` and the
stored `person@example.com` read as two different users.

When that happens the in-flight `SignIn` is discarded and a new one created on
*every* submission. Creating it re-prepares the first factor, which sends a
fresh code and invalidates the one already in the user's inbox, and the code
they typed is then checked against a verification it does not belong to. The
attempt fails with `form_code_incorrect`, and because each retry repeats the
cycle it can never succeed.

This is easy to hit on iOS, where the identifier field sets no
`keyboardType`, `textCapitalization`, `autocorrect` or `autofillHints`, so the
platform treats it as prose and autocorrect capitalises a correctly typed
address as the user types it.

Adds a regression test asserting no second `SignIn` is created when the
submitted identifier differs from the stored one only by case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The identifier field set no `keyboardType`, `textCapitalization`,
`autocorrect` or `autofillHints`, so the platform treated an email address as
prose. On iOS that means autocorrect rewrites a correctly typed address as the
user enters it — capitalising a local part that matches a proper noun in its
dictionary, so `derek@…` becomes `Derek@…` without the user doing anything
wrong. It also means a prose keyboard with no `@` key, and no saved
credentials offered, because the platform has no hints to match on.

`ClerkTextFormField` did not expose these at all, so no caller could opt in.
They are now parameters, defaulting to the previous behaviour so no other
field changes, and set explicitly on the identifier input.

This is the other half of the sign-in failure fixed in the previous commit:
the field corrupted the identifier, and `attemptSignIn` then rejected the
value the field had corrupted.

Note the email branch already knows it is the email branch — it sits under
`Closeable(closed: identifierType.value.isPhoneNumber)`, with phone numbers
handled by a separate widget — so an email keyboard here costs nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dbeattie71 dbeattie71 changed the title Fix sign-in failing permanently when the identifier differs by case fix: email-code sign-in never succeeds when the identifier differs in case Aug 25, 2026
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.

Email-code sign-in never succeeds when the identifier differs in case: attemptSignIn discards the prepared SignIn on every submission

2 participants