Buy

Authorization: access_control and Roles

Authentication is done. So how about we tackle the second half of security: authorization. This is all about figuring out whether or not the user has access to do something. For example, right now we have a fancy admin section, but probably not everyone should have access to it.

Denying with access_control

There are 2 main ways to deny access, and the simplest is right inside of security.yml. It's called "access control". Move in 4 spaces - so that you're at the same level as the firewalls key, but not inside of it. Add access_control:, new line, go out 4 more spaces and add - { path: ^/admin, roles: ROLE_USER }:

35 lines app/config/security.yml
... lines 1 - 2
security:
... lines 4 - 33
access_control:
- { path: ^/admin, roles: ROLE_USER }

That path is a regular expression. So, if anyone goes to a URL that starts with /admin, the system will kick them out unless they have ROLE_USER.

Let see it in action. First, make sure you're logged out. Now, go to /admin/genus. Boom! That was it! Anonymous users don't have any roles, so the system kicked us to the login page.

Tip

Our FormLoginAuthenticator is actually responsible for sending us to /login. You can customize and override this behavior if you need to. If you use a built-in authentication system, like form_login, then it may be responsible for this. This functionality is called an "entry point".

Now, login. It redirects us back to /admin/genus and we do have access. Our user does have ROLE_USER - you can see that if you click the security icon in the web debug toolbar. Remember, that's happening because - in our User class - we've hardcoded the roles: every user has a role that I made up: ROLE_USER:

95 lines src/AppBundle/Entity/User.php
... lines 1 - 12
class User implements UserInterface
{
... lines 15 - 46
public function getRoles()
{
return ['ROLE_USER'];
}
... lines 51 - 93
}

Many access_control

And at first, that's as complex as Symfony's authorization system gets: you give each user some roles, then check to see if they have those roles. In a minute, we'll make it so each user can have different roles.

But we're not quite done yet with access_control. We only have one rule, but you can have many: just create another line below this and secure a different section of your site. For example, maybe ^/checkout requires ROLE_ALLOWED_TO_BUY.

There is one gotcha: Symfony looks for a matching access_control from top to bottom, and stops as soon as it finds the first match. We won't talk about it here, but you can use that fact to lock down every page with an access_control, and then white-list the few public pages with access_control entries above that.

You can also do a few other cool things, like force the user to visit a part of your site via https. If they come via http, they'll be redirected to https.

When you Don't Have Access :(

Change the role to something we don't have, how about ROLE_ADMIN:

35 lines app/config/security.yml
... lines 1 - 2
security:
... lines 4 - 33
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }

Head back and refresh!

Access denied! Ok, two important things.

First, roles can be anything: I didn't have to configure ROLE_ADMIN before using it - I just made that up. The only rule about roles is that they must start with ROLE_. There's a reason for that, and I'll mention it later.

Second, notice this is an access denied screen: 403, forbidden. We see this because we're in development mode. But your users will see a different error page, which you can customize. In fact, you can have a different error page for 403 errors, 404 errors and 500 errors. It's easy to setup - so just check the docs.

Access controls are super easy to use... but they're a bit inflexible, unless you love writing complex, unreadable regular expressions. Next, let's look at a more precise way to control access: in your controller.

Leave a comment!

  • 2017-09-12 weaverryan

    Jarvis!

    Nice to hear from you man! So wow, I *hate* when things only work in dev. It's rare... but it's bad news when it happens! So first, does the problem exist locally on your machine, or only on the production machine? From your description, it sounds like it exists *locally* (but of course in the prod environment). I just want to make sure it's not some issue with the specific *machine*.

    Next, when it doesn't work, what does that mean exactly? Do you mean that the access_control is basically ignored and all users (even anonymous) have access? Or, is it requiring login... but not requiring ROLE_ADMIN? And can you post the full access_control section? As you know, only *one* access control is matched per request... so one thing I'm thinking about is whether or not a different access control is being hit, for some reason.

    And here are a few other things to try: change logging on prod to log everything (basically, copy the config_dev.yml logging stuff into config_prod.yml temporarily. This may help, because you can see all of the info in the logs. You could also temporarily enable the WebProfilerBundle for *all* environment in AppKernel, then copy the web_profiler config from config_dev.yml to config_prod.yml, but change intercept_redirects to true. As you also know, sometimes things get tricky with security because it redirects and you don't see what *really* happened before the redirect. This might help that.

    Phew! Hopefully that gives you something to at least start on :).

    Cheers and stay dry ;)

  • 2017-09-11 JSThePatriot

    Okay... so I have a unique issue with the `access_control` still. However, this issue only exists in production. We are unsure how to figure out what's the cause. Any pointers?

    Very similar to this comment: https://knpuniversity.com/s... we have a final access_control that basically requires everyone to be logged in. We are trying to set that to an even "higher" role of an Admin as we are only releasing a portion of our functionality, and instead of making a bunch of code changes just wanted to catch everything here, and this would also allow `admins` of a particular level to be able to still see and test the new functionality before we enable it for the "lesser" users. Our `access_control` works in `dev` mode (app_dev.php). However, no amount of cache clearing `rm -rf` seems to make this work in production.

    Would love some ideas/thoughts on this!

    Thanks!
    Jarvis

  • 2017-05-01 weaverryan

    Hey Ruslan!

    Good find! This was an oversight on my part honestly. I also implement CSRF protection (not using forms) here: https://knpuniversity.com/s... (that page isn't released at *this* moment, but will be in a week or so!)

    Cheers!

  • 2017-04-30 Ruslan

    Ok, I think I've found how to make csrf check working. Here is the one of Ryan's commits where he implements csrt check: https://github.com/knpunive...

  • 2017-04-30 Ruslan

    It's strange, but if I change csrf token using developer tools in Chrome, so that it normally should not be valid, I still will be logged in. But if I look into symfony profiler under the tab 'Forms', there will be an error message 'The CSRF token is invalid. Please try to resubmit the form.'

    So it looks like Guard detects that csrf token is wrong, but doesn't do anything.

    I've tried to add to LoginForm.php following lines, but can't resolve this issue:

    public function configureOptions(OptionsResolver $resolver)
    {
    $resolver->setDefaults(array(
    'csrf_protection' => true
    ));
    }

  • 2017-04-13 Victor Bocharsky

    Hey Joe,

    Hm, I see. I think somehow your default user equality test is failed. It's difficult to say why this happens, probably you have some problems with serialization/unserialization, but I'm not sure about it. Or maybe the object is modified during the runtime, probably by some event listeners, etc. and that's why default equality test failed. Anyway, with EquatableInterface you override the default implementation of users equality test.

    Cheers!

  • 2017-04-10 joe

    Hi, Victor!

    I have - { path: ^/, roles: ROLE_USER } in security.yml under access_control, despite the fact that it still does not work :(

    I was able to fix it by implementing the Entity class (User) with EquatableInferface (class User implements UserInterface, EquatableInterface) and adding the following method:

    ...
    public function isEqualTo(UserInterface $user)
    {
    return $this->id === $user->getId();
    }

    Any idea as to why this is happening?

    Thanks!

  • 2017-04-10 Victor Bocharsky

    Hey Joe,

    Most of the time it's due to a misconfiguration of your firewall, ensure you have a firewall which applies for all your pages, i.e. which has "pattern: ^/".

    Cheers!

  • 2017-04-07 joe

    Problem solved with EquatableInterface :)

    Cheers

  • 2017-04-07 joe

    I have successfully login, but in the tool bar below, in the user section, it says "Authentication: No". Any help?