Skip to main content

Controlling Access to Drupal 8 Routes with Access Checks

In the previous post, I looked at how to put together a basic route controller in Drupal 8, and restrict access by specifying permissions. But there are my situations where basic permissions aren't enough.

In Drupal 7 we had procedural access callbacks. In Drupal 8 we now have AccessCheck services.

This post takes you through how to use the new AccessCheck interface to provide a custom access checker for your routes.

by kim.pepper /

Controlling Access with AccessCheck

In Using Drupal 8's new route controllersour previous example, we are only checking that a user has a certain permission to access the route. What if we have some custom logic to check? In Drupal 7 we would use an access callback. In Drupal 8 we need to create an implementation of AccessCheckInterface and define it as a service in the dependency injection container.

The first step is to modify our route configuration, and specify a special tag for our access callback in our trousers.routing.yml file. Let's assume our Trousers access check needs to assert that the user has legs.

In trousers.routing.yml file:

  pattern: '/trousers'
    _content: '\Drupal\trousers\Controller\TrouserController::list'
    _access_trousers_has_legs: 'TRUE'

In the above, we have created a new requirement, _access_trousers_has_legs: TRUE

The next step is to create our service definition for our TrousersHasLegsAccessCheck class by adding the following in in the root directory of our module.

    class: Drupal\trousers\Access\TrousersHasLegsAccessCheck
      - { name: access_check }

The above snippet defines a new service with the unique key access_check.trousers_has_legs. By convention, we name any service that is an access check with an access_check. prefix. Next we define the class that implements our AccessCheckInterface. And finally, we tag the service with access_check so Drupal can find it when it loads all access checks.

Like all Drupal 8 classes loaded with PSR-0 namespaces, we create our access check in a directory that matches the namespace. In our trousers module, this is trousers/lib/Drupal/trousers/Access/TrousersHasLegsAccessCheck.php

Our TrousersHasLegsAccessCheck.php access check class looks like this.


namespace Drupal\trousers\Access;

use Drupal\Core\Access\StaticAccessCheckInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\HttpFoundation\Request;
class TrousersHasLegsAccessCheck implements StaticAccessCheckInterface {

  public function appliesTo() {
    return '_access_trousers_has_legs';
public function access(Route $route, Request $request, AccountInterface $account) {
    if (!$account->hasLegs()) { // check if a user has legs
      return static::DENY; // denied! No legs.
    return static::ALLOW; // OK to access trousers

The appliesTo() method checks to see whether this access check should apply. It does this by simply checking whether the current route has an _access_trousers_has_legs requirement. As we defined this in our trouser.routing.yml file earlier, this should match when our route gets fired.

The access() method is where all the action happens. It gets passed the current Route, Request and current user (AccountInterface) objects. As this access check is a service, you could also inject in a database connection here to do some custom lookups. (See Using Drupal 8's new route controllers for an example of dependency injection).

There are some useful contstants on the StaticAccessCheckInterface interface (e.g DENY, ALLOW) which you return to let the router component know whether to allow or deny access. As you can see, we only allow access to the trousers route, if the current user has legs.

There are more advanced access rules that I haven't gone into here, like combining multiple access checks on the same route.

Please feel free to post comments or questions!

UPDATE: AccessInterface now passes current user (AccountInterface) as a parameter, so we don't need to look it up!

Posted by kim.pepper
Technical Director



Comment by rszrama


A helpful follow-up might be a clarification or succinct definition of what a "service" as defined in a *.services.yml file actually represents. Because of my experience with the Services module, my first assumption was that these were somehow related to the REST module, but as it turns out they're more like types of classes a module might define to do something on behalf of another module.

But I'm not sure that really gets at it... and I haven't yet got to a part of the Symfony tutorial that covers this stuff. : P

Comment by Berdir


@rszrama: Yes, that's basically it. A service is a class that you can instantiate and call methods on it. The main advantage of defining it as a service is that the service container takes care of your dependencies. Your service might need the database, cache and some other services, you define that in the services.yml file and when requested, your class is instantiated, those other services are too if necessary and passed into your class.

See for the main change notice

Comment by martin frances


Minor correction for you folks following at home

I am getting errors saying return value from TrousersHasLegsAccessCheck:access - must be a string so

_access_trousers_has_legs: TRUE


_access_trousers_has_legs: 'TRUE'


Comment by kim.pepper


Thanks Martin. I've updated the code sample.

Comment by kim.pepper


AccessInterface has been updated and now the current user (AccountInterface $account) is passed as a parameter to the access() method. I've updated the code sample to reflect this.

Comment by Alex Weber


This is really cool! Here's the official change record: bad multiple access checkers of the same type can't be stacked... it would be awesome to be able to check for multiple permissions without having to write a custom access checker...

Comment by matslats


Note that the appliesTo function has changed. Here is an example from views:
public function applies(Route $route) {
return $route->hasDefault('view_id');

Comment by dpi


The link to "Using Drupal 8's new route controllers" is not invalid.

Comment by Michael Welford


You now need to return an AccessResult

Comment by Robert Sollman


Hate to dig up something this old, but could you possibly update your post since they have removed StaticAccessCheckInterface altogether from core. I would love to use this, just can't figure out the work around since that update.