WordPress.org

Make WordPress Core

Opened 6 years ago

Last modified 4 months ago

#9568 assigned feature request

Allow users to log in using their email address

Reported by: Denis-de-Bernardy Owned by:
Milestone: Future Release Priority: low
Severity: normal Version: 2.8
Component: Users Keywords: has-patch needs-testing
Focuses: Cc:

Description

I've been looking into a few tickets and adding patches to things that were potentially problematic when implementing a membership plugin (#1626, #4170, #9563, #9564). The general idea being twofold: to avoid duplicate users at all costs -- because it's a bloody mess to merge users in a billing system.

I'm about to embark on a mid-sized patch that renders the WP username field optional. This is a natural next step from #9563 and #9564.

Users could then be allowed to specify an email only, when registering. And they could use their email to login -- in addition to their username if they specified one.

Attachments (11)

9568.diff (7.6 KB) - added by Denis-de-Bernardy 6 years ago.
updated patch, against 10966
9568.2.diff (7.6 KB) - added by Denis-de-Bernardy 6 years ago.
patch against 11077
9568.3.diff (9.5 KB) - added by Denis-de-Bernardy 6 years ago.
patch against 11130, using ID as user_nicename
9568.4.diff (9.7 KB) - added by Denis-de-Bernardy 6 years ago.
as 9568.3.diff with a different approach for extracting a user_nicename from the email
9568.5.diff (9.8 KB) - added by Denis-de-Bernardy 6 years ago.
yet another refresh, against 11256
9568.6.diff (9.8 KB) - added by Denis-de-Bernardy 6 years ago.
9568.7.diff (9.2 KB) - added by Denis-de-Bernardy 6 years ago.
refreshed patch
9568.8.diff (7.2 KB) - added by ericlewis 6 months ago.
9568.9.diff (7.8 KB) - added by vhomenko 5 months ago.
auth_with_login_tests.php (2.1 KB) - added by vhomenko 5 months ago.
add wp_authenticate_username_password and wp_authenticate_email_password unit tests to wordpress-develop/tests/phpunit/tests/auth.php
9568.10.diff (8.3 KB) - added by MikeHansenMe 5 months ago.
patch with simple test in auth.php

Download all attachments as: .zip

Change History (89)

comment:1 @Denis-de-Bernardy6 years ago

  • Keywords has-patch tested added

patch is well tested, but it could always use a little more of it. it includes #9563 and #9564, which are prerequisites to make this work.

Detailed Changes:

  • Username is still available, but no longer is a required field
  • If left empty, the username will default to the email address (and change when the email address changes)
  • Note that internally, the username is still used, to ensure backward compatibility
  • The various form labels and error messages have been changed to reflect the optional nature of the username

comment:2 @Denis-de-Bernardy6 years ago

  • Keywords needs-patch added; has-patch tested removed

patch no longer works against the current trunk

@Denis-de-Bernardy6 years ago

updated patch, against 10966

comment:3 @Denis-de-Bernardy6 years ago

tests conducted:

  • register user without a username from register form and users / add new
  • change user email from profile and users / edit
  • verified user is not logged out when changing email under profile

comment:4 @ryan6 years ago

I like this in principle. I'll try out the patch after the weekend.

comment:5 @Denis-de-Bernardy6 years ago

sigh... patch no longer applies cleanly :P

@Denis-de-Bernardy6 years ago

patch against 11077

comment:6 @Denis-de-Bernardy6 years ago

  • Keywords has-patch tested added; needs-patch removed

actually, it did work:

patching file wp-login.php
patching file wp-includes/registration.php
Hunk #1 succeeded at 188 with fuzz 2 (offset 12 lines).
Hunk #2 succeeded at 266 (offset 12 lines).
patching file wp-admin/includes/user.php
patching file wp-admin/user-new.php
patching file wp-admin/user-edit.php
Hunk #1 succeeded at 246 (offset 8 lines).

but refreshed it nonetheless.

comment:7 @Denis-de-Bernardy6 years ago

  • Keywords commit added

Please? :-)

comment:8 @misterbisson6 years ago

+1

I've been working on this same issue for a while myself (implementing it entirely as a plugin). Though I'm about to go live with a solution, core support would be great.

Thinking forward a bit, this has some implications for MU and BuddyPress (and it's inside BP that I'm trying to solve this). How would BP construct a persistent user profile URL if not by user_login (as it currently does), for instance. Still, there's an easy answer to that (use the users.ID, I guess), and being able to use an email address as username is an important big step.

comment:9 @ryan6 years ago

I'm curious about user_nicename impact as well. I'd like to do this, but maybe we should wait for 2.9.

comment:10 @Denis-de-Bernardy6 years ago

@misterbisson: Agreed. What prompted me to write the patch was that this wasn't trivial to implement with a plugin, and is a much needed functionality when you're dealing with a membership site.

@Ryan: foo[at]bar.com gets sanitized as foo-bar-com (or foobar-com) as the user_nicename. It presumably doesn't get changed afterwards due to this check in wp_insert_user():

if ( empty($user_nicename) )
  $user_nicename = sanitize_title( $user_login );

It's the only place where it actually gets written. Else, it's behaving as a read-only field. If you'd rather see it changed as the username changes, just let me know and I'll adjust the patch accordingly.

comment:11 @Denis-de-Bernardy6 years ago

Thinking out loud... Another possibility could be to leave an empty login field around that is actually writable in the profile/edit user page. Upon filling it once, it would become read-only and change the username accordingly at that time.

Whichever option feels best to you will work for me. Just let me know how you'd prefer this to get implemented and/or what you are dreading so I can check and test, so we can get this into 2.8.

comment:12 @ryan6 years ago

Also thinking out loud... user_nicename is exposed in various places (author links). Having it be based on email means exposing an email address.

comment:13 @Denis-de-Bernardy6 years ago

  • Keywords needs-patch added; has-patch tested commit removed

sounds reasonable. I'll refresh the patch and make it use the user's ID instead for those who are using their email address as a login.

@Denis-de-Bernardy6 years ago

patch against 11130, using ID as user_nicename

comment:14 @Denis-de-Bernardy6 years ago

  • Keywords has-patch tested added; needs-patch removed

uploaded two patches against the current trunk.

  • 9568.3.diff post-processes inserts so as to use the ID instead of the sanitized email
  • 9568.4.diff takes another approach, and uses preg_replace("/@.*/", "", $user_email) as the user_name

If you've other suggestions/potential objections, just let me know and I'll get them sorted out.

@Denis-de-Bernardy6 years ago

as 9568.3.diff with a different approach for extracting a user_nicename from the email

comment:15 follow-up: @misterbisson6 years ago

I'm concerned that using preg_replace("/@.*/", "", $user_email) as the username creates a large opportunity for duplicate usernames. Also, the idea of building permalinks against an email address (which will necessarily change in my application as applicants become students at my university) seems off.

This isn't the solution, but the plugin I developed filters sanitize_user (which is used for all sorts of things other than actually sanitizing usernames/aside) and replaces it with a random string (pattern: 'p'. rand( 0, 9 ) . randomstring( 7, 'abcdefghijklmnopqrstuvwxyz' )). I then replaced wp_authenticate() so it tests against get_user_by_email().

As noted above, that isn't a solution for anybody but me, but it does result in slightly more friendly author URLs than numbers alone. Still, I'd prefer using the user ID over using anything based on email address (other than, perhaps, an md5 of the email address).

comment:16 in reply to: ↑ 15 ; follow-up: @Denis-de-Bernardy6 years ago

Replying to misterbisson:

I'm concerned that using preg_replace("/@.*/", "", $user_email) as the username creates a large opportunity for duplicate usernames.

It's the user_nicename, and there is a function further down that ensures it's unique.

Also, the idea of building permalinks against an email address (which will necessarily change in my application as applicants become students at my university) seems off.

Once set, the user_nicename isn't changed.

Still, I'd prefer using the user ID over using anything based on email address (other than, perhaps, an md5 of the email address).

Oh, that's what the replace is about. john.doe@… would have a nicename of john-doe (or john-doe-2 if there is one already). if this is a problem on some servers, one could use a plugin to change it as needed.

comment:17 in reply to: ↑ 16 @misterbisson6 years ago

Replying to Denis-de-Bernardy:
True. I can set user_nicename to whatever I want with the pre_user_nicename filter. I'm good with either patch.

comment:18 @Denis-de-Bernardy6 years ago

  • Keywords commit added

comment:19 @Denis-de-Bernardy6 years ago

see also #9577 if this makes it into 2.8. hoping it does...

@Denis-de-Bernardy6 years ago

yet another refresh, against 11256

comment:20 @Denis-de-Bernardy6 years ago

@ryan: could this get committed before the patch needs yet another refresh? :-|

comment:21 @Denis-de-Bernardy6 years ago

  • Priority changed from normal to high

comment:22 @Denis-de-Bernardy6 years ago

tests done:

  • create a user with no username (succeeds), from register form and edit user form
  • create a user with existing email (fails), from register form and edit user form
  • update user to new email (succeeds)
  • update user to existing email (fails)
  • create user with dup user_nicename based on email (succeeds with a suffix)

dunno how to test this any further...

comment:23 @Denis-de-Bernardy6 years ago

oh, and the latest patch users the email up to the @ as a nicename, so as to not reveal it in the user_nicename.

comment:24 @Denis-de-Bernardy6 years ago

  • Severity changed from normal to major

comment:25 @markjaquith6 years ago

I really like this idea, especially since the e-mail address exposure issue has been mitigated. I'm all for this as an early 2.9 feature addition.

comment:26 @Denis-de-Bernardy6 years ago

Are you sure you can't add this to 2.8? I'd really hate to need to override entire pages in WP to make my plugin work? :-|

comment:27 @Denis-de-Bernardy6 years ago

  • Status changed from new to accepted

comment:28 @westi6 years ago

  • Keywords early added
  • Milestone changed from 2.8 to 2.9

This is a big change and I agree with Mark that it should wait for early in 2.9

comment:29 @Denis-de-Bernardy6 years ago

  • Keywords needs-patch added; has-patch tested commit removed

needs a new refresh

comment:30 @Denis-de-Bernardy6 years ago

  • Keywords has-patch added; needs-patch removed

@Denis-de-Bernardy6 years ago

comment:31 @Denis-de-Bernardy6 years ago

haven't tested the refresh yet.

comment:32 @westi6 years ago

Not sure that making username optional is the right way to go.

It feels like it would be nicer to work like quite a few other sites do and that is required you to pick a username on signup but then let you log in with your username or your email address at a later date.

comment:33 @Denis-de-Bernardy6 years ago

Sometimes, you need to register users without prompting them to enter a username. When importing data from a separate application, for instance. How about I change it as follows:

  • Require the username on signup
  • If username = email, allow user to edit it and refuse to save until he picks one
  • Once username <> email, refuse to edit username

That way, we'd have the best of both worlds. Thoughts?

comment:34 @aaroncampbell6 years ago

  • Cc aaroncampbell added

comment:35 follow-ups: @aaroncampbell6 years ago

I haven't gone through everything here yet (there's a lot of patches here), but I definitely have to say that logging in with an E-Mail is a big deal, and implemented on almost all the sites I do. I'd love to see this taken a little further, because right now most of what I do is simply:

function allow_email_login($user, $pass) {
     global $wpdb;
     if (is_email($user)) {
          $user = $wpdb->get_var("SELECT user_login FROM $wpdb->users WHERE user_email = '$user'");
     }
     return;
}
add_action('wp_authenticate', 'allow_email_login', 0, 2);

The problem is that when we import users (for example from an existing fulfillment or billing system), we still have to create some sort of (rather pointless) username that isn't used anywhere.

comment:36 in reply to: ↑ 35 @Denis-de-Bernardy6 years ago

Replying to aaroncampbell:

The problem is that when we import users (for example from an existing fulfillment or billing system), we still have to create some sort of (rather pointless) username that isn't used anywhere.

Hehe. That's the very reason I opened it in the first place. :-)

comment:37 follow-up: @Denis-de-Bernardy6 years ago

@aaron: thoughts on this?

http://core.trac.wordpress.org/ticket/9568#comment:33

I'm still hesitating on whether to make it a mandatory field (in which case you'd *need* to fill it in) or an optional one.

comment:38 in reply to: ↑ 37 @aaroncampbell6 years ago

Replying to Denis-de-Bernardy:

@aaron: thoughts on this?

http://core.trac.wordpress.org/ticket/9568#comment:33

I'm still hesitating on whether to make it a mandatory field (in which case you'd *need* to fill it in) or an optional one.

The problem still lies in the automated imports. I've found that a lot of places (Harvard for instance) tend to collect very little info on customers. We had to import a lot of users where they had little more than an E-Mail address, so we had to create a bogus username (which was ugly). I'd like to see it be an "either or" kind of thing, and I see two ways to do that:

  1. Make a setting to change from using usernames for logging in (and for permalinks etc) to using E-Mail addresses. This would mean that when the blog owner tried to change from one to the other that we need to do some error checking to make sure that the chosen column has unique values (This is because I envision being able to have empty usernames).
  1. Simply open it up and make it so a user can log in with either a username OR email, and that only one is required (although both could be filled in). The difficulty here is that you need to enforce a multi-column unique key that allows for empty values (which there could be plenty of).

I'm thinking less on the user's end when they sign up, and more about the back end for when they're imported.

comment:39 @Denis-de-Bernardy6 years ago

Well, what the patch currently does is this:

  • set user_login to user_email when no user_login is supplied
  • for such users, work out a user_nicename (used in permalinks) based on the first part of the email (the stuff up to the @, basically), which is left unchanged from that point forward
  • for users where user_email = user_login, change user_login along with user_email; for users where user_email <> user_login, keep user_login
  • allow to log in with either of user_login or user_email

the only real remaining question are:

  • per westi's suggestion, whether we should allow users to pick a user_login when none is present
  • per simonwheatley's question in IRC yesterday, whether we should allow users to change their user_login when one is present

comment:40 @dd326 years ago

per westi's suggestion, whether we should allow users to pick a user_login when none is present

If one is auto-generated from the email.. Nah.. well.. Not unless every user can change their usernames - Which would be a nice feature, But permalink history'ness and all that..

My main question is:

  • What happens when a user changes their email, Does their user_login change to reflect it? IMO, It should stay static, So that behaviour will need to be documented for developers who may rely on it for internal systems/custom integrations.

comment:41 @Denis-de-Bernardy6 years ago

@DD32: To the end user, it looks as if there is no user_login field at all. In the background, I keep user_login in sync with the email without ever displaying the login field.

Westi's question goes down to do whether to show a blank login field that the user can then fill out to choose a (final) user_login when he currently has none. I like the idea, personally.

comment:42 @dd326 years ago

Ah, just looked at the patch. Looks like thats a nice way to go about it.

Westi's idea of letting the user choose a username later on sounds like a good idea, Just a extra explanation of "You currently do not have a username, You may optionally create one now. Please note that once your username is chosen, You cannot change it"

comment:43 @Denis-de-Bernardy6 years ago

Adding this for reference, by DD32 in IRC:

I just noticed that bbPress allows email login too now, You might want to check up on their implementation, since bbpress/wordpress are integratable

comment:44 @aaroncampbell6 years ago

I looked at the patch, and I like what's going on. I agree with dd32 that a little extra instruction would be good, but it also looks like you lost the "<strong>ERROR</strong>: Please enter a username." error that currently shows if no username is entered for login. And else added to your if ( !empty($user_login) ) could easily bring it back though.

Also, I really like the idea of letting users change their login, but maybe we can implement that after we have permalink history (and probably from another ticket).

@Denis-de-Bernardy6 years ago

refreshed patch

comment:45 @Denis-de-Bernardy6 years ago

  • Keywords needs-testing added

refreshed patch attached, that takes comments from above into account.

  • registration form -> unchanged (requires a username)
  • login form -> allow to log in using username or email
  • internal functions -> mostly changed as was previously done: if no username is present, use the email instead (this is to allow for imports from 3rd party software that lack a username)
  • edit user form -> it now requires the user to fill in a username if it was lacking
  • new user form -> it allows to fill in no username (this is mostly for testing purposes, but this also allows users registered by the admin to choose one to their liking)

needs testing.

comment:46 @Denis-de-Bernardy6 years ago

reposting previous comment with formatting:

  • registration form -> unchanged (requires a username)
  • login form -> allow to log in using username or email
  • internal functions -> mostly changed as was previously done: if no username is present, use the email instead (this is to allow for imports from 3rd party software that lack a username)
  • edit user form -> it now requires the user to fill in a username if it was lacking *new user form -> it allows to fill in no username (this is mostly for testing purposes, but this also allows users registered by the admin to choose one to their liking)

comment:47 @Denis-de-Bernardy6 years ago

  • Owner Denis-de-Bernardy deleted
  • Status changed from accepted to assigned

comment:48 @ryan6 years ago

  • Milestone changed from 2.9 to Future Release

comment:49 @asbjornu5 years ago

What's the plan with applying this patch? There was mentioning of including it in 2.9, but that obviously didn't happen and now it's set to "Future Release". Does that mean it will be included in a dot release of 2.9 or do we have to wait until 3.0 to see this implemented?

I'm having lots of customers that would love to just eradicate the user name from all registration and login forms, but at the moment that requires a heap of custom code fiddling, core file patching and whatnot. Having this supported in the core is very, very welcome!

comment:50 @asbjornu5 years ago

  • Cc asbjornu added

comment:51 @nacin5 years ago

Future Release means it is currently not slated for any milestone. You won't see it before 3.1.

See also http://wordpress.org/extend/plugins/wp-email-login/

comment:52 in reply to: ↑ 35 @aaroncampbell5 years ago

  • Milestone changed from Future Release to 3.1

It doesn't take a lot of code or any modification of core files to allow E-Mail login, just 8 lines of code posted above. You will have to make sure that you collect the E-Mail address on registration though.

This is definitely something I'd like to see though, so I'm going to bump it up to 3.1 so I can take a look at it after the 3.0 release.

comment:53 @mikeschinkel5 years ago

  • Cc mikeschinkel@… added

I can't tell from reading through the comments but I hope that if this patch is applied a site owner can still require a user to provide a username while still allowing them to login with an email address.

comment:54 @F J Kaiser5 years ago

  • Cc 24-7@… added

comment:56 @nacin5 years ago

  • Milestone changed from Awaiting Triage to Future Release

comment:57 @norbertm5 years ago

  • Cc norbert@… added

comment:58 @johnbillion4 years ago

  • Cc johnbillion@… added

comment:59 @gruvii3 years ago

  • Cc gruvii added

comment:60 @webord3 years ago

  • Cc bordoni.dev@… added

comment:61 @aniketpant2 years ago

  • Cc me@… added

comment:62 @mau2 years ago

  • Cc ngomau@… added

comment:63 @ocean907 months ago

#30626 was marked as a duplicate.

comment:64 @gputignano7 months ago

Is it time to consider this enhancement?

comment:65 @ericlewis6 months ago

  • Milestone changed from Future Release to 4.2

Let's get into this for 4.2. Anyone want to check up on the patch attachment:9568.7.diff and make sure it's fresh and still makes sense?

comment:66 @pento6 months ago

  • Keywords needs-refresh needs-unit-tests added; early removed
  • Priority changed from high to normal
  • Severity changed from major to normal

9568.7.diff doesn't apply against trunk at all, and has no unit tests. Fixing that up is probably a good place to start, for discussing putting it into 4.2.

@ericlewis6 months ago

comment:67 @ericlewis6 months ago

attachment:9568.8.diff is a fresh approach here. Rather than usernames to be a non-required field, modify the login process to allow for logging in with user email.

wp_signon() is wp-login.php's form submission handler, which calls wp_authenticate(), the canonical "authenticate username and password" function, which is also used by XMLRPC. This patch changes the $username parameter to $user_identifier, which supports usernames for back-compat, and now also emails.

Authentication logic (i.e. is this username and password valid?) actually takes place in a callback on the authenticate filter. wp_authenticate_username_password() is the hooked function for checking usernames and passwords. I've added another callback for wp_authenticate_email_password(). The two share a good amount of logic. We could create another function like wp_authenticate_user_field_and_password( $field_name, $field_value, $password ), which could share almost all this logic, and then the username and email functions would proxy to this function.

I've modified the two functions to coallate error messages, so we can figure out what the best way to present error messages to the user (this needs ironing out). e.g. so if you enter "eric@…," we might say "The username or email address and password combination was incorrect." rather than just the last-in error "The email address and password combination was incorrect."

comment:68 @vhomenko5 months ago

In case of wrong username or password:

Notice: Undefined variable: username in /var/www/html/wordpress-develop/src/wp-includes/pluggable.php on line 573

Should be $user_identifier instead as both are strings?

@vhomenko5 months ago

comment:69 @vhomenko5 months ago

I discovered that the following part of the wp-login.php wasn't considering the possibility of an email based login:

case 'login' :
default:

...

	// If the user wants ssl but the session is not ssl, force a secure cookie.
	if ( !empty($_POST['log']) && !force_ssl_admin() ) {
		$user_name = sanitize_user($_POST['log']);

		if ( $user = get_user_by('login', $user_name) ) {
...

I updated the patch with a fix.

The Undefined variable: username in wp_authenticate() is also fixed.

Newbie question: Could anyone give me a few pointers, as to what kind of tests are necessary for the wp_authenticate_email_password()? I was hoping there'd be a test case for wp_authenticate_username_password() that I could bootstrap from. Does it suffice to write some unit tests for wp_authenticate()?

@vhomenko5 months ago

add wp_authenticate_username_password and wp_authenticate_email_password unit tests to wordpress-develop/tests/phpunit/tests/auth.php

comment:70 @vhomenko5 months ago

Please review the unit tests.

Also, there is an UX issue: if the user enters a wrong email address at the login screen, there are two error messages displayed:

ERROR: Invalid username. Lost your password?
ERROR: Invalid email address.

While the wp_authenticate_email_password() returns if the given email doesn't validate, making wp_authenticate_username_password() do a similar check seems non-trivial to me.

I'd propose using the wp_login_errors filter, that's applied in the end of the wp-login.php, to purge the 'invalid_username' entry, if there is 'invalid_email', in the WP_Error object.

If that's acceptable, then where would be a good place to put the callback function?

comment:71 @vhomenko5 months ago

Oops. auth_with_login_tests.php attachment is actually a diff file.

comment:72 @vhomenko5 months ago

A snippet to reduce the noise level of the errors, in case of entering a wrong email address into the login form.

add_filter( 'wp_login_errors', 'consolidate_wrong_user_id_errors', 20 );

function consolidate_wrong_user_id_errors( $errors ) {
	if ( ! is_wp_error( $errors ) ) return $errors;
	if ( 2 !== count( $errors->errors ) ) return $errors;

	$codes = $errors->get_error_codes();
	$is_invalid_username = in_array( 'invalid_username', $codes );
	$is_invalid_email = in_array( 'invalid_email', $codes );

	if ( $is_invalid_username && $is_invalid_email ) {
		$errors->remove( 'invalid_username' );
	}

	return $errors;
}

comment:73 @slackbot5 months ago

This ticket was mentioned in Slack in #core by drew. View the logs.

comment:74 @DrewAPicture5 months ago

  • Priority changed from normal to low

@MikeHansenMe5 months ago

patch with simple test in auth.php

comment:75 @MikeHansenMe5 months ago

  • Keywords needs-refresh needs-unit-tests removed

comment:76 @DrewAPicture4 months ago

9568.10.diff still applies. Some feedback:

  • Any of the hooks where the $username parameter is changed, the parameter description should reflect that it could be a username or email and a changelog entries should be added to the hook docs to reflect that emails are now also possible
  • "Collate" is misspelled as "Coallate" in a couple of places
  • Coding standards (mostly spacing) should be applied to changed or new code
  • The test method should have a docblock with @ticket 9568 so that test can be targeted by group (test passes)

comment:77 @slackbot4 months ago

This ticket was mentioned in Slack in #core by helen. View the logs.

comment:78 @helen4 months ago

  • Milestone changed from 4.2 to Future Release

Seems like this still needs a little work, and definitely needs more review.

Note: See TracTickets for help on using tickets.