Repository navigation
Accept a username as a password-login identifier - #76
Merged
Merged
Conversation
Password login read the `email` input and looked the account up on the `email` column, so an account that signs in with a username could not sign in at all. Name the columns instead. `laranail.authkit.login.identifiers` lists the user columns an identifier is matched against, in order, and `login.field` names the request input it arrives in. Both default to email, so a form or API client written before this keeps working untouched. Validation is derived from that same list rather than configured beside it: with email alone the identifier is still validated as an address, and any wider set drops that rule, because a username is not an address and validating one as an address rejects every username before the lookup runs. The lookup is one single-column query per identifier rather than one `or` query. EloquentUserProvider would take a closure credential expressing the `or`, but DatabaseUserProvider and any provider an application has substituted need not -- and going through the provider is pointless if the call only works for one of them. The password is checked per candidate, so a username that happens to equal another account's address does not lock its owner out.
Ten cases: the four the issue asks for -- email login, username login, an identifier matching no account, and throttling -- plus the wrong-password case, the derived validation rules in both modes, and the collision where one account's username equals another's address. Two of them exist to pin the default rather than the feature: email is the only identifier shipped, and a username is refused until one is configured. Widening the default fails them. The `username` column lives in the workbench, not in a package migration, which is the claim the docs make made executable: AuthKit does not own the users table, so if it ever started depending on a column it does not create, the test harness would be the only place that column existed. Proven red three ways before being relied on: hardcoding the lookup back to the email column fails 2, always validating the identifier as an address fails 1, and widening the shipped default fails 3.
Records the answer the issue asks for: the username column, its uniqueness constraint and its registration validation belong to the application, because AuthKit resolves whatever user model is configured and does not own the users table. Also states the two things a reader would otherwise have to infer -- that identifier spaces may overlap and which account wins, and that CheckEmailExists is deliberately not widened, since answering it for usernames would add an enumeration surface nothing asks for.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Password login read the
emailinput and looked the account up on theemailcolumn, so anaccount that signs in with a username could not sign in at all. This names the columns instead.
Add
'username'toidentifiersto accept either.fieldis the request input the identifierarrives in; it stays
emailso existing forms and API clients are untouched.The open design decision, closed
The issue asks whether username login should be an independently enabled feature or a configurable
email-or-username identifier. It is a configurable identifier.
A separate feature flag means a second login path — its own action, request and controller — kept
in step with the first for ever, and an abstraction whose every branch has exactly one
implementation. One path reading an ordered list is the plain version, and email-only remains the
default either way.
Three things worth reviewing
Validation is derived from the identifier list, not configured beside it. With email alone the
identifier is still validated as an address — byte-identical to before. Any wider set drops that
rule, because a username is not an address and validating one as an address rejects every username
before the lookup runs. Deriving it means a rule cannot describe a mode the lookup does not
implement.
The lookup is one single-column query per identifier, not one
orquery.EloquentUserProviderwould accept a closure credential expressing the
or, butDatabaseUserProviderand any provideran application has substituted need not — and going through the provider at all is pointless if the
call only works for one of them.
The password is checked per candidate, not only against the first row found. Nothing stops one
account's username from equalling another's address; checking each candidate keeps the username's
owner able to sign in, and makes a failed attempt cost the same number of hash comparisons whichever
column the identifier belongs to.
Who owns the column
The application. AuthKit resolves whatever user model is configured and does not own the users
table, so it ships no migration for one — adding
username, its uniqueness constraint and itsregistration validation is the application's job. That is the question the issue asks to be
documented, and it is now in
docs/login.md.The test harness adds the column in
workbench/, not in a package migration, which makes that claimexecutable: if the package ever started depending on a column it does not create, the suite would be
the only place that column existed.
CheckEmailExistsis deliberately not widened — answering it for usernames would add ausername-enumeration surface nothing here asks for.
Verification
laranail-pint --testProven red before being relied on, three separate ways:
emailcolumn['email', 'username']Nothing renames.
AttemptEmailPasswordLogin, its request, its interface, its abstract controllerand
FindUserByEmailkeep their names and behaviour; only the identifier set widens, and only whenconfigured. Renaming five public classes is a separate decision and would make this diff
unreviewable.
Closes #65.