Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5fdb0d0

Browse files
feat: implement HttpBearerAuthenticator
1 parent d8b342e commit 5fdb0d0

14 files changed

+734
-0
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Http\Authenticator;
13+
14+
use Exception;
15+
use Lcobucci\JWT\Token;
16+
use Psr\Log\LoggerInterface;
17+
use RuntimeException;
18+
use Symfony\Component\HttpFoundation\Request;
19+
use Symfony\Component\HttpFoundation\Response;
20+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
21+
use Symfony\Component\Security\Core\Exception\AuthenticationException;
22+
use Symfony\Component\Security\Core\User\UserProviderInterface;
23+
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\TokenBadge;
24+
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
25+
use Symfony\Component\Security\Http\Authenticator\Passport\TokenPassport;
26+
use Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken;
27+
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
28+
use Symfony\Component\Security\Http\TokenExtractor\BearerTokenExtractorInterface;
29+
30+
/**
31+
* @author Vincent Chalamon <[email protected]>
32+
*/
33+
abstract class AbstractBearerAuthenticator implements AuthenticatorInterface, AuthenticationEntryPointInterface
34+
{
35+
protected UserProviderInterface $userProvider;
36+
37+
protected BearerTokenExtractorInterface $tokenExtractor;
38+
39+
protected string $realmName;
40+
41+
protected string $payloadKey;
42+
43+
protected LoggerInterface $logger;
44+
45+
public function __construct(
46+
UserProviderInterface $userProvider,
47+
BearerTokenExtractorInterface $tokenExtractor,
48+
string $realmName,
49+
string $payloadKey,
50+
LoggerInterface $logger = null
51+
) {
52+
if (!interface_exists(Token::class)) {
53+
throw new RuntimeException(sprintf('%s requires lcobucci/jwt, please run "composer require lcobucci/jwt" to install it.', self::class));
54+
}
55+
56+
$this->userProvider = $userProvider;
57+
$this->tokenExtractor = $tokenExtractor;
58+
$this->realmName = $realmName;
59+
$this->payloadKey = $payloadKey;
60+
$this->logger = $logger;
61+
}
62+
63+
public function start(Request $request, AuthenticationException $authException = null): Response
64+
{
65+
$response = new Response();
66+
$response->headers->set('WWW-Authenticate', sprintf('Bearer realm="%s"', $this->realmName));
67+
$response->setStatusCode(Response::HTTP_UNAUTHORIZED);
68+
69+
return $response;
70+
}
71+
72+
public function supports(Request $request): ?bool
73+
{
74+
return $this->tokenExtractor->supports($request);
75+
}
76+
77+
public function authenticate(Request $request): Passport
78+
{
79+
try {
80+
$token = $this->getToken($this->tokenExtractor->extract($request));
81+
} catch (Exception $exception) {
82+
throw new AuthenticationException($exception->getMessage(), $exception->getCode(), $exception);
83+
}
84+
85+
$userIdentifier = $token->claims()->get($this->payloadKey);
86+
if (null === $userIdentifier) {
87+
throw new AuthenticationException(sprintf('Cannot retrieve key "%s" from token.', $this->payloadKey));
88+
}
89+
90+
return new TokenPassport(
91+
new TokenBadge($token, $userIdentifier, [$this->userProvider, 'loadUserByIdentifier'])
92+
);
93+
}
94+
95+
public function createToken(Passport $passport, string $firewallName): TokenInterface
96+
{
97+
return new PostAuthenticationToken($passport->getUser(), $firewallName, $passport->getUser()->getRoles());
98+
}
99+
100+
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
101+
{
102+
return null;
103+
}
104+
105+
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
106+
{
107+
if (null !== $this->logger) {
108+
$this->logger->info('Bearer authentication failed for token.', ['token' => $this->tokenExtractor->extract($request), 'exception' => $exception]);
109+
}
110+
111+
return $this->start($request, $exception);
112+
}
113+
114+
abstract protected function getToken(string $data): Token;
115+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Http\Authenticator\Configuration;
13+
14+
use Lcobucci\JWT\Configuration;
15+
use Lcobucci\JWT\Signer;
16+
use Lcobucci\JWT\Signer\Hmac\Sha256;
17+
use Lcobucci\JWT\Signer\Hmac\Sha384;
18+
use Lcobucci\JWT\Signer\Hmac\Sha512;
19+
use Lcobucci\JWT\Signer\Key\InMemory;
20+
use Symfony\Contracts\HttpClient\HttpClientInterface;
21+
22+
/**
23+
* todo Migrate this factory to framework bundle configuration.
24+
*
25+
* @author Vincent Chalamon <[email protected]>
26+
*
27+
* @final
28+
*/
29+
class ConfigurationFactory
30+
{
31+
/**
32+
* @var array<string, class-string<Signer>>
33+
*/
34+
final public const SIGN_ALGORITHMS = [
35+
'HS256' => Sha256::class,
36+
'HS384' => Sha384::class,
37+
'HS512' => Sha512::class,
38+
'ES256' => Signer\Ecdsa\Sha256::class,
39+
'ES384' => Signer\Ecdsa\Sha384::class,
40+
'ES512' => Signer\Ecdsa\Sha512::class,
41+
'RS256' => Signer\Rsa\Sha256::class,
42+
'RS384' => Signer\Rsa\Sha384::class,
43+
'RS512' => Signer\Rsa\Sha512::class,
44+
];
45+
46+
public static function createFromBase64Encoded(string $algorithm, string $key): Configuration
47+
{
48+
$signerClass = self::SIGN_ALGORITHMS[$algorithm];
49+
50+
return Configuration::forSymmetricSigner(new $signerClass(), InMemory::base64Encoded($key));
51+
}
52+
53+
public static function createFromFile(string $algorithm, string $key): Configuration
54+
{
55+
$signerClass = self::SIGN_ALGORITHMS[$algorithm];
56+
57+
return Configuration::forSymmetricSigner(new $signerClass(), InMemory::file($key));
58+
}
59+
60+
public static function createFromPlainText(string $algorithm, string $key): Configuration
61+
{
62+
$signerClass = self::SIGN_ALGORITHMS[$algorithm];
63+
64+
return Configuration::forSymmetricSigner(new $signerClass(), InMemory::plainText($key));
65+
}
66+
67+
public static function createFromUri(string $algorithm, string $key, HttpClientInterface $client = null): Configuration
68+
{
69+
$signerClass = self::SIGN_ALGORITHMS[$algorithm];
70+
71+
return Configuration::forSymmetricSigner(new $signerClass(), InMemory::plainText(sprintf(<<<KEY
72+
-----BEGIN PUBLIC KEY-----
73+
%s
74+
-----END PUBLIC KEY-----
75+
KEY
76+
, $client->request('GET', $key)->toArray()['public_key']
77+
)));
78+
}
79+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Http\Authenticator;
13+
14+
use Lcobucci\JWT\Configuration;
15+
use Lcobucci\JWT\Token;
16+
use Psr\Log\LoggerInterface;
17+
use Symfony\Component\HttpFoundation\Request;
18+
use Symfony\Component\Security\Core\User\UserProviderInterface;
19+
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\SignedTokenBadge;
20+
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\TokenBadge;
21+
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
22+
use Symfony\Component\Security\Http\TokenExtractor\BearerTokenExtractorInterface;
23+
24+
/**
25+
* @author Vincent Chalamon <[email protected]>
26+
*
27+
* @final
28+
*/
29+
class HttpBearerAuthenticator extends AbstractBearerAuthenticator
30+
{
31+
private readonly Configuration $configuration;
32+
33+
public function __construct(UserProviderInterface $userProvider, BearerTokenExtractorInterface $tokenExtractor, Configuration $configuration, string $realmName, string $payloadKey, LoggerInterface $logger = null)
34+
{
35+
parent::__construct($userProvider, $tokenExtractor, $realmName, $payloadKey, $logger);
36+
37+
$this->configuration = $configuration;
38+
}
39+
40+
/**
41+
* Override Passport to add SignedTokenBadge.
42+
*/
43+
public function authenticate(Request $request): Passport
44+
{
45+
$passport = parent::authenticate($request);
46+
47+
return $passport->addBadge(new SignedTokenBadge($this->configuration, $passport->getBadge(TokenBadge::class)->getToken()));
48+
}
49+
50+
protected function getToken(string $data): Token
51+
{
52+
return $this->configuration->parser()->parse($data);
53+
}
54+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Http\Authenticator\Passport\Badge;
13+
14+
use Lcobucci\JWT\Configuration;
15+
use Lcobucci\JWT\Token;
16+
17+
/**
18+
* @author Vincent Chalamon <[email protected]>
19+
*
20+
* @final
21+
*/
22+
class SignedTokenBadge implements BadgeInterface
23+
{
24+
private readonly Configuration $configuration;
25+
26+
private readonly Token $payload;
27+
28+
public function __construct(Configuration $configuration, Token $token)
29+
{
30+
$this->configuration = $configuration;
31+
$this->payload = $token;
32+
}
33+
34+
public function getConfiguration(): Configuration
35+
{
36+
return $this->configuration;
37+
}
38+
39+
public function getToken(): Token
40+
{
41+
return $this->payload;
42+
}
43+
44+
public function isResolved(): bool
45+
{
46+
return true;
47+
}
48+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Http\Authenticator\Passport\Badge;
13+
14+
use Lcobucci\JWT\Token;
15+
use LogicException;
16+
use Symfony\Component\Security\Core\Exception\AuthenticationException;
17+
use Symfony\Component\Security\Core\Exception\AuthenticationServiceException;
18+
use Symfony\Component\Security\Core\User\UserInterface;
19+
use Symfony\Component\Security\Http\EventListener\TokenUserLoaderListener;
20+
21+
/**
22+
* @author Vincent Chalamon <[email protected]>
23+
*
24+
* @final
25+
*/
26+
class TokenBadge extends UserBadge
27+
{
28+
private readonly Token $payload;
29+
30+
private ?UserInterface $user = null;
31+
32+
public function __construct(Token $token, string $userIdentifier, callable $userLoader = null)
33+
{
34+
parent::__construct($userIdentifier, $userLoader);
35+
36+
$this->payload = $token;
37+
}
38+
39+
public function getToken(): Token
40+
{
41+
return $this->payload;
42+
}
43+
44+
/**
45+
* @throws AuthenticationException when the user cannot be found
46+
*/
47+
public function getUser(): UserInterface
48+
{
49+
if (!isset($this->user)) {
50+
if (null === $this->getUserLoader()) {
51+
throw new LogicException(sprintf('No user loader is configured, did you forget to register the "%s" listener?', TokenUserLoaderListener::class));
52+
}
53+
54+
$this->user = ($this->getUserLoader())($this->getUserIdentifier(), $this->getToken());
55+
if (!$this->user instanceof UserInterface) {
56+
throw new AuthenticationServiceException(sprintf('The user provider must return a UserInterface object, "%s" given.', get_debug_type($this->user)));
57+
}
58+
}
59+
60+
return $this->user;
61+
}
62+
}

0 commit comments

Comments
 (0)