LexikJWTAuthenticationBundle

repository·3.x·Indexed 25 days ago

https://github.com/lexik/lexikjwtauthenticationbundle

A Symfony bundle providing JSON Web Token (JWT) authentication for APIs, enabling stateless authentication. It supports symmetric (HMAC) and asymmetric (RSA, ECDSA) encryption, various token extraction methods (headers, cookies, query parameters), and integration with the Web-Token framework for encrypted tokens and key rotations. Compatible with PHP > 8.2 and Symfony > 6.4 up to 8.

Tokens
17.4K
Snippets
51
Records
80
Agent score
80%

What's inside LexikJWTAuthenticationBundle

  1. Overview of LexikJWTAuthenticationBundle

    3.x

    LexikJWTAuthenticationBundle provides JWT (JSON Web Token) authentication for Symfony APIs. It is designed to handle the authentication process using JWTs, making it suitable for stateless API architectures.

    Compatibility:

    • PHP > 8.2
    • Symfony > 6.4 up to 8
  2. Update KeyLoader implementation for version 3.0

    3.x

    If you are implementing a custom KeyLoaderInterface, you must update your implementation to include the following three new methods required by version 3.0:

    • getSigningKey
    • getPublicKey
    • getAdditionalPublicKeys

    Note that the OpenSSLKeyLoader class and the lexik_jwt_authentication.key_loader.openssl service have been removed in version 3.0.

  3. Validate data in the JWT payload using Events::JWT_DECODED

    3.x

    You can perform additional validation on the decoded JWT payload by listening to the lexik_jwt_authentication.on_jwt_decoded event. If validation fails, you can call $event->markAsInvalid() to reject the token.

    # config/services.yaml
    services:
        acme_api.event.jwt_decoded_listener:
            class: App\EventListener\JWTDecodedListener
            arguments: [ '@request_stack' ]
            tags:
                - { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_decoded, method: onJWTDecoded }
    // src/App/EventListener/JWTDecodedListener.php
    use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTDecodedEvent;
    
    class JWTDecodedListener
    {
        public function onJWTDecoded(JWTDecodedEvent $event)
        {
            $request = $this->requestStack->getCurrentRequest();
            $payload = $event->getPayload();
    
            // Example: Validate client IP
            if (!isset($payload['ip']) || $payload['ip'] !== $request->getClientIp()) {
                $event->markAsInvalid();
            }
    
            // Example: Add data to payload for custom UserProvider
            // $payload['custom_user_data'] = ...;
            // $event->setPayload($payload);
        }
    }
  4. Configure Application Security Firewalls

    3.x

    Set up your security.yaml to handle both the login process (to obtain a token) and the API requests (to use the token).

    Important: The login firewall must be placed before the api firewall. If a main firewall exists, it must be placed after api to avoid 404 errors on the login route.

    # config/packages/security.yaml
    security:
        enable_authenticator_manager: true # Only for Symfony 5.4
        # ...
    
        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 }
  5. Migrate key configuration from 2.x to 2.5

    3.x

    In version 2.5, the key configuration options have changed. The following options are deprecated and will be removed in version 3.0:

    • Replace private_key_path with secret_key. The new secret_key option accepts either a raw key string or a file path.
    • Replace public_key_path with public_key. The new public_key option accepts either a raw key string or a file path.

    Note: You only need to provide one of these. A server can hold only the secret_key to issue tokens, while clients can hold only the public_key to verify them.

  6. Configure test-specific JWT keys

    3.x

    To avoid using production keys during functional testing, generate dedicated test keys using OpenSSL and override the bundle configuration in your test environment configuration file (e.g., config/test/lexik_jwt_authentication.yaml).

    1. Generate keys:

      openssl genrsa -out config/jwt/private-test.pem -aes256 4096
      openssl rsa -pubout -in config/jwt/private-test.pem -out config/jwt/public-test.pem
    2. Update config/test/lexik_jwt_authentication.yaml:

      lexik_jwt_authentication:
          secret_key: '%kernel.project_dir%/config/jwt/private-test.pem'
          public_key: '%kernel.project_dir%/config/jwt/public-test.pem'
    # config/test/lexik_jwt_authentication.yaml
    lexik_jwt_authentication:
        secret_key: '%kernel.project_dir%/config/jwt/private-test.pem'
        public_key: '%kernel.project_dir%/config/jwt/public-test.pem'
  7. Add public data to the JWT response using Events::AUTHENTICATION_SUCCESS

    3.x

    By default, the authentication response is a JSON containing only the JWT. You can customize this response to include additional public data (e.g., user roles) by listening to the lexik_jwt_authentication.on_authentication_success event.

    # config/services.yaml
    services:
        acme_api.event.authentication_success_listener:
            class: App\EventListener\AuthenticationSuccessListener
            tags:
                - { name: kernel.event_listener, event: lexik_jwt_authentication.on_authentication_success, method: onAuthenticationSuccessResponse }
    // src/App/EventListener/AuthenticationSuccessListener.php
    use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent;
    
    class AuthenticationSuccessListener
    {
        public function onAuthenticationSuccessResponse(AuthenticationSuccessEvent $event)
        {
            $data = $event->getData();
            $user = $event->getUser();
    
            if ($user instanceof UserInterface) {
                $data['data'] = [
                    'roles' => $user->getRoles(),
                ];
            }
    
            $event->setData($data);
        }
    }
  8. Customize the failure response body using Events::AUTHENTICATION_FAILURE

    3.x

    You can override the default 401 JSON response for failed authentication by listening to the lexik_jwt_authentication.on_authentication_failure event and setting a custom JWTAuthenticationFailureResponse.

    # config/services.yaml
    services:
        acme_api.event.authentication_failure_listener:
            class: App\EventListener\AuthenticationFailureListener
            tags:
                - { name: kernel.event_listener, event: lexik_jwt_authentication.on_authentication_failure, method: onAuthenticationFailureResponse }
    // src/App/EventListener/AuthenticationFailureListener.php
    use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationFailureEvent;
    use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;
    use Symfony\Component\HttpFoundation\JsonResponse;
    
    class AuthenticationFailureListener
    {
        public function onAuthenticationFailureResponse(AuthenticationFailureEvent $event)
        {
            $data = [
                'name' => 'John Doe',
                'foo'  => 'bar',
            ];
    
            $response = new JWTAuthenticationFailureResponse('Bad credentials, please verify that your username/password are correctly set', JsonResponse::HTTP_UNAUTHORIZED);
            $response->setData($data);
    
            $event->setResponse($response);
        }
    }
  9. Handle multiple JWT failure events with a single listener

    3.x

    If you want to use the same logic to handle multiple failure scenarios (such as JWT_INVALID, JWT_NOT_FOUND, and JWT_EXPIRED), you can implement a single listener method.

    Instead of type-hinting a specific event class (like JWTExpiredEvent), type-hint the Lexik\Bundle\JWTAuthenticationBundle\Event\JWTFailureEventInterface in your listener's method argument. This allows the method to catch any event that implements this interface.