File: AccessTokenAuthenticator.php

package info (click to toggle)
symfony 7.3.2%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 129,560 kB
  • sloc: php: 1,503,693; xml: 6,816; javascript: 1,043; sh: 586; makefile: 241; pascal: 70
file content (123 lines) | stat: -rw-r--r-- 4,781 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Security\Http\Authenticator;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\AccessToken\AccessTokenExtractorInterface;
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken;
use Symfony\Contracts\Translation\TranslatorInterface;

/**
 * Provides an implementation of the RFC6750 of an authentication via
 * an access token.
 *
 * @author Florent Morselli <florent.morselli@spomky-labs.com>
 */
class AccessTokenAuthenticator implements AuthenticatorInterface
{
    private ?TranslatorInterface $translator = null;

    public function __construct(
        private readonly AccessTokenHandlerInterface $accessTokenHandler,
        private readonly AccessTokenExtractorInterface $accessTokenExtractor,
        private readonly ?UserProviderInterface $userProvider = null,
        private readonly ?AuthenticationSuccessHandlerInterface $successHandler = null,
        private readonly ?AuthenticationFailureHandlerInterface $failureHandler = null,
        private readonly ?string $realm = null,
    ) {
    }

    public function supports(Request $request): ?bool
    {
        return null === $this->accessTokenExtractor->extractAccessToken($request) ? false : null;
    }

    public function authenticate(Request $request): Passport
    {
        $accessToken = $this->accessTokenExtractor->extractAccessToken($request);
        if (!$accessToken) {
            throw new BadCredentialsException('Invalid credentials.');
        }

        $userBadge = $this->accessTokenHandler->getUserBadgeFrom($accessToken);
        if ($this->userProvider && (null === $userBadge->getUserLoader() || $userBadge->getUserLoader() instanceof FallbackUserLoader)) {
            $userBadge->setUserLoader($this->userProvider->loadUserByIdentifier(...));
        }

        return new SelfValidatingPassport($userBadge);
    }

    public function createToken(Passport $passport, string $firewallName): TokenInterface
    {
        return new PostAuthenticationToken($passport->getUser(), $firewallName, $passport->getUser()->getRoles());
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        return $this->successHandler?->onAuthenticationSuccess($request, $token);
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
    {
        if (null !== $this->failureHandler) {
            return $this->failureHandler->onAuthenticationFailure($request, $exception);
        }

        if (null !== $this->translator) {
            $errorMessage = $this->translator->trans($exception->getMessageKey(), $exception->getMessageData(), 'security');
        } else {
            $errorMessage = strtr($exception->getMessageKey(), $exception->getMessageData());
        }

        return new Response(
            null,
            Response::HTTP_UNAUTHORIZED,
            ['WWW-Authenticate' => $this->getAuthenticateHeader($errorMessage)]
        );
    }

    public function setTranslator(?TranslatorInterface $translator): void
    {
        $this->translator = $translator;
    }

    /**
     * @see https://datatracker.ietf.org/doc/html/rfc6750#section-3
     */
    private function getAuthenticateHeader(?string $errorDescription = null): string
    {
        $data = [
            'realm' => $this->realm,
            'error' => 'invalid_token',
            'error_description' => $errorDescription,
        ];
        $values = [];
        foreach ($data as $k => $v) {
            if (null === $v || '' === $v) {
                continue;
            }
            $values[] = \sprintf('%s="%s"', $k, $v);
        }

        return \sprintf('Bearer %s', implode(',', $values));
    }
}