Buy
Buy

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Go back to the HTML form: it has one other field that we haven't talked about yet: the "remember me" checkbox:

... lines 1 - 10
{% block body %}
<form class="form-signin" method="post">
... lines 13 - 26
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> Remember me
</label>
</div>
... lines 32 - 34
</form>
{% endblock %}

You could check & uncheck this to your heart's delight: that works great. But... checking it does... nothing. No worries: making this actually work is super easy - just two steps.

First, make sure that your checkbox has no value and that its name is _remember_me:

... lines 1 - 10
{% block body %}
<form class="form-signin" method="post">
... lines 13 - 26
<div class="checkbox mb-3">
<label>
<input type="checkbox" name="_remember_me"> Remember me
</label>
</div>
... lines 32 - 34
</form>
{% endblock %}

That's the magic name that Symfony will look for. Second, in security.yaml, under your firewall, add a new remember_me section. Add two other keys below this. The first is required: secret set to %kernel.secret%:

security:
... lines 2 - 8
firewalls:
... lines 10 - 12
main:
... lines 14 - 22
remember_me:
secret: '%kernel.secret%'
... lines 25 - 40

Second, lifetime set to 2592000, which is 30 days in seconds:

security:
... lines 2 - 8
firewalls:
... lines 10 - 12
main:
... lines 14 - 22
remember_me:
secret: '%kernel.secret%'
lifetime: 2592000 # 30 days in seconds
... lines 26 - 40

This option is... optional - it defaults to one year.

More about Parameters

As soon as you add this key, if the user checks a checkbox whose name is _remember_me, then a "remember me" cookie will be instantly set and used to log in the user if their session expires. This secret option is a cryptographic secret that's used to sign the data in that cookie. If you ever need a cryptographic secret, Symfony has a parameter called kernel.secret. Remember: anything surrounded by percent signs is a parameter. We never created this parameter directly: this is one of those built-in parameters that Symfony always makes available.

To see a list of all of the parameters, don't forget this handy command:

php bin/console debug:container --parameters

The most important ones start with kernel. Check out kernel.secret. Interesting, it's set to %env(APP_SECRET)%. This means that it's set to the environment variable APP_SECRET. That's one of the variables that's configured in our .env file.

Anyways, let's try this out! I'll re-open my inspector and refresh the login page. Go to Application, Cookies. Right now, there is only one: PHPSESSID.

This time, check the "remember me" box and log in. Now we also have a REMEMBERME cookie! And, check this out: I'm logged in as [email protected]. Delete the PHPSESSID - it currently starts with q3 - and refresh. Yes! We are still logged in!

A totally new session was created - with a new id. But even though this new session is empty, the remember me cookie causes us to stay logged in. You can even see that there's a new Token class called RememberMeToken. That's a low-level detail, but, it's a nice way to prove that this just worked.

Next - we've happily existed so far without storing or checking user passwords. Time to change that!

Leave a comment!

  • 2019-02-14 weaverryan

    Hey Carlos Eduardo!

    Cool question! The event you want to listen to is SecurityEvents::INTERACTIVE_LOGIN - you can see it being dispatched from inside the remember me system right here: https://github.com/symfony/...

    However, that event is dispatched whenever you log in... in any way - including Guard authentication and pretty much anything else (https://github.com/symfony/.... So... in some ways... this might be perfect! You could listen to this ONE event and increase the expires_at token. But... in other ways... it's not perfect - because it will be triggered also when your API clients authenticate... which means they would be increasing their *own* expires_at!

    So, your listener function will be passed an instance of InteractiveLoginEvent (https://github.com/symfony/... - and you can use its getAuthenticationToken() method to get the "token" for how the user is authenticating. This is a super low-level object, but it should be a bit different based on the different ways of authenticating (form login vs guard vs remember me) - so you should be able to use it to determine what "type" of login is currently happening, and whether or not you should increase the expires_at.

    Phew! Let me know if that helps!

    Cheers!

  • 2019-02-13 Carlos Eduardo

    Hey!!
    What's the best way to "listen" to the remember me authentication?? I ask this because I need to increase the "expires_at" token for my API clients. Now I only can do this when the user login through the login form.

  • 2018-10-29 weaverryan

    Hey Ahmed EBEN HASSINE!

    I like people who like to know how things work behind the scenes :). Let's look at each question:

    > I want to understand behind the scene how symfony add the REMEMBERME cookie to the cookies

    Answer! https://github.com/symfony/... - and also from its parent class - https://github.com/symfony/... - you need to look at both of these to get the full story.

    Basically, after a successful authentication, Symfony calls loginSuccess() on the "remember me" service, which looks for the _remember_me parameter and, if it exists, sets the cookie. That parameter name is configurable, by the way, and now you can see how it works :).

    > Why should not have a value?

    This question is mostly answered above. If you look at AbstractRememberMeServices::isRememberMeRequested(), you'll see that it's basically checking to see if the box was checked. If I remember correctly, if you give you checkbox a value="", then when it's checked, it will have that value. The remember me class is not expecting this.

    > Is it possible to affect the PHPSESSID lifetime, without adding new cookie called REMBERME ?

    I'm not sure I understand this part of the question. Do you mean that, if someone checks a "remember me" checkbox, that all you really do is increase the PHPSESSID lifetime? Hmm, you could do that I think, but it wouldn't be via the "remember me" official functionality. I believe that PHP only sends the cookie when the session is first created... though, for security purposes, the session is "rebuilt" after logging on... so you could in theory temporarily set session.cookie_lifetime to a higher value... Basically, I'm not too sure if this is possible, at least cleanly :). I haven't dynamically tried tweaking the session expiration before. I wish I could give you more info!

    Cheers!

  • 2018-10-29 weaverryan

    Hey Azeem Michael!

    Ahh! I understand better now. Ok, a few things that I think will help:

    1) As I mentioned earlier, the getUsername() method is used to know what to store in the remember me cookie. And so, what will eventually be passed to loadUserByUsername(). You can make this method return whatever you want. If, ultimately, you just need the API token in order to fetch the user data from the remember me, you could even JUST return the api token. Or, you could return the "hybrid" of email+API token I mentioned before. OR, you could just return the email, and maintain a local database table that stores the API token (or maybe refresh token) for each user. You would then look up the token in that table by using the email.

    2) About this:

    > The Guard API Token authentication example on Symfony docs makes no sense. It seems to assume apps authenticating users via api token would have direct access to the database. Why would someone want to fetch user from database if they have apiToken.

    There are SO many different ways to authenticate - it's complicated :p. That documentation is explaining a slightly different system than your system. If I build an app, it's I may want to allow people to talk to my app's API via some API tokens. But, in reality, I don't have a separate "authentication app" or anything like that. Nope, I have a normal, local database table full of user information. In this model, my API users send me the token, and load the users from the local database, and everyone is happy :). Your model is not so different - it's just that your "database of user information" is actually another API that you call behind the scenes.

    I hope that clarifies a little bit! I definitely understand more about what you're trying to do now :).

    Cheers!

  • 2018-10-27 Ahmed EBEN HASSINE

    Hey Thank you for the tuto <3

    1) '_remember_me' : The magic name that symfony looks for. Hmm .. I want to understand behind the scene how symfony add the REMEMBERME cookie to the cookies ? Why should not have a value ? normally checkbox gonna return 0 or 1 as a vlaue .. and how the value of checkbox is handled ?

    2) Is it possible to affect the PHPSESSID lifetime, without adding new cookie called REMBERME ?

  • 2018-10-23 Azeem Michael

    weaverryan
    I'm using remember me on the client side. Trying to login user via external api that takes POST request with username/password and returns a token. Looks like B might be the way to go. I guess I can just return apiToken in getUsername and fetch new user via apiToken. The Guard API Token authentication example on Symfony docs makes no sense. It seems to assume apps authenticating users via api token would have direct access to the database. Why would someone want to fetch user from database if they have apiToken. Wouldn't they want to make a new POST request to the external API and fetch the user with apiToken?

  • 2018-10-22 weaverryan

    Hey Azeem Michael!

    That's right! That's exactly how Remember me knows how to load your users - good find! Question: why do you want to use remember me functionality in an API situation? But anyways, let me give you some more information:

    A) In loadUserByUsername(), as you know, you need to return a User object. And yes, by default, you are only passed the username (well, email in your case) to this method. Actually, the remember me token calls $user->getUsername() in order to know what to store in the remember me token and, ultimately, what to pass to the loadUserByUsername() method.

    B) Because of the last fact above, you could, for example, update your getUsername() method to return more information - e.g. the email AND API token as a string - e.g. [email protected]:123abcAPI_TOKEN456def. That's a bit of weird solution, but there's no problem with this - the getUsername() method is just used by the remember me system and to display the logged in user on your web debug toolbar.

    Let me know if this helps!

    Cheers!

  • 2018-10-19 Azeem Michael

    Thanks weaverryan for the response. It looks like UserProvider::loadUserByUsername($username) is the problem here. Remember me seems to call this function to fetch new user. Which calls GET /users?filter={$email} resource on the API. However, This is a public API resource and only returns public info on user. I can't return apiToken from there. I don't see any clean way to fetch new user with refreshToken or username/password here. As, only arg passed to this function is $username.

  • 2018-10-19 weaverryan

    Hey Azeem Michael!

    Nice to hear from you again :). I *may* have a simple answer for you: after you set the apiToken on your User object, make sure you save it to the database. Here is what I think is happening when you set the apiToken

    1) You are creating the apiToken and setting it on the user
    2) At the end of that request, the User object is serialized to the session
    3) When you delete the PHPSESSID, it forces the "remember me" token to query for a fresh User object from the database. And, if you never saved the apiToken, it will be null.

    Actually, the normal behavior (even without remember me) is to query for a fresh User object at the beginning of every request. So, you should actually see this behavior in all situations. So, I'm not sure why you would only see this when deleting the session cookie... so there may be something else going on here.

    Cheers!

  • 2018-10-18 Azeem Michael

    How is the user refreshed after we delete the PHPSESSID? In my LoginFormAuthenticator::getUser($credentials, $userProvider) I am making a POST request to my api to get $apiToken using $credentials['username'] and $credentials['password']. Then I make a POST request to fetch user data using the $apiToken. I then create a user object with user data and apiToken and return the user object. In my Controller if I dd($this->getUser()). I do get correct user object with $user->apiToken set. However, when I delete the PHPSESSID and refresh, my $user->apiToken prints null.