Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Symfony Security: Beautiful Authentication, Powerful Authorization

3:34:13

What you'll be learning

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
        "knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
        "knplabs/knp-time-bundle": "^1.8", // 1.8.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.2.0
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.1.4
        "symfony/console": "^4.0", // v4.1.4
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/framework-bundle": "^4.0", // v4.1.4
        "symfony/lts": "^4@dev", // dev-master
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/security-bundle": "^4.0", // v4.1.4
        "symfony/serializer-pack": "^1.0", // v1.0.1
        "symfony/twig-bundle": "^4.0", // v4.1.4
        "symfony/web-server-bundle": "^4.0", // v4.1.4
        "symfony/yaml": "^4.0", // v4.1.4
        "twig/extensions": "^1.5" // v1.5.2
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.7
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/debug-bundle": "^3.3|^4.0", // v4.1.4
        "symfony/dotenv": "^4.0", // v4.1.4
        "symfony/maker-bundle": "^1.0", // v1.7.0
        "symfony/monolog-bundle": "^3.0", // v3.3.0
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.1.4
        "symfony/profiler-pack": "^1.0", // v1.0.3
        "symfony/var-dumper": "^3.3|^4.0" // v4.1.4
    }
}

Oh no, it's time to add security! Ahhh!

Wait, come back! Security in Symfony is awesome! Seriously, between things called "voters" and the Guard authentication system, you can do anything you want inside of Symfony, and the code to do it is simple and expressive.

Security has two sides: authentication (who are you?) and authorization (do you have access to do X). We'll talk about each of these, creating an traditional form login system and and API token authentication. Then, we'll turn to authorization, with roles, voters and other good stuff:

  • Making a User with the fancy new make:user command (ooOOOoo)
  • Security & Firewall Fundamentals
  • Creating a custom login form
  • CSRF protection
  • API token authentication system
  • All about Guard authentication
  • User Providers (why you need them, but don't care)
  • Password Encryption
  • Logging out!
  • Protecting entire URLs with access_control(s)
  • IS_AUTHENTICATED_FULLY, IS_AUTHENTICATED_REMEMBERED
  • Checking access with roles! ROLE_USER
  • Denying access in a controller
  • What are voters?
  • Role hierarchies
  • Impersonation (switch_user)
  • Automatic Login (after Registration)

... and how to create a back door into your... spaceship... that will allow it to be destroyed with one careful shot. Just kidding! Let's make some secure sites / spaceships!


Your Guides

Ryan Weaver

Buy Access

Join the Conversation?

71
Login or Register to join the conversation

I will be waiting for that.

4 Reply
Kribo Avatar

sounds good and is long over due... cannot wait to get started.

3 Reply

I agree! I'm finishing the React tutorial this week - then straight to security! Weeeee!!!!

1 Reply
Default user avatar

Hey, wrote a letter on your mail (through Contact Us section), still no answer (sir.****@y*.**)

Reply

Hey Vadim,

We're sorry, it were weekends. Just answered both your emails. Thank you for your patience!

Cheers!

1 Reply
Jimmy S. Avatar

Yes when will this one be completed?

Reply
Fabien P. Avatar

Hey Jimmy,

We completely released ReactJS tutorial finally, but now we have an intermediate small one - Contributing to Symfony: https://knpuniversity.com/s... . So the next should be this one. Thank you for your patience!

Cheers!

Reply
Mike P. Avatar

I see that there are still react tutorial published, is there a new ETA? Cant wait to see this new high quality tutorial from you Ryan!

Reply

Hey Mike!

Here's our upcoming tutorials list: https://knpuniversity.com/c... - as you can see this one should be next for now. We need about 2-3 weeks to completely release ReactJS course, and, in the best case, I think this tutorial will be started releasing in a month. But that's just a wild guess for now, we'll have more precise dates after ReactJS. Thank you for your patience!

Cheers!

1 Reply
ovidiu Avatar

Hello, I just finished this course and the previous ones about s4 and I wanted to ask a few questions if you would kindly reply. Coming from a non-framework background, I still struggle a bit wrapping my head around Symfony. So here are my questions:

1. When we built the login form, we chose an entry point for where the form is located and how to respond on the submit. The problem is that you log normal users and admin with the same form. How would I go about building multiple login forms? I need a login form for the normal users(clients) and another one for the admins, basically the urls are /profile/login and /admin/login

2. I am a bit confused about when I need to pass arguments in the __construct method of a class and when I don't need to. What I mean is, sometimes you just type hint an object in the method declaration and have it available, but other times you passed an object via the __construct method. Is there like a rule where I know what is available to me and what is not so that I need to inject it via the __construct ? Because I only worked with normal php, I always instantiate a class with new ClassName() and then inject it to other classes when needed.

3. Finally, how would you recommend using third-parties libraries that have nothing to do with symfony? For example, I often use http://image.intervention.io/ for image manipulation when I need to upload and resize images. How exactly would I use this library in my controller which handles the upload form, via __construct or there is a better way?

Thank you again for these amazing courses and I apologize if my questions are too overwhelming.

1 Reply
Mouad E. Avatar
Mouad E. Avatar Mouad E. | posted 4 years ago

will be a great tutorial, we are waiting :D

1 Reply
Default user avatar
Default user avatar Arek Mateusiak | posted 4 years ago

can't wait for this one :-) when it will be available? :-)

1 Reply

Hey Arek,

We're going to start releasing ReactJS tutorial this week, and then this one will be next I think. Thanks for your patience.

You can track upcoming screencasts on this page: https://knpuniversity.com/c...

Cheers!

1 Reply
Ruslan Avatar

Hi, Tell me please, Are there many diference between 4.x 5.3 SF versions? Can I study authentication and authtorsation by this tutorial, or better to start from "API Platform Part 2: Security" ?
Thank you.

Reply

Hey Ruslan

In Symfony 5.1 they introduced a new security system, which is just simpler than the current one, so, if you already understand how this "old" security system works, you'll understand pretty quickly how the new one works. I leave you here a blog related to it https://symfony.com/blog/ne...

About the ApiPlatform security, well, that tutorial is focused on how to enable/add security to your API built with ApiPlatform, although it uses Symfony security under the hood.

Cheers!

Reply
Ruslan Avatar

Hi Diego Aguiar ,
Thank you for clarification.
Today many people use php (and Synfony) for back ground only. But I prefer to use total MVC and too :) .
Twig and related security settings,commands interesting for me.

1 Reply

That's nice, I believe you may enjoy watching our tutorial about Symfony Messenger https://symfonycasts.com/sc...

Cheers!

Reply
Default user avatar

Inside Symfony profiler/Request/Response/Session/Attributes/_security_main we have this text format i.e.:
... \x00App\Entity\User\x00firstname";s:4:"John";s:25:"\x00 ...

I'm trying to get the users firstname to display in twig when user is logged on. I wonder if I can get it from here ? Or i need to set it up manually in the session ?

Reply

hey Wuwu,

Look at Lulu tip here: https://symfonycasts.com/sc... - you don't want to extract that raw info from the session, better use "app" global Twig var that gives you access to the current user and other useful objects like request, etc.

Cheers!

Reply
Default user avatar

Dear wuwu,

you can use twig global object User as {{ app.user.firstname }}

Reply

Hey Lulu,

Thank for this tip!

Cheers!

Reply

Hello!

I am not sure this is the right place to ask this question but as it's about secure access, I think this is not the worst place :) One of my customer sent me a bundle of files that should be accessed in a secure way (user must be at least logged in). This bundle is composed by html files (the starting point is an html files), javascript and css. All files must be placed in the same directory as they are links between them. This bundle has been generated by my customer with this tool: https://articulate.com/360/.... So I can not change the generated code (otherwise each time my customer will make a modification, I will have to redo my "adaptations". If I copy the files in a subfolder of the public folder, I can access them and it's working ... but it's completely open to everybody. Is there so a way in Symfony to "serve" html files in a protected way? Btw I am using Symfony 5.

Thx!

Reply

Hey Lydie

What you could do is to put all the bundle files in a non-accessible web directory (in other words, out of the public directory in Symfony), and then, code a controller's action (an endpoint), so it can check if there is a logged in user before serving any files. It's like a proxy, you first have to hit "some" route in order to access a file

Cheers!

Reply

Thx Diego Aguiar .

I have also thought about this solution but the issue for me is how to "redirect" to the entry point of the bundle? Most of my controllers render a twig template or return a JsonResponse for ajax request. How to return an html file?

Thx!

Reply

Ah ok, I understand. It depends on what you want to do. Do you want to download the file, show it as plain text, or render it?

Reply

render it :) it's an e-learning application done with html, javascript, css and video files.

Reply

Have you tried to render it as you would do with any other template? I think it should just work if you render it with Twig, unless it contains PHP code inside it

Reply

yep but it did not work due to the urls included in this html files (even if all the files were in the same directory).

Ex: the. starting html file contains :

<script src="story_content/user.js" type="text/javascript"></script>

This can not be found as symfony will look into the public folder to find this file.

Reply

Ohh, that's a problem indeed. The assets that your files depend on have to live in a public directory because they will be downloaded by the user's browser (unless you inline all the content in a single file). You may want to move all those assets to a public folder so they can be accessible

Reply
Jose carlos C. Avatar
Jose carlos C. Avatar Jose carlos C. | posted 2 years ago

Hello weaverryan ,
I'm looking for SymfonyCasts the way to do it correctly when a session ends show a modal, either a login form or a few buttons to stay logged in or log out.
Is there any of this or any thought of making a video?

Reply

Hey CharlES

So, you want to inform the user that his session has expired due to non-activity? Or what you mean by "When a session ends"?

Cheers!

Reply
Jose carlos C. Avatar
Jose carlos C. Avatar Jose carlos C. | MolloKhan | posted 2 years ago

Hey Diego Aguiar , when it has expired.

Reply

Ah I get it. What I've seen other sites doing is that they ping the server every minute (or maybe less) via javascript. So, they can know if the session already ended and if so, you execute your logic. Does it makes any sense to you?

1 Reply
Jose carlos C. Avatar
Jose carlos C. Avatar Jose carlos C. | MolloKhan | posted 2 years ago

I'm sorry for the delay, it was what I was planning to do, but I asked if there was any way less hard, but well you have already confirmed it to me, so I will do it that way. Thank so much

Reply
Khaled A. Avatar
Khaled A. Avatar Khaled A. | posted 2 years ago

hello
how can I login with facebook or google in Symfony?
thank you

Reply

Hey khaled Sig

I recommend you to use this bundle https://github.com/hwi/HWIO...

Cheers!

Reply

Is it possible to get security debug information that merges annotations and security.yaml? maybe a console command that show all controller actions and their associated rules? or maybe by endpoint?

Reply

Yo Skylar Scotlynn Gutman !

Nothing exists like that currently - it's actually really cool idea though, however security rules are added in some many ways, it's hard to make a solution that would work for everyone. I *have* seen a company do this in the past who used security annotations for everything - they wrote their own console command. It's actually a bit easier than you may think, it involves:

1) Iterating over all of your controller classes (I would probably use Finder to find all files in my Controller directory, then do a little string "playing" to convert that into class names)

2) Inside the loop, use PHP Reflection (http://php.net/manual/en/re... to get all the methods.

3) For each method, it it is public (if it's not public, just "continue"), use the annotations reader (autowireable by type-hinting the argument with Doctrine\Common\Annotations\Reader) and call getMethodAnnotations() to read the annotations off of that method. This will return all of the annotations, including @Route and @IsGranted. You can use this information to figure out which endpoints use which security config.

This really requires very consistent use of annotations for security to work - if you have a bunch of security applied inside the function itself (e.g. $this->denyAccessUnlessGranted()</code),>

Reply
Default user avatar
Default user avatar Waldemar Dell | posted 3 years ago

Hello. IS authentication Made via guard? Because there are Changes in SF 4.2. at SimplePreAuthenticatorInterface. Thanks in advance

Reply

Hey Waldemar Dell!

Yes, in this tutorial, we do all authentication via Guard. The SimplePreAuthenticatorInterface was deprecate in Symfony 4.2 because it basically does the same thing as Guard, but Guard is a bit easier to use.

Cheers!

1 Reply
Default user avatar
Default user avatar Waldemar Dell | weaverryan | posted 3 years ago

Thank you very much. That's the answera I needed.Wow, I got an answer from Ryan, I feel so good. Love your Tutorials. I watched nearly everything about symfony 3.3. IT was great.

Reply
Justin V. Avatar
Justin V. Avatar Justin V. | posted 3 years ago

Hello and thank you for the incredible tutorials!

Is there a way to implement a two-part authentication process? For example, after username and password are authenticated, some users would be logged in while others could be directed to a second login page to complete their login by providing, perhaps, a security question answer or two factor code?

With very little Symfony experience so far I was hoping there was a "correct" way that I am just not aware of. My thought was to setup username/password authentication as described in this tutorial. Upon success, if logic determines another step is required, set a session variable. Then create another guard that looks for that session variable and prompt the user for the second part of the login process. I'm just not sure how to go about having this guard use a different page/form and how to restrict users from doing anything else on the site until that second part of the login is complete.

Thanks again for the tutorials!

Reply

Hey Justin!

Actually, you nailed it - nice work! Ok, back story: Symfony doesn’t natively handle two factor auth. It’s not that it can’t - but no official solution has been set forward. I’ve never had to implement it, but I’ve been telling people for years to do exactly what you’re proposing. Let me see if I can fill in a few of the fuzzy pieces:

1) in the first authenticator, once you determine that authentican is successful (you found their User), you cannot return the User from getUser(). That would cause auth to be successful, which is actually not what you want. You’ll need to create a custom Exception class and make it implement Synfony’s AuthenticationException. Add a User argument to the constructor, store the user on a property, and add a getUser() method to the exception class. Then, at the bottom of getUser(), instead of returning the user, throw now NowReadyForSecondStepAuthException($user) - or whatever you call it.

2) this will cause onAuthenticationException() to be thrown. If you see a different exception, handle it like normal. But if you see this exception, store the user I’d in the session and redirect to whatever URL will hold your second step of auth.

3) make your second authenticator only return true in supports() if the user is on the correct URL and if that session key exists. Do whatever you need to do here - it’s kind of a normal authenticator, except that it’s checking for and using that value in the session.

4) and that’s it! You kinda don’t need to prevent the user from navigating away from the 2nd factor auth, because with this method, they are not authenticated at all until they complete step 2. If they navigate away, good for them - they are anonymous still.

So, you can see that it’s kinda straightforward, but the details are tricky. There are people that definitely want to make this easier.

Let’s me know what questions you have - happy to help!

Cheers!

2 Reply
Justin V. Avatar

Awesome! It works perfectly! Quick note to anyone else trying this, the method is onAuthenticationFailure() not onAuthenticationException().

Once I got this working (yay) I realized maybe I didn't fully think through the UX (boo). I would like to try again, this time using AJAX to submit the login form. Basically, show the username/password, submit via AJAX, if the user needs 2FA pass back some JSON indicating the 2FA code is needed, show the 2FA field, submit the login again, this time with all three fields populated. This would also be nicer as it only needs one authenticator. I'm guessing a POST request to the same path would work but I would need to somehow return JSON on failure.

Essentially, the only part I'm unsure about is how, if submitting a form via AJAX, are form/field errors handled? Ideally I would keep the functionality of "wrong username or password" for the initial two fields, but then add some "wrong 2fa code" as necessary. Is there some JS form magic for working with forms/returned errors via AJAX?

Thank you again for the incredible tutorials and assistance!

Reply

Hey Justin Voelker!

Woohoo! Thanks for the update that it worked :).

> I would like to try again, this time using AJAX to submit the login form. Basically, show the username/password, submit via AJAX, if the user needs 2FA pass back some JSON indicating the 2FA code is needed, show the 2FA field, submit the login again, this time with all three fields populated.

Yes, except I would only submit the 2FA code the second time - not the *also* the email & password again. It's just not necessary (you can set something on the session that they have passed this first "step") and it *could* get weird because you'll need to keep the email & password fields somewhere on the DOM so you can re-submit those values. Basically, you CAN do what you're saying, just be aware that you will have the password "floating around" the client side a bit longer. But yea, you could use 1 or 2 authenticators really, no matter what you do. It's just a matter of code organization - so do what feels best.

> Essentially, the only part I'm unsure about is how, if submitting a form via AJAX, are form/field errors handled

By default, if you're extending the AbstractFormLoginAuthenticator, the onAuthenticationException method stores the validation error to the session and redirects you. I think you're already not relying on this, as you've overridden this method. So basically, you would just need to grab the exception error from the AuthenticationException and put it in the JSON you want to return - you can see what the base class does here: https://github.com/symfony/... - what you actually need is $exception->getMessageKey() - that is the string that contains "what went wrong during authentication". Return that in the JSON, then it will be your responsibility to render that onto your form somehow via JavaScript.

Let me know if that makes sense! Sounds like the feature will be awesome.

Cheers!

Reply
Daniel Avatar

Ryan, is this still available solution also for symfony 5? or there exists a native solution for two factor auth?
Thank you!

Reply

Hey neo119!

Excellent question! today I would use: https://github.com/scheb/2fa - it's VERY high quality :).

Cheers!

1 Reply
Daniel Avatar

Hi Ryan,
Thank you for reply and the proposal, but I would like to try the option proposed by you above in order to learn new things, but I have a doubt about the following statement:

. Add a User argument to the constructor, store the user on a property, and add a getUser() method to the exception class. Then, at the bottom of getUser(), instead of returning the user, throw now NowReadyForSecondStepAuthException($user) - or whatever you call it.

The "NowReadyForSecondStepAuthException" is the name of the exception class itself?
I took the following approch:


class NowReadyForSecondStepAuthException extends AuthenticationException
{
/**
* @var UserRepository
*/
private $user;

public function __construct(UserRepository $user)
{
$this->user = $user;
}

/**
* Get the user.
*
* @return string
*/
public function getUser()
{
throw new NowReadyForSecondStepAuthException($this->user);
}

/**
* {@inheritdoc}
*/
public function getMessageData()
{
return ['{{ username }}' => $this->user];
}

}

And from the first authenticator, from getUser method I called:


// $this->authenticatorException - object of NowReadyForSecondStepAuthException class
return $this->authenticatorException->getUser();

Do I understand well?

Reply

Hey neo119

You can call the NowReadyForSecondStepAuthException whatever you want, the important thing is you'll catch that exception, so you can start the second part of the authentication process, just as Ryan described in the second and third step https://symfonycasts.com/sc...

Cheers!

1 Reply
Daniel Avatar

I find it a little strange how the

NowReadyForSecondStepAuthException

class is called, from within it. Also I didn't understand why we need to send $user to the custom exception constructor class.
Can't we store the user in the session regardless of whether the login was successful or not?

Reply
Cat in space

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