Score:2

AccessDeniedHttpException on a route with _access: TRUE

iq flag

In my custom module I have a few "publication" routes that do not need any kind of authentication. Months ago I learnt that I could achieve this with the following requirements in routing.yml:

my_module.myroute:
  [...]
  requirements:
    _access: 'TRUE'

This works on my existing routes.

Now I'm trying to add a new one that parses the Authorization HTTP header only for identification purposes: the purpose is to show a custom view on public data, without any authentication or authorization need. So, I tried to reach my custom route adding an Authorization header (via a browser extension), and I get the following error:

Path: /CLS/it/pub/quadroxml. Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException: 
The used authentication method is not allowed on this route. 
in Drupal\Core\EventSubscriber\AuthenticationSubscriber->onExceptionAccessDenied() 
(line 134 of [...]/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php).

So, sending an Authorization header apparently triggers some authentication method even on routes with _access: 'TRUE'.

Can I disable completely all authentication and authorization on some routes? Alternatively, can I enable the "used authentication method" on my route, and then accept any password? (I am only interested in the user id!)

Score:1
cn flag

I'm not sure about how to accept any password. But there is a difference between the default user authentication and basic_auth you are probably using for this route. The first one is defined as global:

core/modules/user/user.services.yml

user.authentication.cookie:
    class: Drupal\user\Authentication\Provider\Cookie
    arguments: ['@session_configuration', '@database', '@messenger']
    tags:
      - { name: authentication_provider, provider_id: 'cookie', priority: 0, global: TRUE }

while the second one is not:

core/modules/basic_auth/basic_auth.services.yml

services:
  basic_auth.authentication.basic_auth:
    class: Drupal\basic_auth\Authentication\Provider\BasicAuth
    arguments: ['@config.factory', '@user.auth', '@flood', '@entity_type.manager']
    tags:
      - { name: authentication_provider, provider_id: 'basic_auth', priority: 100 }

In this case the route needs to specify the _auth option. See

https://www.drupal.org/docs/drupal-apis/routing-system/structure-of-routes

Francesco Marchetti-Stasi avatar
iq flag
Well, this is indeed not complete, but it put me on the right track. I understood that the authentication providers available were not suited to my needs, so I implemented a custom one. and then I added it to the `_auth` option, as I suggested. I think this may deserve a new answer, and a comment to the other question that put me on the right track...
4uk4 avatar
cn flag
I've hesitated to suggest a custom authentication provider, because the question topic was the route definition. But showing the two core definitions was pointing in that direction and then of course you need a priority of >100 to override both.
Francesco Marchetti-Stasi avatar
iq flag
Yes, indeed it pointed in the right direction. I'm curious about your statement on the priority, is it possible that if I use a low priority the predefined authentication providers are still used? I ask because that's what seems to be happening...
4uk4 avatar
cn flag
If you have enabled HTTP Basic Authentication and you are sending an Authorization header, this module is used, unless you implement a custom authentication provider with a higher priority. The authentication happens before routing. After routing it's only checked whether the provider which won the authentication race before is appropriate for the route.
Francesco Marchetti-Stasi avatar
iq flag
I see. That explains everything I saw. I didn't find this behaviour documented anywhere – but I may have not looked deep enough. Also, I guess that implementing a custom behaviour on an HTTP Basic Authentication is not really a common requirement, especially nowadays... Anyway, I think that _now_ this is documented, _here_ :)
Score:0
bd flag

You can do pretty much anything you want when it comes to access control, by either defining a custom access check via a controller class (1) that you set directly on the specific route, or by creating your own access check service (2).

1. Access check via controller class
Docs: Custom route access checking

example.routing.yml

example:
  [...]
  requirements:
    _custom_access: '\Drupal\example\Controller\ExampleController::access'

ExampleController.php

class ExampleController {

  public function access(AccountInterface $account) {
    // Return \Drupal\Core\Access\AccessResultInterface here
  }

}

2. Access check service
Docs: Advanced route access checking

Basically, you do the same thing that provides the _access parameter that you already use (which is implemented by DefaultAccessChecker. So you create a tagged service and set that service to be a requirement on your route. The example from the docs is very complete, so I don't copy that here.

Francesco Marchetti-Stasi avatar
iq flag
Thank you, I already add a custom route access check in my module. I tried to add another one for this new path, but it made no difference: in the end I managed to solve my problem using a custom authentication provider, so I guess my problem was in the earlier phase of authentication rather than in the access check phase.
Score:0
iq flag

Inspired by @4k4 answer, I implemented a custom authentication provider, as detailed here: the sample implementation used on that page only checks for the presence of an header, I just had to replace X-Auth-Token with Authorization to get the code to do what I need.

With a minor, but important correction: if I left priority: 10 in the tags of the authentication service, I still keep on getting the same error when I send an Authorization header in the request. I struggled with this problem for a few hours, googling and searching; in the end my eye fell on the priority: -10 value of a path processor service I implemented months ago, where I had left the helpful comment "Low priority acts last", so I changed the value of the priority to 1000, and suddenly I managed to get my route to work!

I don't understand exactly why this happens, since I had only my custom authentication provider in the _auth vector under options, so I don't see how another provider can get a higher priority. I think this may be a bug, I'll try to find the time to reproduce it on a clean installation and file it.

Anyone needing help on this issue should also have a look at this question: it didn't help me much because the problem with priority is not mentioned, but since the problem is exactly the same, maybe other hints may be collected.

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.