lexik / LexikJWTAuthenticationBundle

JWT authentication for your Symfony API
MIT License
2.53k stars 610 forks source link

Configure multiple lexik_jwt_auth #707

Open Taluu opened 4 years ago

Taluu commented 4 years ago

Kinda linked to #504, but here goes ;

It could be great to be able to set multiple configurations for the config name, by using a prototype in the config. As does doctrine for example to be able to configure multiple entity managers / connections, so we can inject the jwt authentication we need.

e.g

lexik_jwt_authentication:
    api:
        secret_key: 'secret_key'
        token_ttl: 3600
        encoder:
            signature_algorithm: HS256

    other:
        secret_key: 'secret_key'
        token_ttl: 60
        encoder:
            signature_algorithm: HS256
fl0cke commented 4 years ago

Would be really nice to have this feature 👍

badrutdinovrr commented 4 years ago

As soon as Symphony lacks multiple bundle instances support and it seems @lexikteam is not interested in this issue, here is a workaround.

First of all, DI gives us ability to override any part of the bundle. A hint how to make many authenticators is given here

We have to define another authenticator on our own using existing configuration. Given the application with client and admin api (^/api/client and ^/api/admin respectively), we have to add another guard (config/security.yaml):

security:
    firewalls:
...
        admin:
            pattern: ^/api/admin
            anonymous: false
            lazy: true
            provider: jwt

            guard:
                authenticators:
                    - app.jwt_admin_authenticator
...

Here we use another authenticator, whose config looks like (config/services.yaml):

....
app.jwt_admin_jws_key_loader:
        parent: lexik_jwt_authentication.key_loader.raw
        arguments:
            index_0: '%env(JWT_ADMIN_SECRET_KEY)%'

    app.jwt_admin_jws_provider:
        parent: lexik_jwt_authentication.jws_provider.lcobucci
        arguments:
            index_0: '@app.jwt_admin_jws_key_loader'
            index_2: '%lexik_jwt_authentication.encoder.signature_algorithm%'

    app.jwt_admin_encoder:
        parent: lexik_jwt_authentication.encoder.lcobucci
        arguments:
            index_0: '@app.jwt_admin_jws_provider'

    app.jwt_admin_manager:
        parent: lexik_jwt_authentication.jwt_manager
        arguments:
            index_0: '@app.jwt_admin_encoder'
            index_2: 'systemUserId'
        calls:
            - [setUserIdentityField, ['systemUserId']]

    app.jwt_admin_authenticator:
        parent: lexik_jwt_authentication.jwt_token_authenticator
        arguments:
            index_0: '@app.jwt_admin_manager'
....

So, you can just assemble your own JWT authenticator using existing components. Unfortunately this approach can cause to tight coupling with bundle structure since you have to know how to build necessary objects.

TonyBogdanov commented 3 years ago

+1 for this!

In my setup I'm using JWT authentication for regular users, but I'm also using Google Cloud Pub/Sub with push endpoints, which I obviously need to firewall somehow. Google supports authenticating its requests with a JWT token generated for a service account, so all I need to do is verify that token against the service account.

The bundle works nicely to achieve this, the only problem is I can't use separate key pairs for Pub/Sub and my regular users, which is a disastrous security hole.

I'll have to explore @badrutdinovrr's custom authenticator workaround.

chalasr commented 3 years ago

With the new Symfony authenticator-based security system, we are probably going to move most of the config to per-firewall configuration (with a lexik_jwt: ~ configurable authenticator). Would that fit your use cases?

TonyBogdanov commented 3 years ago

With the new Symfony authenticator-based security system, we are probably going to move most of the config to per-firewall configuration (with a lexik_jwt: ~ configurable authenticator). Would that fit your use cases?

Yes, that would actually be the best solution. I've been looking into the new approach and it really does simplify things a lot! Too bad it'll be mostly used in Symfony 6.0, which in my mind, is to be widely adopted in at least a couple of years...

Aweptimum commented 3 years ago

With the new Symfony authenticator-based security system, we are probably going to move most of the config to per-firewall configuration (with a lexik_jwt: ~ configurable authenticator). Would that fit your use cases?

Yes +1 to this, just wanted to add my support. Thank you for this awesome bundle, though!

fd6130 commented 3 years ago

With the new Symfony authenticator-based security system, we are probably going to move most of the config to per-firewall configuration (with a lexik_jwt: ~ configurable authenticator). Would that fit your use cases?

How can we reuse to the other firewall if we put the config under it? Assume we want api1 and api2 using the same configuration (and also for the login part):

api2:
    lexik_jwt: ~

api1:
   lexik_jwt: ~
mbabker commented 2 years ago

Something else that will help massively with supporting multiple authenticators is re-working the AuthenticationSuccessHandler behavior. Right now the onAuthenticationSuccess() method passes only the user object into its handleAuthenticationSuccess() method, and the user plus the generated JWT are all that are available in the AuthenticationSuccessEvent. It'd be nice if more of the security information (firewall and token) were available without having to pull data from other services which relies on order of operations not changing inside Symfony's AuthenticatorManager class. I looked at this a year ago in JWTRefreshTokenBundle and IMO having to dig around for those two bits of info makes using the AuthenticationSuccessEvent a bit more complicated than it needs to be (especially considering that the two events the authenticator manager dispatches around the success handler both include the token and use the firewall specific event dispatcher).

Plus, if the settings do move from the top-level bundle config to the authenticator config (or, similar to the way I handled the authenticator in the refresh token bundle, supporting "global" settings through the bundle config and overriding config values on a per-firewall basis), it becomes more important that the services used in the authentication flow become aware of the final firewall specific configuration.