scheb / two-factor-bundle

[ABANDONED] Two-factor authentication for Symfony 2 & 3 applications 🔐. Please use the newer versions from https://github.com/scheb/2fa.
https://github.com/scheb/2fa
MIT License
385 stars 111 forks source link

Use multiple interfaces, one with JWT and API #283

Closed RonRademaker closed 4 years ago

RonRademaker commented 4 years ago

Bundle version: v4.14.0 Symfony version: 4.4.7

Description I have an application that has two interfaces, a traditional (twig based) admin and an API (powered by API platform and lexik JWT for authentication). I want to add two factor authentication to both interfaces, preferably both with Google Authenticator. Now, for the admin interface this is pretty straight forward, but it gets complex for the API. A few of my issues:

Thanks!

scheb commented 4 years ago

Do the admin UI and the API share the same firewall? If not, would it be possible to give each one its own firewall? Would make things much easier. Ideally, please share your security.yaml so I get a better idea how your application works.

A question that I can already anwer.

How can I config the same provider twice (I want to use a different server_name and issuer for each interface)

You can't. You could try to do some dependency-injection magic and inject different values into scheb_two_factor.security.google_totp_factory depending on the context. Or duplicate all the code for the Google Authenticator provider as a custom provider. But then it would be not the same Google Authenticator provider, you'd need to have it configured twice for each user, one for the UI, another one for the API.

RonRademaker commented 4 years ago

Thanks for the quick reply, I use two firewalls:

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        admin:
            pattern: ^/admin
            anonymous: ~
            form_login:
                login_path: /admin/inloggen
                check_path: /admin/login_check
                default_target_path: /admin
            logout:
                path: /admin/logout
                target: easyadmin

            two_factor:
                auth_form_path: 2fa_login
                check_path: 2fa_login_check
            guard:
                authenticators:
                    - ConnectHolland\UserBundle\Security\UserBundleAuthenticator

        api_login:
            pattern: ^/api/users/authenticate
            stateless: true
            anonymous: true
            provider: app_user_provider
            json_login:
                check_path:               /api/users/authenticate
                success_handler:          lexik_jwt_authentication.handler.authentication_success
                failure_handler:          lexik_jwt_authentication.handler.authentication_failure
                require_previous_session: false

        api:
            pattern: ^/api
            stateless: true
            anonymous: true
            provider: app_user_provider
            json_login:
                check_path:                 /api/users/authenticate
                success_handler:            lexik_jwt_authentication.handler.authentication_success
                failure_handler:            lexik_jwt_authentication.handler.authentication_failure
                require_previous_session:   false
            guard:
                authenticators:
                    - lexik_jwt_authentication.jwt_token_authenticator
scheb commented 4 years ago

I see a problem there: your API has stateless security. To do two-factor authentication you have to keep some state between requests, because you have to know if the user has already passed 2fa or not. Normally, this is done through security token/session management.

There is no easy way to do this. I had a discussion a while ago on the same issue: https://github.com/scheb/two-factor-bundle/issues/141

As an outcome of this discussion, some new configuration options for custom handlers have been added to the bundle. They allow you to customize the handlers so that they return an API response instead of doing the default behavior (redirecting).

# config/packages/security.yaml
security:
    firewalls:
        yourFirewallName:
            two_factor:
                success_handler: acme.custom_success_handler # Use a custom success handler instead of the default one
                failure_handler: acme.custom_failure_handler # Use a custom failure handler instead of the default one

                # Use a custom authentication required handler instead of the default one
                # This can be used to modify the default behavior of the bundle, which is always redirecting to the
                # two-factor authentication form, when two-factor authentication is required.
                authentication_required_handler: acme.custom_auth_reqired_handler

And you should also replace lexik_jwt_authentication.handler.authentication_success with a custom implementation that checks if a TwoFactorToken is present. In that case return the "you have to do 2fa" API response, otherwise fall-back to the behavior of lexik_jwt_authentication.handler.authentication_success.

The tricky part is in fact how to restore TwoFactorToken in suceeding requests as long as the 2fa hasn't been completed yet. That's the thing you'd need to find out, maybe this post no 2) works for you?

Would be curious to learn if that approach works. I'd like to write a little "how-to" for stateless firewalls, because that question is coming up every once in a while.

RonRademaker commented 4 years ago

Thanks for the solution direction, I'll let you know if I can get things to work.

RonRademaker commented 4 years ago

I got 2FA on JWT working, but took a different approach.

I create a security provider (as described in https://symfony.com/doc/current/security/custom_authentication_provider.html) that:

tumbochka commented 4 years ago

@RonRademaker Could you please share your solution?

RonRademaker commented 4 years ago

Hello @tumbochka, I plan to open source our solution next week

RonRademaker commented 4 years ago

@RonRademaker Could you please share your solution?

I've made an open source bundle that has several options to make JWT more secure, including a 2FA implementation, at https://github.com/ConnectHolland/secure-jwt-bundle