Access tokens or API tokens are commonly used as authentication mechanism
in API contexts. The access token is a string, obtained during authentication
(using the application or an authorization server). The access token's role
is to verify the user identity and receive consent before the token is
issued.
Access tokens can be of any kind, for instance opaque strings,
JSON Web Tokens (JWT)
or
SAML2 (XML structures)
. Please refer to the
RFC6750
:
The OAuth 2.0 Authorization Framework: Bearer Token Usage
for
a detailed specification.
This guide assumes you have setup security and have created a user object
in your application. Follow
the main security guide
if
this is not yet the case.
To use the access token authenticator, you must configure a
token_handler
.
The token handler receives the token from the request and returns the
correct user identifier. To get the user identifier, implementations may
need to load and validate the token (e.g. revocation, expiration time,
digital signature, etc.).
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:srv="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/security
https://symfony.com/schema/dic/security/security-1.0.xsd">
<config>
<firewall name="main">
<access-token token-handler="App\Security\AccessTokenHandler"/>
</firewall>
</config>
</srv:container>
use App\Security\AccessTokenHandler;
use Symfony\Config\SecurityConfig;
return static function (SecurityConfig $security): void {
$security->firewall('main')
->accessToken()
->tokenHandler(AccessTokenHandler::class)
use App\Repository\AccessTokenRepository;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
class AccessTokenHandler implements AccessTokenHandlerInterface
public function __construct(
private AccessTokenRepository $repository
public function getUserBadgeFrom(string $accessToken): UserBadge
$accessToken = $this->repository->findOneByValue($accessToken);
if (null === $accessToken || !$accessToken->isValid()) {
throw new BadCredentialsException('Invalid credentials.');
return new UserBadge($accessToken->getUserId());
The access token authenticator will use the returned user identifier to
load the user using the user provider.
Caution
It is important to check the token if is valid. For instance, the
example above verifies whether the token has not expired. With
self-contained access tokens such as JWT, the handler is required to
verify the digital signature and understand all claims, especially
sub
, iat
, nbf
and exp
.
The application is now ready to handle incoming tokens. A token extractor
retrieves the token from the request (e.g. a header or request body).
By default, the access token is read from the request header parameter
Authorization
with the scheme Bearer
(e.g. Authorization: Bearer
the-token-value
).
Symfony provides other extractors as per the RFC6750:
header
(default)
The token is sent through the request header. Usually Authorization
with the Bearer
scheme.
query_string
The token is part of the request query string. Usually access_token
.
request_body
The token is part of the request body during a POST request. Usually
access_token
.
Caution
Because of the security weaknesses associated with the URI method,
including the high likelihood that the URL or the request body
containing the access token will be logged, methods query_string
and request_body
SHOULD NOT be used unless it is impossible to
transport the access token in the request header field.
You can also create a custom extractor. The class must implement
AccessTokenExtractorInterface.
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:srv="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/security
https://symfony.com/schema/dic/security/security-1.0.xsd">
<config>
<firewall name="main">
<access-token token-handler="App\Security\AccessTokenHandler">
<token-extractor>request_body</token-extractor>
<token-extractor>App\Security\CustomTokenExtractor</token-extractor>
</access-token>
</firewall>
</config>
</srv:container>
use App\Security\AccessTokenHandler;
use App\Security\CustomTokenExtractor;
use Symfony\Config\SecurityConfig;
return static function (SecurityConfig $security): void {
$security->firewall('main')
->accessToken()
->tokenHandler(AccessTokenHandler::class)
->tokenExtractors('request_body')
->tokenExtractors(CustomTokenExtractor::class)
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:srv="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/security
https://symfony.com/schema/dic/security/security-1.0.xsd">
<config>
<firewall name="main">
<access-token token-handler="App\Security\AccessTokenHandler">
<token-extractor>header</token-extractor>
<token-extractor>App\Security\CustomTokenExtractor</token-extractor>
</access-token>
</firewall>
</config>
</srv:container>
use App\Security\AccessTokenHandler;
use App\Security\CustomTokenExtractor;
use Symfony
\Config\SecurityConfig;
return static function (SecurityConfig $security): void {
$security->firewall('main')
->accessToken()
->tokenHandler(AccessTokenHandler::class)
->tokenExtractors([
'header',
CustomTokenExtractor::class,
That's it! Your application can now authenticate incoming requests using an
API token.
Using the default header extractor, you can test the feature by submitting
a request like this:
$ curl -H 'Authorization: Bearer an-accepted-token-value' \
https://localhost:8000/api/some-route
By default, the request continues (e.g. the controller for the route is
run). If you want to customize success handling, create your own success
handler by creating a class that implements
AuthenticationSuccessHandlerInterface
and configure the service ID as the success_handler
:
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:srv="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/security
https://symfony.com/schema/dic/security/security-1.0.xsd">
<config>
<firewall name="main">
<access-token token-handler="App\Security\AccessTokenHandler"
success-handler="App\Security\Authentication\AuthenticationSuccessHandler"
</firewall>
</config>
</srv:container>
use App\Security\AccessTokenHandler;
use App\Security\Authentication\AuthenticationSuccessHandler;
use Symfony\Config\SecurityConfig;
return static function (SecurityConfig $security): void {
$security->firewall('main')
->accessToken()
->tokenHandler(AccessTokenHandler::class)
->successHandler(AuthenticationSuccessHandler::class)
If you want to customize the default failure handling, use the
failure_handler
option and create a class that implements
AuthenticationFailureHandlerInterface.
OpenID Connect (OIDC) is the third generation of OpenID technology and it's a
RESTful HTTP API that uses JSON as its data format. OpenID Connect is an
authentication layer on top of the OAuth 2.0 authorization framework. It allows
to verify the identity of an end user based on the authentication performed by
an authorization server.
The OidcUserInfoTokenHandler
requires the symfony/http-client
package to
make the needed HTTP requests. If you haven't installed it yet, run this command:
$ composer require symfony/http-client
Symfony provides a generic OidcUserInfoTokenHandler
to call your OIDC server
and retrieve the user info:
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:srv="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/security
https://symfony.com/schema/dic/security/security-1.0.xsd">
<config>
<firewall name="main">
<access-token>
<token-handler oidc-user-info="https://www.example.com/realms/demo/protocol/openid-connect/userinfo"/>
</access-token>
</firewall>
</config>
</srv:container>
use Symfony\Config\SecurityConfig;
return static function (SecurityConfig $security) {
$security->firewall('main')
->accessToken()
->tokenHandler()
->oidcUserInfo('https://www.example.com/realms/demo/protocol/openid-connect/userinfo')
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:srv="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/security
https://symfony.com/schema/dic/security/security-1.0.xsd">
<config>
<firewall name="main">
<access-token>
<token-handler>
<oidc-user-info claim="email" base-uri="https://www.example.com/realms/demo/protocol/openid-connect/userinfo"/>
</token-handler>
</access-token>
</firewall>
</config>
</srv:container>
use Symfony\Config\SecurityConfig;
return static function (SecurityConfig $security) {
$security->firewall('main')
->accessToken()
->tokenHandler()
->oidcUserInfo()
->claim('email')
->baseUri('https://www.example.com/realms/demo/protocol/openid-connect/userinfo')
The oidc_user_info
token handler automatically creates an HTTP client with
the specified base_uri
. If you prefer using your own client, you can
specify the service name via the client
option:
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:srv="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/security
https://symfony.com/schema/dic/security/security-1.0.xsd">
<config>
<firewall name="main">
<access-token>
<token-handler>
<oidc-user-info client="oidc.client"/>
</token-handler>
</access-token>
</firewall>
</config>
</srv:container>
use Symfony\Config\SecurityConfig;
return static function (SecurityConfig $security) {
$security->firewall('main')
->accessToken()
->tokenHandler()
->oidcUserInfo()
->client('oidc.client')
By default, the OidcUserInfoTokenHandler
creates an OidcUser
with the
claims. To create your own user object from the claims, you must
create your own UserProvider:
use Symfony\Component\Security\Core\User\AttributesBasedUserProviderInterface;
class OidcUserProvider implements AttributesBasedUserProviderInterface
public function loadUserByIdentifier(string $identifier, array $attributes = []): UserInterface
The OidcTokenHandler
requires the web-token/jwt-library
package.
If you haven't installed it yet, run this command:
$ composer require web-token/jwt-library
Symfony provides a generic OidcTokenHandler
to decode your token, validate
it and retrieve the user info from it:
algorithms: ['ES256', 'RS256']
keyset: '{"keys":[{"kty":"...","k":"..."}]}'
audience: 'api-example'
issuers: ['https://oidc.example.com']
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:srv="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/security
https://symfony.com/schema/dic/security/security-1.0.xsd">
<config>
<firewall name="main">
<access-token>
<token-handler>
<oidc keyset="{'keys':[{'kty':'...','k':'...'}]}" audience="api-example">
<algorithm>ES256</algorithm>
<algorithm>RS256</algorithm>
<issuer>https://oidc.example.com</issuer>
</oidc>
</token-handler>
</access-token>
</firewall>
</config>
</srv:container>
use Symfony\Config\SecurityConfig;
return static function (SecurityConfig $security) {
$security->firewall('main')
->accessToken()
->tokenHandler()
->oidc()
->algorithms(['ES256', 'RS256'])
->keyset('{"keys":[{"kty":"...","k":"..."}]}')
->audience('api-example')
->issuers(['https://oidc.example.com'])
The support of multiple algorithms to sign the JWS was introduced in Symfony 7.1.
In previous versions, only the ES256
algorithm was supported.
Following the OpenID Connect Specification, the sub
claim is used by
default as user identifier. To use another claim, specify it on the
configuration:
claim: email
algorithms: ['ES256', 'RS256']
keyset: '{"keys":[{"kty":"...","k":"..."}]}'
audience: 'api-example'
issuers: ['https://oidc.example.com']
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:srv="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/security
https://symfony.com/schema/dic/security/security-1.0.xsd">
<config>
<firewall name="main">
<access-token>
<token-handler>
<oidc claim="email" keyset="{'keys':[{'kty':'...','k':'...'}]}" audience="api-example">
<algorithm>ES256</algorithm>
<algorithm>RS256</algorithm>
<issuer>https://oidc.example.com</issuer>
</oidc>
</token-handler>
</access-token>
</firewall>
</config>
</srv:container>
use Symfony\Config\SecurityConfig;
return static function (SecurityConfig $security) {
$security->firewall('main')
->accessToken()
->tokenHandler()
->oidc()
->claim('email')
->algorithms(['ES256', 'RS256'])
->keyset('{"keys":[{"kty":"...","k":"..."}]}')
->audience('api-example')
->issuers(['https://oidc.example.com'])
By default, the OidcTokenHandler
creates an OidcUser
with the claims. To
create your own User from the claims, you must
create your own UserProvider:
use Symfony\Component\Security\Core\User\AttributesBasedUserProviderInterface;
class OidcUserProvider implements AttributesBasedUserProviderInterface
public function loadUserByIdentifier(string $identifier, array $attributes = []): UserInterface
The support for CAS token handlers was introduced in Symfony 7.1.
Central Authentication Service (CAS) is an enterprise multilingual single
sign-on solution and identity provider for the web and attempts to be a
comprehensive platform for your authentication and authorization needs.
Symfony provides a generic Cas2Handler
to call your CAS server. It requires
the symfony/http-client
package to make the needed HTTP requests. If you
haven't installed it yet, run this command:
$ composer require symfony/http-client
You can configure a cas
token handler as follows:
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:srv="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/security
https://symfony.com/schema/dic/security/security-1.0.xsd">
<config>
<firewall name="main">
<access-token>
<token-handler>
<cas validation-url="https://www.example.com/cas/validate"/>
</token-handler>
</access-token>
</firewall>
</config>
</srv:container>
use Symfony\Config\SecurityConfig;
return static function (SecurityConfig $security) {
$security->firewall('main')
->accessToken()
->tokenHandler()
->cas()
->validationUrl('https://www.example.com/cas/validate')
The cas
token handler automatically creates an HTTP client to call
the specified validation_url
. If you prefer using your own client, you can
specify the service name via the http_client
option:
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:srv="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/security
https://symfony.com/schema/dic/security/security-1.0.xsd">
<config>
<firewall name="main">
<access-token>
<token-handler>
<cas validation-url="https://www.example.com/cas/validate" http-client="cas.client"/>
</token-handler>
</access-token>
</firewall>
</config>
</srv:container>
use Symfony\Config\SecurityConfig;
return static function (SecurityConfig $security) {
$security->firewall('main')
->accessToken()
->tokenHandler()
->cas()
->validationUrl('https://www.example.com/cas/validate')
->httpClient('cas.client')
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:srv="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/security
https://symfony.com/schema/dic/security/security-1.0.xsd">
<config>
<firewall name="main">
<access-token>
<token-handler>
<cas validation-url="https://www.example.com/cas/validate" prefix="cas-example"/>
</token-handler>
</access-token>
</firewall>
</config>
</srv:container>
use Symfony\Config\SecurityConfig;
return static function (SecurityConfig $security) {
$security->firewall('main')
->accessToken()
->tokenHandler()
->cas()
->validationUrl('https://www.example.com/cas/validate')
->prefix('cas-example')
Some types of tokens (for instance OIDC) contain all information required
to create a user entity (e.g. username and roles). In this case, you don't
need a user provider to create a user from the database:
namespace App\Security;
class AccessTokenHandler implements AccessTokenHandlerInterface
public function getUserBadgeFrom(string $accessToken): UserBadge
$payload = ...;
return new UserBadge(
$payload->getUserId(),
fn (string $userIdentifier) => new User($userIdentifier, $payload->getRoles())
When using this strategy, you can omit the user_provider
configuration
for stateless firewalls.
Be an active part of the community and contribute ideas, code and bug fixes.
Both experts and newcomers are welcome.
Learn how to contribute