184 lines (128 sloc) 6.49 KB

Getting started


This bundle requires Symfony 3.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 the bundle in app/AppKernel.php:

public function registerBundles()
    return array(
        // ...
        new Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle(),

Generate the SSH keys :

$ mkdir -p config/jwt # For Symfony3+, no need of the -p option
$ openssl genrsa -out config/jwt/private.pem -aes256 4096
$ openssl rsa -pubout -in config/jwt/private.pem -out config/jwt/public.pem

In case first openssl command forces you to input password use following to get the private key decrypted

$ openssl rsa -in config/jwt/private.pem -out config/jwt/private2.pem
$ mv config/jwt/private.pem config/jwt/private.pem-back
$ mv config/jwt/private2.pem config/jwt/private.pem


Configure the SSH keys path in your config/packages/lexik_jwt_authentication.yaml :

    secret_key:       '%kernel.project_dir%/config/jwt/private.pem' # required for token creation
    public_key:       '%kernel.project_dir%/config/jwt/public.pem'  # required for token verification
    pass_phrase:      'your_secret_passphrase' # required for token creation, usage of an environment variable is recommended
    token_ttl:        3600

Configure your config/packages/security.yaml :

    # ...

            pattern:  ^/api/login
            stateless: true
            anonymous: true
                check_path:               /api/login_check
                success_handler:          lexik_jwt_authentication.handler.authentication_success
                failure_handler:          lexik_jwt_authentication.handler.authentication_failure

            pattern:   ^/api
            stateless: true
                    - lexik_jwt_authentication.jwt_token_authenticator

        - { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/api,       roles: IS_AUTHENTICATED_FULLY }

Configure your routing.yml :

    path: /api/login_check


1. Obtain the token

The first step is to authenticate the user using its credentials. A classical form_login on an anonymously accessible firewall will do perfect.

Just set the provided lexik_jwt_authentication.handler.authentication_success service as success handler to generate the token and send it as part of a json response body.

Store it (client side), the JWT is reusable until its ttl has expired (3600 seconds by default).

Note: You can test getting the token with a simple curl command like this:

curl -X POST -H "Content-Type: application/json" http://localhost:8000/api/login_check -d '{"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"

2. Use the token

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 for a fully working example.


About token expiration

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.

Working with CORS requests

This is more of a Symfony2 related topic, but see Working with CORS requests document to get a quick explanation on handling CORS requests.

A stateless form_login replacement

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

Important note for Apache users

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

Further documentation

The following documents are available: