New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
InsufficientAuthenticationException handling #862
Comments
Just found this related: #298 |
I ended up resolving this by decorating the authenticator (based on some solutions in #298): // \App\Security\AppTokenAuthenticator
<?php
declare(strict_types=1);
namespace App\Security;
use Lexik\Bundle\JWTAuthenticationBundle\Security\Guard\JWTTokenAuthenticator;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
use Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\TokenExtractorInterface;
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
/**
* @see JWTTokenAuthenticator
*
* Our decorator adds special handling for the anonymous use case.
* Adopted from https://github.com/lexik/LexikJWTAuthenticationBundle/issues/298#issuecomment-673408586
*/
class AppTokenAuthenticator extends JWTTokenAuthenticator
{
private ?FirewallMap $firewallMap;
private ?AuthenticationTrustResolverInterface $authenticationTrustResolver;
private ?KernelInterface $kernel;
private JWTTokenAuthenticator $decorated;
private EventDispatcherInterface $dispatcher;
public function __construct(
JWTTokenAuthenticator $decorated,
JWTTokenManagerInterface $jwtManager,
EventDispatcherInterface $dispatcher,
TokenExtractorInterface $tokenExtractor,
TokenStorageInterface $preAuthenticationTokenStorage
) {
$this->decorated = $decorated;
parent::__construct($jwtManager, $dispatcher, $tokenExtractor, $preAuthenticationTokenStorage);
$this->dispatcher = $dispatcher;
}
public function setFirewallMap(FirewallMap $firewallMap): void
{
$this->firewallMap = $firewallMap;
}
public function setTrustResolver(AuthenticationTrustResolverInterface $trustResolver): void
{
$this->authenticationTrustResolver = $trustResolver;
}
public function setKernel(KernelInterface $kernel)
{
$this->kernel = $kernel;
}
public function start(Request $request, AuthenticationException $authException = null)
{
if (null === $authException) {
return parent::start($request, $authException);
}
// Only takes effect for anonymous access violations.
if ($this->authenticationTrustResolver->isFullFledged($authException->getToken())) {
return parent::start($request, $authException);
}
// If the firewall does not allow anonymous, default behaviour applies.
if (!$this->firewallMap->getFirewallConfig($request)->allowsAnonymous()) {
return parent::start($request, $authException);
}
// We need to return a normal 403 access denied response.
$subrequest = $request->duplicate(null, null, [
'exception' => $authException,
]);
$subrequest->setMethod(Request::METHOD_GET);
return $this->kernel->handle($subrequest, HttpKernelInterface::SUB_REQUEST, false);
}
} # api/config/services.yaml
services:
App\Security\AppTokenAuthenticator:
decorates: lexik_jwt_authentication.security.guard.jwt_token_authenticator
calls:
- [ 'setTrustResolver', [ '@security.authentication.trust_resolver' ] ]
- [ 'setFirewallMap', [ '@security.firewall.map' ] ]
- [ 'setKernel', [ '@Symfony\Component\HttpKernel\KernelInterface' ] ] This gives me what I was looking for - a properly serialized "Access Denied" error in places where endpoints allow anonymous users, but voters reject access. |
Here is my workaround for the most recent version of the bundle: <?php
namespace App\Security;
use Symfony\Component\HttpFoundation\Request;
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authenticator\JWTAuthenticator;
use Symfony\Component\Security\Http\AccessMapInterface;
final class AppTokenAuthenticator extends JWTAuthenticator
{
private ?AccessMapInterface $accessMap = null;
public function setAccessMap(AccessMapInterface $accessMap): void
{
$this->accessMap = $accessMap;
}
/**
* {@inheritdoc}
*/
public function supports(Request $request): ?bool
{
// Add your logic here to check if the current path has public access
// If the path has public access, return false to skip token validation
if ($this->isPublicPath($request)) {
return false;
}
return parent::supports($request);
}
private function isPublicPath(Request $request): bool
{
[$roles, $channel] = $this->accessMap->getPatterns($request);
if ($roles[0] === 'PUBLIC_ACCESS' || $roles[0] === 'IS_AUTHENTICATED_ANONYMOUSLY') {
return true;
}
return false;
}
} Then I declare the service in services.yaml: services:
app.jwt_authenticator:
class: App\Security\AppTokenAuthenticator
parent: lexik_jwt_authentication.security.guard.jwt_token_authenticator
calls:
- [ 'setAccessMap', [ '@security.access_map' ] ] Then I set the authenticator in security.yaml: security:
firewalls:
main:
jwt:
authenticator: app.jwt_authenticator |
This one seems somewhat related to #489. I'm writing because I may have discovered a bug in
\Lexik\Bundle\JWTAuthenticationBundle\Security\Guard\JWTTokenAuthenticator::start
(or I may have just misconfigured something by mistake, hopefully you'll know which).Background:
I'm using this bundle in an APIP project (currently Symfony 5.0.x), and I want to configure a security declaration for a "Location" entity that uses a custom voter. That voter is supposed to return
true
forvoteOnAttribute
if the user is anonymous, and the item is active. If the user is anonymous, and the item is inactive, it should return false. The request is hitting mymain
firewall, which uses the jwt token authenticator in its work.The relevant access control directive is:
Problem:
I'm getting 'JWT Token not found' in my output. This is expected (I'm anonymous), but what I really want here is just a straight-up AccessDeniedException or AccessDeniedHttpException, since the resource isn't supposed to need a token in all circumstances.
The message in question is being triggered by
\Symfony\Component\Security\Http\Firewall\ExceptionListener::handleAccessDeniedException
, which recognizes that the caller is anonymous, and is throwing an insufficient authentication exception. That in turn triggers\Lexik\Bundle\JWTAuthenticationBundle\Security\Guard\JWTTokenAuthenticator::start
, which triggers aMissingTokenException
in all circumstances.Is this an instance of me misusing the 'security' directive, or should this authenticator be checking that the resource allows anonymous user access?
Updates
2.10.6
of this bundle, at the moment, if it makes a difference.The text was updated successfully, but these errors were encountered: