Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
When you visit a route that is protected with basic authentication and you do not provide credentials, then the resulting 401 page shows a stacktrace rather than a human friendly error message.
Steps to replicate:
- Create a route that is protected with basic authentication. For example put this in
user.routing.yml
:user.basic_auth: path: '/user/basic-auth' defaults: _form: '\Drupal\user\Form\UserLoginForm' options: _auth: [ 'basic_auth' ]
- Enable the
basic_auth
module. - Visit the path
user/basic-auth
without an active session (easiest is to open an anonymous browser window to test) - You will be asked to authenticate. Close the dialog without authenticating.
Result: the following backtrace is shown:
Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException: No authentication credentials provided. in Drupal\basic_auth\Authentication\Provider\BasicAuth->challengeException() (line 138 of core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php).
Drupal\Core\Routing\AccessAwareRouter->matchRequest(Object)
Symfony\Component\HttpKernel\EventListener\RouterListener->onKernelRequest(Object, 'kernel.request', Object)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch('kernel.request', Object)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1)
Drupal\Core\StackMiddleware\PageCache->fetch(Object, 1, 1)
Drupal\Core\StackMiddleware\PageCache->lookup(Object, 1, 1)
Drupal\Core\StackMiddleware\PageCache->handle(Object, 1, 1)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1)
Stack\StackedHttpKernel->handle(Object, 1, 1)
Drupal\Core\DrupalKernel->handle(Object)
Expected result: A human readable message such as:
Unauthorized - Please log in to access this page.
Comment | File | Size | Author |
---|---|---|---|
#20 | interdiff.txt | 1.49 KB | Dom. |
#20 | comprehensive-401-error--fix--2472371-19.patch | 5.45 KB | Dom. |
#18 | interdiff.txt | 828 bytes | pfrenssen |
#18 | 2472371-18.patch | 4.71 KB | pfrenssen |
#6 | comprehensive-error--test-fails-2472371-6.patch | 1.6 KB | Dom. |
Comments
Comment #1
Dom. CreditAttribution: Dom. commentedAdd test fail patch.
Comment #3
pfrenssen401 is not "access denied" but "unauthorized". Access denied is 403. The difference is between "We know who you are and you are not allowed here", and "This is a restricted area, please identify".
Comment #4
Dom. CreditAttribution: Dom. commentedChanged the failing-test patch accordingly to #3
Comment #5
Dom. CreditAttribution: Dom. commentedThis test should fail: it is made for that. Here it does not (in #4) because the stacktrace contains the "Unauthorized" field :
Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException: No authentication credentials provided. in Drupal\basic_auth\Authentication\Provider\BasicAuth->challengeException() (line 138 of core\modules\basic_auth\src\Authentication\Provider\BasicAuth.php).
So it take rework to actually figure out it the display is a stracktrace or a user friendly message.
Anyone have a clue on that ?
Comment #6
Dom. CreditAttribution: Dom. commentedTry another way : check no "Exception" text is displayed.
Interdiff :
$this->assertText('Unauthorized', "A user friendly access unauthorized message is displayed");
changed to
$this->assertNoText('Exception', "A user friendly access unauthorized message is displayed");
Comment #8
Dom. CreditAttribution: Dom. commentedAdding patch fix to be reviewed.
Comment #9
Dom. CreditAttribution: Dom. commentedComment #10
znerol CreditAttribution: znerol commented@Dom.: The reason why the stacktrace is displayed is because we explicitly enable verbose logging in tests (see
WebTestBase::setUp()
line 918). There is a test inErrorHandlerTest.php
which makes sure that no backtrace is displayed if theerror_level
is not verbose. Also note thaterror_level
defaults tohide
(seecore/modules/system/config/install/system.logging.yml
).Therefore I do not think that there is anything to do here in this issue.
Comment #11
Dom. CreditAttribution: Dom. commentedStill with standard profile, and error page is sent instead of a nice 401 page. Plus the exception is in the logfile.
Comment #12
Dom. CreditAttribution: Dom. commentedI forgot a wording was actually available in the issue summary.
- Simply changed the 401 page wording regarding this new.
- Also added a test for this sentence thus in BasicAuthTest.php
Note: I still think it would be better to ask for credentials instead of login since it more in conjunction with the reason why 401 happens regarding authentication (basic, oauth or whatever).
Interdiff:
'#markup' => $this->t('Please provide valid credentials.'),
changed to :
'#markup' => $this->t('Please log in to access this page.'),
$this->assertNoText('Exception', "A user friendly access unauthorized message is displayed");
changed to :
$this->assertNoText('Exception', "No raw exception is displayed on the page.");
and added :
$this->assertText('Please log in to access this page.', "A user friendly access unauthorized message is displayed.");
Comment #13
znerol CreditAttribution: znerol commentedOh, given the screenshot in #11 I agree that we should do something about it.
Comment #16
pfrenssenAssigning for review. I am also looking into the test failure. On a 401 page we have to output a "WWW-Authenticate" HTTP header that informs the client about the authentication type and realm. With the patch applied this HTTP header is no longer output.
Comment #17
znerol CreditAttribution: znerol commentedpossibly related #2432657: BasicAuth challenge never sent to browser
Comment #18
pfrenssenThis makes sure that any special HTTP headers that are output on an HTTP exception page is also output when a human friendly message is shown.
Comment #19
pfrenssenLeaving this in the capable hands of Dom.
Comment #20
Dom. CreditAttribution: Dom. commented@pfrenssen, thanks so much for help !
Adding new patch with interdiff for review. The purpose is just to reduce the scope of added headers to HttpExceptions only.
Comment #21
Dom. CreditAttribution: Dom. commentedAny updates on the #20 ? Is this okay ?
Comment #22
znerol CreditAttribution: znerol commentedI'm okay with this. Tagging it in order to give people behind #2323759: Modularize kernel exception handling a chance to review.
Comment #23
Crell CreditAttribution: Crell at Palantir.net commentedThis is consistent with how the exception system is supposed to work, and I like the addition of ensuring that HTTP headers get transferred. That's a useful bug fix on its own. Thanks all!
Comment #24
xjmComment #26
xjmThis issue is a normal bug fix, and doesn't include any disruptive changes, so it is allowed per https://www.drupal.org/core/beta-changes.
Thanks for the clear, specific steps to reproduce the issue. I was able to confirm both the bug and fix using them. I also like that the fix is clean and consistent with our existing 4xx handling.
Minor: This summary is supposed to be only one line of 80 characters or fewer. See https://www.drupal.org/node/1354#drupal. I fixed this on commit:
This assertion will not test what is expected if errors aren't being reported to the screen (and, indeed, it failed to fail in https://qa.drupal.org/pifr/test/1027378). However, the other assertions provide the coverage needed. Under normal circumstances I'd suggest asserting the response was not a 500 instead of checking for the text but that obviously is irrelevant when we're asserting it's a 401. :)
Minor: This is technically supposed to start with a verb (see https://www.drupal.org/node/1354#functions), but since it's exactly the same as the other similar methods on the class, I'll take it.
I didn't consider any of the above worth delaying this patch on, so committed and pushed to 8.0.x. Thanks!
Comment #27
znerol CreditAttribution: znerol commentedRe #26.2: In fact the
error_level
is set toverbose
inWebTestBase::setUp()
, so the stack trace and the termException
is displayed without the patch. Note that the wording of the assert-message was subsequently changed in #12, that's why the message in the test result of the test-only patch in #6 is different from the one committed.