This bundle requires Symfony 4.4+ and the openssl extension.
Protip: Though the bundle doesn't enforce you to do so, it is highly recommended to use HTTPS.
Add lexik/jwt-authentication-bundle
to your composer.json
file:
php composer.phar require "lexik/jwt-authentication-bundle"
Register bundle into config/bundles.php
(Flex did it automatically):
return [
//...
Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true],
];
$ php bin/console lexik:jwt:generate-keypair
Your keys will land in config/jwt/private.pem
and config/jwt/public.pem
(unless you configured a different path).
Available options:
--skip-if-exists
will silently do nothing if keys already exist.--overwrite
will overwrite your keys if they already exist.
Otherwise, an error will be raised to prevent you from overwriting your keys accidentally.
Configure the SSL keys path and passphrase in your .env
:
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
JWT_PASSPHRASE=
# config/packages/lexik_jwt_authentication.yaml
lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_SECRET_KEY)%' # required for token creation
public_key: '%env(resolve:JWT_PUBLIC_KEY)%' # required for token verification
pass_phrase: '%env(JWT_PASSPHRASE)%' # required for token creation
token_ttl: 3600 # in seconds, default is 3600
Configure your config/packages/security.yaml
:
Make sure the firewall login
is place before api
, and if main
exists, put it after api
, otherwise you will encounter /api/login_check
route not found.
# Symfony versions prior to 5.3
security:
# ...
firewalls:
login:
pattern: ^/api/login
stateless: true
json_login:
check_path: /api/login_check # or api_login_check as defined in config/routes.yaml
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
api:
pattern: ^/api
stateless: true
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
access_control:
- { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
# Symfony 5.3 and higher
security:
enable_authenticator_manager: true
# ...
firewalls:
login:
pattern: ^/api/login
stateless: true
json_login:
check_path: /api/login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
api:
pattern: ^/api
stateless: true
jwt: ~
access_control:
- { path: ^/api/login, roles: PUBLIC_ACCESS }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
Configure your routing into config/routes.yaml
:
api_login_check:
path: /api/login_check
The first step is to authenticate the user using its credentials.
You can test getting the token with a simple curl command like this (adapt host and port):
Linux or macOS
curl -X POST -H "Content-Type: application/json" http://localhost/api/login_check -d '{"username":"johndoe","password":"test"}'
Windows
curl -X POST -H "Content-Type: application/json" http://localhost/api/login_check --data {\"username\":\"johndoe\",\"password\":\"test\"}
If it works, you will receive something like this:
{
"token" : "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJleHAiOjE0MzQ3Mjc1MzYsInVzZXJuYW1lIjoia29ybGVvbiIsImlhdCI6IjE0MzQ2NDExMzYifQ.nh0L_wuJy6ZKIQWh6OrW5hdLkviTs1_bau2GqYdDCB0Yqy_RplkFghsuqMpsFls8zKEErdX5TYCOR7muX0aQvQxGQ4mpBkvMDhJ4-pE4ct2obeMTr_s4X8nC00rBYPofrOONUOR4utbzvbd4d2xT_tj4TdR_0tsr91Y7VskCRFnoXAnNT-qQb7ci7HIBTbutb9zVStOFejrb4aLbr7Fl4byeIEYgp2Gd7gY"
}
Store it (client side), the JWT is reusable until its ttl has expired (3600 seconds by default).
Simply pass the JWT on each request to the protected firewall, either as an authorization header or as a query parameter.
By default only the authorization header mode is enabled : Authorization: Bearer {token}
See configuration reference document to enable query string parameter mode or change the header value prefix.
See Functionally testing a JWT protected api document or the sandbox application (Symfony2 or Symfony4) for a fully working example.
Each request after token expiration will result in a 401 response. Redo the authentication process to obtain a new token.
Maybe you want to use a refresh token to renew your JWT. In this case you can check JWTRefreshTokenBundle.
This is more of a Symfony2 related topic, but see Working with CORS requests document to get a quick explanation on handling CORS requests.
Using form_login security factory is very straightforward but it involves cookies exchange, even if the stateless parameter is set to true.
This may not be a problem depending on the system that makes calls to your API (like a typical SPA). But if it is, take a look at the GfreeauGetJWTBundle, which provides a stateless replacement for form_login.
For impersonating users using JWT, see https://symfony.com/doc/current/security/impersonating_user.html
As stated in this link and this one, Apache server will strip any Authorization header
not in a valid HTTP BASIC AUTH format.
If you intend to use the authorization header mode of this bundle (and you should), please add those rules to your VirtualHost configuration :
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
The following documents are available: