Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

The Mysterious "User Provider"

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

Let's see that error again: change intercept_redirects back to false:

... lines 1 - 12
web_profiler:
... line 14
intercept_redirects: false
... lines 16 - 49

Refresh and re-post the form. Oof, there it is again:

There is no user provider for user AppBundle\Entity\User.

What the heck is a user provider and why do we need one?

What is a User Provider?

A user provider is one of the most misunderstood parts of Symfony's security. It's an object that does just a few small jobs for you. For example, the user provider is responsible for loading the User from the session and making sure that it's up to date. In Doctrine, we'll want our's to re-query for a fresh User object to make sure all the data is still up-to-date.

The user provider is also responsible for a few other minor things, like handling "remember me" functionality and a really cool feature we'll talk about later called "impersonation".

Long story short: you need a user provider, but it's not all that important. And if you're using Doctrine, it's super easy to setup.

Setting up the Entity User Provider

In security.yml, you already have a providers section - as in "user providers". Delete the in_memory stuff and replace it with our_users: that's a totally meaningless machine name - it could be anything. But below that, say entity and set it to { class: AppBundle\Entity\User, property: email }:

... lines 1 - 2
security:
# http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
providers:
our_users:
entity: { class: AppBundle\Entity\User, property: email }
... lines 9 - 28

The property part is not something we care about right now, but we will use and talk about it later.

But yea, that's it! Go back to /login. Right now, I am not logged in. But try logging in again.

It's alive!!! We can finally surf around the site and stay logged in. Cool.

Custom User Provider

In your app, if you're not loading users from the database, then you'll need to create a custom user provider class that implements UserProviderInterface. Check out the official docs in this case. But if you have any questions, let me know.

Leave a comment!

6
Login or Register to join the conversation
Michael V. Avatar
Michael V. Avatar Michael V. | posted 4 years ago

Yo SfCasts team,

I have been learning a lot, and so far things have been going easy, but now i'm stuck/puzzled.

I made a login form, made the authenticator, and after login, I am properly authenticated and authorized with a GuardToken.
Then the redirect happens, and I still seem to be authorized, have roles, but Authenticated is "false".
Any other action after that wipes out my login and redirects me to "/".

To give to further details, the solution i'm trying to get to is a retrofit. Before Drupal did the authorization (their login form), but now we want to do it ourselves. We have a full-fledged User class, it's called 'Account', but implements UserInterface (also Serializable btw).
And we already had an entity provider in security.yml.

I'm not sure where it's going wrong, i'm thinking the serializer was not sending the correct stuff, but when removing the custom serialize (and un), after login I'm Authenticated, but my user is now unknown (blank)? Do I need a custom user provider?

Hopefully you can help, before I figure it out myself ;-)

Source: https://github.com/griidc/p...

Reply

Yo @Mickel!

Super glad to hear you’re learning a lot :). Let’s see if we can figure this issue out!

It certainly seems like a serialization issue. I noticed, at this moment, that your serialize and unserialize methods in Account are blank - I’m guessing just as part of debugging. Have you tried removing these and the Serializable interface? It’s usually not needed, and can mess things up.

Also, for debugging, I’ll point you to a class at the core of Symfony - AbstractToken https://github.com/symfony/... - it has a method called hasUserChanged(). This is called at the beginning of each request, once you’re authenticated. My guess is that this is returning false, and my guess is that a serialization problem is the issue.

Let me know what you find out!

Reply
Michael V. Avatar
Michael V. Avatar Michael V. | weaverryan | posted 4 years ago | edited

Hi weaverryan,

I tried and removed the Serializer, and some other combination. But the Guardtoken kept losing it's user, or in some cases gets wiped out completely.

So I went a bit backward, "sometimes you have to go backwards to go forwards" (Wolly Winka), and used the form_login instead (commented out guard), and created a simple PasswordEncoder. That seems to work fine, it's not as cool or flexible, but ok for now.

I would still like to use Guard, but I am unsure why the GuardToken is not working for me.

Thanks for you help, I will still try to get this to work, but this will get us passed one hurdle.

Reply

Hey Michael V.!

Interesting! So, you had success with form_login... and you made NO other changes to your User (Account) class? That IS weird. What version of PHP are you using? If you're using 7.3, there's a small chance you're getting hit by a bug (which was fixed in Symfony 3.4.22). But let me know.

One other thing to try - which would help rule out what is going on, would be to implement the EquatableInterface on your Account class. This will require you to have an isEqualTo(UserInterface $user) method. Just return true; from that. If that fixes your problem, then I'll know what the cause is. If not, then I'll know what the cause is NOT ;).

Cheers!

Reply
Michael V. Avatar
Michael V. Avatar Michael V. | weaverryan | posted 4 years ago | edited

Hi weaverryan, I think you DO know what the cause is.

For some magical reason, implementing the EquatableInterface WORKED! Whaaaat! Yes whats up with that?

FYI, currently using PHP 7.1.26 and Symfony 3.4.22. Also, have not changed the Serializer implementation (that wasn't it I guess).

Well, thanks! Just as I was putting my eggs into the form_login basket, now have to put them back into the Guard basket. But now I know how to do it both ways, just have to read up more on Guard. ;-)

Thanks for coming thru...

Reply

Hey Michael V.!

Ok, we got it! Here is the long explanation, as you are far from the first person to be bit by this: https://symfonycasts.com/sc...

The fix usually is just to remove the custom Serializable stuff from your entity - you almost never need this. That should fix the problem. There are some security implications with just returning true from isEqualTo(). The idea of that method is that Symfony passes you the User object stored in the session and the fresh User object from the database, and you're supposed to decide if they have changed enough to log the user out (return false to log the user out). The idea would be that, if you change your password, you might want all other systems that are currently logged in as you to be logged out. In that example, if you compared the hashed password field on the 2 users and they were not the same, you would return false from isEqualTo() to log them out. But again, just removing the serialization stuff (Serializable) solves the issue and you don't need to have an isEqualTo() method at all.

By the way, this issue is not special to Guard - it's actually just that Guard sorta handles it "more properly" than form_login, so you're noticing much more easily that, currently, it appears that your user is alway changing.

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.1.*", // v3.1.4
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.4
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // 2.11.1
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
        "doctrine/doctrine-migrations-bundle": "^1.1" // 1.1.1
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.7
        "symfony/phpunit-bridge": "^3.0", // v3.1.3
        "nelmio/alice": "^2.1", // 2.1.4
        "doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
    }
}
userVoice