FriendsOfSymfony / FOSUserBundle

Provides user management for your Symfony project. Compatible with Doctrine ORM & ODM, and custom storages.
https://symfony.com/doc/master/bundles/FOSUserBundle/index.html
MIT License
3.24k stars 1.57k forks source link

Register/Login with email only #555

Open gavinwilliams opened 12 years ago

gavinwilliams commented 12 years ago

Is there any way to get FOSUserBundle to register/login by email only and disable/remove the username property?

nathanw-jc commented 12 years ago

A recent project of mine required exactly what you're trying to do, rather then disabling/removing the username I made it a hidden field that was filled in with a random set of numbers during registration and enabled the email log in functionality found here: https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/logging_by_username_or_email.md.

Not really what you're looking for but maybe it helps.

treethinking commented 12 years ago

Another way to do this is to hide the username field and in the prePersist/preUpdate for your User entity set the username to the email address. That fixes the logging in by username or email issue too since the username is the same as the email address.

weyandch commented 12 years ago

@treethinking the prePersist/preUpate won't help with the validation of setting the username. The form validates before the Hooks are executed, thus the registration will fail without further modifications

GTheron commented 12 years ago

I just did it for the project I'm working on, what I did is :

And I think that's about it, I was able to register correctly with my email only.

bamarni commented 12 years ago

@GTheron : It's just some naming, why don't you simply use the email as username?

Baachi commented 12 years ago

You should not extend your user class from the FOS\UserBundle\Entity\Userbut you should implement the FOS\UserBundle\Model\UserInterface instead.

stof commented 12 years ago

@Baachi why ?

Baachi commented 12 years ago

@stof If you extend from FOS\UserBundle\Entity\User you have a empty column named username. To avoid this, you should implement the UserInterface instead. :)

stof commented 12 years ago

@Baachi but you have to do all the mapping yourself. It is not always worth it

Baachi commented 12 years ago

Yes, i know. That sucks, but to create a column that always empty is very dirty.

GTheron commented 12 years ago

@bamarni If a user wants to change their email, the username one remains valid for login, which can be seen as a problem in some situations, as the username cannot (or at least should not) be changed, which is why it seemed good to have a uid in the username. Note that it can always be used in "emergency" situations.

advancingu commented 12 years ago

From a general viewpoint, when you think about it, asking users to come up with a unique username when their email address already satisfies the very same uniqueness constraint is really not that great a thing to do in many cases. It would therefore be great to have support for "email usernames" added to this bundle.

As for my use case, the general concept of what GTheron wrote above (assigning a unique internal ID as a username) appears to be a good approach for the time being, with the difference that I would get my IDs from a non-random source.

advancingu commented 12 years ago

Fyi, for those who want to set their programmatic username only after registration form validation, it will be necessary to create and apply a custom validation_groups configuration, completely replacing the default Registration validation group configured for registration forms. In this new validation group, the only username field value allowed must be null. All the other validation settings can be copied over.

The issue is that it doesn't seem to be possible to simply override only the username validation in a new validation group and add that group behind the existing one, e.g. validation_groups: [Registration, RegistrationNoUsername]. It seems Registration always has precedence.

GTheron commented 12 years ago

@advancingu As a side note, the unique_id is not actually random, as it's based on a unique identifier corresponding to the machine time on which it's produced, therefore it cannot appear twice.

advancingu commented 12 years ago

@GTheron You're right that unique_id is not random as I said, however I would assume that if you run your application on more than one server in parallel you might still get duplicates with unique_id. Of course, chances are small but they're greater than zero.

nurikabe commented 12 years ago

I agree with @advancingu. We're upgrading to sf2.1 right now and revisiting our FOSUserBundle integration. The dual username/email requirement is the only thing that requires a non-obvious workaround. It would be nice if "email as username" was baked-in as a feature.

seltzlab commented 11 years ago

@nurikabe me too, vote for the new feature

NinjDS commented 11 years ago

Wouldn't it be better to simply make the "main login fields" fully configurable?

FOSUserBundle is aiming at flexibility. Should it simply not assume anything about the role of fields, except password and some internal needs? There would be defaults, easily configurable by some yaml pairs:

fields:
  login: [email, company_registration_number]
  non_editable: [email, surname, firstname]

@stof: i tried to find the answer to the following question on GIT and stackoverflow, but couldn't: can we expect FOSUserBundle to at least make the login field configurable some day? Or isn't that planned for the moment? Thank you :)

Baachi commented 11 years ago

@NinjDS Stof started a PR for FOSUserBundle 2.

And i think your idea is out of the scope from FOSUserBundle.

NinjDS commented 11 years ago

Indeed, i saw the PR some minute later. But why do you think it's not the purpose of FOSUserBundle? It's not named "FOSUsernameBundle" :)

Anyway, you may be right because i start to realize that FOSUserBundle is not what i need. It's very specific, aimed at particular cases (which fits in 90% of the cases i agree). But as soon as your user management is not too common, it's probably better to write it by yourself rather than overwrite 50% of the bundle's code.

In my case i need minimalistic user structure all drive by the email, and to which one or more "shop" entities ar attached, as a part of the user account. It requires me to tweak all FOSUserBundle form types and validations to deal with the username trick, and override the process methods everywhere to manage the "shop" links.

I'm not yet too confortable with this bundle so i don't see the whole job it caries. In other words, i don't see how much code i'll have to write if i want my own user management bundle to replace it taking my specifications into account, but i feel like it will be a better solution to go with.

stof commented 11 years ago

@NinjDS configuring form fields in YAML is out of the scope of the bundle. For the edit form, we are using the Form component, and we are making the form type configurable. It allows to do approximately anything in the edit form, which is far more flexible than your non_editable suggestion.

NinjDS commented 11 years ago

@stof Hi, You are right, the way it's done makes it very configurable but not flexible. Flexible would mean that no assumption is made about what a user account is. It's the opposite here, starting with the fact that a user profile is defined primarly by a username and integrates an e-mail. I'm not telling FOSUB is bad or anything, no, really, it's great i'm sure, when your needs gets in its way. Not my case i'm afraid ;)

spolischook commented 11 years ago

Hey guys, if you afraid that unique_id is not unique than you must use sluggable!

kingcrunch commented 11 years ago

What I always thought about this: Why not removing setUsername(), getUsername(), setUsernameCanonical() and getUsernameCanonical() complety and replace it with a single getIdentity()? The intention is, that the one, who wants to use the email as login/identifier can just return it, as it is

public function getIdentity()
{
  return $this->getEmailCanonical();
}

I think nowadays this is more common, than a username. But those, who wants to use usernames instead, shouldn't have any problems adding this instead. And when somebody wants to use the real name of the user instead, it is easily usable too :) (Isn't useful because they are usually not unique, but it is possible).

What I want to say: Separating the login-name from the username and/or email would make the UserInterface more flexible against chances, what the login-name should be.

Any suggestions?

ghost commented 11 years ago

i think UserInterface itself should be changed as you suggested @KingCrunch , but in symfony not just for FOSUserBundle

kingcrunch commented 11 years ago

Ah, havent thought this far... Right, getUsername() is part of \Symfony\Component\Security\Core\User\UserInterface ... Well, I thik for a change of a Symfony-Core interface it is too late (2.3 is out). In this case I would treat getUsername() like the getIdentity() I mentioned before and just remove the setters and getUsernameCanonical().

sfblaauw commented 11 years ago

You can create your own custom provider:

# app/config/security.yml
providers:
    fos_userbundle:
        id: my_custom.user_provider.email

# UserBundle/Resources/config/services.yml
services:
    my_custom.user_provider.email:
        class: UserBundle\Security\EmailProvider
        public: false
        arguments: [@fos_user.user_manager]

# UserBundle\Security\EmailProvider
namespace UserBundle\Security;

use FOS\UserBundle\Security\UserProvider;

class EmailProvider extends UserProvider
{
    protected function findUser($username)
    {
        return $this->userManager->findUserByEmail($username);
    }
}
PeterWooster commented 11 years ago

This works out quite simply in FOSUserBundle V2.0, first set the UserProvider to allow username or email:

    providers:
    fos_userbundle:
        id: fos_user.user_provider.username_email

Then add an EventListener for Registration Initialization

    public static function getSubscribedEvents()
    {
        return array(
            FOSUserEvents::REGISTRATION_INITIALIZE => 'onRegistrationInit',
        );
    }

/**
 * take action when registration is initialized
 * set the username to a unique id
 * @param \FOS\UserBundle\Event\FormEvent $event
 */    
    public function onRegistrationInit(UserEvent $userevent)
    {
        $user = $userevent->getUser();
        $user->setUsername(uniqid());
    }

And override forms and templates to get rid of references to the user name.

peterjmit commented 11 years ago

Just to add my two cents (sorry if this has already been discussed), the bare minimum to register a user in any online application is an email address and a password. There are a considerable number of applications where a username is not appropriate.

From a UX perspective (especially in commerce) usernames are difficult for users to remember, whereas an email address is arguably easier to remember.

As it stands to achieve what I think is a better practice requires work-around on the bundle's defaults, and leaves us with an unused DB column (a minor issue for the majority of small projects, but still not ideal).

I think that usernames should be an optional extra on the bundle rather than the default, and the bundle would be a lot more useful if that were the case.

The suggestion from @KingCrunch could be a good approach, because the bare minimum (i.e. a means of confirming an account and password resetting) for account registration in a web app could easily replace email for a cellphone number for example.

kingcrunch commented 11 years ago

I remembered this issue and I thought about a PR, but first: This would obviously to a BC-break. 2.x is currently in dev, so from the versioning point of view there is no problem. However, this bundle in version 2 seems to be quite widespreaded, so it may (actually I have no idea) affect many users. But again: It is a dev-version :confused:

I would remove all "username"-related properties and setters/getters and redirect getUsername() to getEmailCanonical() like mentioned above 2 months earlier.

public function getUsername()
{
    return $this->getEmailCanonical();
}

What remains: Should I also provide an additional User-class with username, to make it easier for those, who want to keep it? Or maybe a trait? :wink:

What is the opinion of the maintainers?

nchervyakov commented 10 years ago

I added my method to remove the username to gist: https://gist.github.com/nchervyakov/c013aa2eaf4da72150f6

MickL commented 10 years ago

Like @PeterWooster said, this works out of the box. But it took me a while because im new to Symonfy. So here is the solution:

You have to write a event listener like described in the docs.

First, enable login with email OR username, provided by FOSUserBundle as described here:

# app/config/security.yml
security:
    providers:
        fos_userbundle:
            id: fos_user.user_provider.username_email

Now create the event listener as described here:

// src/Acme/UserBundle/EventListener/UserRegistrationListener.php
<?php

namespace Acme\UserBundle\EventListener;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use FOS\UserBundle\Event\UserEvent;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;

class UserRegistrationListener implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return array(
            FOSUserEvents::REGISTRATION_INITIALIZE => 'onRegistrationInit',
        );
    }

    /**
     * take action when registration is initialized
     * set the username to a unique id
     * @param \FOS\UserBundle\Event\FormEvent $event
     */
    public function onRegistrationInit(UserEvent $userevent)
    {
        $user = $userevent->getUser();
        $user->setUsername(uniqid());
    }
}

Finally, register it as a service:

// src/Acme/UserBundle/Resources/config/services.yml
services:
    acme_user_registration_listener:
        class: Acme\UserBundle\EventListener\UserRegistrationListener
        tags:
            - { name: kernel.event_subscriber }
acidjames commented 10 years ago

@MickL thanks for this workaround, if you could spare some time to explain how you override templates and forms to remove reference to the "username" field it would be great for beginner users ! :)

phillipsnick commented 10 years ago

@MickL what you have written there looks perfect but from what I can tell events have either been removed or moved from version 1.3 onwards (can't seem to find any reference of this in the change log).

As a quick fix I have added this to my user entity.

    public function setEmail($email)
    {
        if (is_null($this->getUsername())) {
            $this->setUsername(uniqid());
        }

        return parent::setEmail($email);
    }
merk commented 10 years ago

Events are present in the 2.0-dev/master branch which currently has a beta tag as its most stable tag.

MickL commented 10 years ago

Yes I just downloaded the master branch which is recommended for Symfony 2.1+. Also check out the Documentation. The navigation is (unfortunately) at the very bottom: https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/index.md

MickL commented 10 years ago

P.S. Would be awesome to see an easy configuration-line to enable login with another field than username without any additional work.

merk commented 10 years ago

It isnt possible to provide a configuration option. Setting a listener as suggested above is a suitable solution - though I wouldnt use the username_email provider. For the systems where we use email-only, we set the username to be the same as the email address.

ThomasBerends commented 10 years ago

I solved this problem by copying the setEmail() function to my own entity, and adding '$this->username = $email'.

Would there be any problems with my implementation?

MickL commented 10 years ago

The username should be unique. The email can be changed by the user. This is why the event listener sets an unique id to the username.

phillipsnick commented 10 years ago

Unfortunately I can't use 2.0-dev as I'm using SonataUserBundle which requires 1.3.x so am kind of stuck without events.

kingcrunch commented 10 years ago

@phillipsnick You don't need to use SonataUserBundle when you want to use Sonata.

phillipsnick commented 10 years ago

@KingCrunch but if you want to use the SonataNewsBundle then you need to use SonataUserBundle.

I have found some hacky ways around for now, I guess SonataUserBundle isn't using FOSUserBundle 2.0 as it isn't a final release yet? Seems I'm not the only one having issues https://github.com/sonata-project/SonataUserBundle/issues/322 and https://github.com/sonata-project/SonataUserBundle/issues/348

Overall I'm happy to use FOSUserBundle 1.3 but just curious as to why events have been removed? Can't find the relevant commits/change log/issue which supports this.

maxstekel commented 10 years ago

@acidjames Good question, so to complete this 'away with the username field solution' you should follow MickL's changes and the following files:

//Acme/UserBundle/Form/Type/RegistrationFormType.php

namespace Acme\UserBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;

class RegistrationFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // add your custom field
        $builder->remove('username');

    }

    public function getParent()
    {
        return 'fos_user_registration';
    }

    public function getName()
    {
        return 'acme_user_registration';
    }
}
//app/config/config.php

// ...

fos_user:
    // ...
    registration:
        form:
            type: acme_user_registration
ryanhalliday commented 9 years ago

@maxstekel, for this you also need to add the form type to services.yml as shown in overriding_forms.md

# src/Acme/UserBundle/Resources/config/services.yml
services:
    acme_user.registration.form.type:
        class: Acme\UserBundle\Form\Type\RegistrationFormType
        tags:
            - { name: form.type, alias: acme_user_registration }
bsaverino commented 9 years ago

Is there anything wrong with overriding the User like this? I know there is redundant info in the database but this should take care of all other situations correct?

public function setEmail($email){
    $this->email = $email;
    $this->username = $email;
}

public function setEmailCanonical($emailCanonical){
    $this->emailCanonical = $emailCanonical;
    $this->usernameCanonical = $emailCanonical;
}
merk commented 9 years ago

We did this in one application but it ends up taking up 4 times as much space which can add up. We ended up implementing FOSUserBundle's UserInterface and manually mapped the fields we required instead.

bsaverino commented 9 years ago

I suppose it is 2 more fields than normal however 4 times as much space is shocking due to the nature of the data. I'll keep that in mind, thanks for your input.

merk commented 9 years ago

Sorry, you're right - its only twice as much data, not 4 times as much - given the bundle stores a canonical representation of the email address already.

heyalbert commented 9 years ago

@MickL Is it possible to make a complete explanation what you did? For example the missing pieces. That would be very great! So that we have a stable solution. Thank you. :-)