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

Confirm 2FA #274

Closed xoniq closed 4 years ago

xoniq commented 4 years ago

Thank you for this wonderful bundle. I have figured it out mostly. But now I have my custom controller where the user can tell the controller he wants to use 2FA. If true, it generates a secret and a set of backup codes, and then redirect to a custom action where it shows a QR code and input field, to verify a successful setup.

The question it, it all works so far, but in my custom controller, upon submitting the field where they have to fill in the generated code to verify, how do I handle this?

In fact I have a POST statement, here I need to check if the OTP is valid, if so, it's marked as 'enabled' in the database. (next to the secret and backup codes array)

scheb commented 4 years ago

Depending on what authentication provider you're using (not sure if it's GoogleAuthenticator or TOTP), use either the scheb_two_factor.security.google_authenticator or scheb_two_factor.security.totp_authenticator service and call the checkCode() method to validate a 2fa code. The both have an identical method:

checkCode(TwoFactorInterface $user, string $code): bool

The $user should must have the requirements for 2fa set-up, but it doesn't need to be persisted yet.

xoniq commented 4 years ago

Thank you sir! I managed to make it work using your example. The back-end part is done.

I now struggle with the front-end. I prepared everything, it does hook into /2fa after logging in, I created a custom twig template, but kept form and fields intact. But when I submit, I get this error:

Unable to find the controller for path "/2fa_check". The route is wrongly configured.

Now, I can add /2fa_check as a custom route, but then I have no idea how to handle it (I could do the same as my initial question, but then I need to somehow tell the system its authorized), but I assume it should handle it by your bundle by default, but it doesn't seem to do it.

This is my routing:

2fa_login:
    path: /2fa
    defaults:
        _controller: "scheb_two_factor.form_controller:form"

2fa_login_check:
    path: /2fa_check
scheb commented 4 years ago

That's exactly the same route configuration that I'm using in my test application, so nothing wrong with that.

https://github.com/scheb/two-factor-app/blob/master/config/routes/scheb_two_factor.yaml

Are your 2fa routes within the firewall's pattern?

xoniq commented 4 years ago

This is in my firewall.yml for the admin area which allows 2FA:

        admin_secured_area:
            pattern:   ^/(admin/|admin$)
            anonymous: ~
            provider: fos_userbundle
            form_login:
                provider: fos_userbundle
                use_referer: true
                login_path: /admin/login
                check_path: /admin/login_check
                default_target_path: /admin
            logout:
                path:   /admin/logout
                target: /admin
            switch_user: true
            context: primary_auth
            remember_me:
                secret:   '%kernel.secret%'
                lifetime: 604800 # 1 week in seconds
                path:     /
                domain:   ~
            two_factor:
                csrf_token_generator: security.csrf.token_manager
                auth_form_path: 2fa_login
                check_path: 2fa_login_check

And for the access_control section:

    access_control:
        - { path: ^/2fa, role: IS_AUTHENTICATED_2FA_IN_PROGRESS }

I just checked, and it's the same as your security.yml (those parts) There is no /2fa_check part in access_control (it isn't in your example).

Additional info; this is Symfony 3.4

scheb commented 4 years ago

Your 2fa paths /2fa_check and /2fa do not match the firewall's pattern: pattern: ^/(admin/|admin$). Changing their path to /admin/2fa_check and /admin/2fa should fix it.

xoniq commented 4 years ago

Thanks! That was the problem. I kept it at /2fa at first to have a route for both backend and front-end, but then I have to define both i guess.

Now only I have to find out why the error message is shown in french (it uses the french translation file instead of dutch).

scheb commented 4 years ago

Thanks! That was the problem. I kept it at /2fa at first to have a route for both backend and front-end, but then I have to define both i guess.

Yes, you have to. These routes work similar to the login/logout routes, which also need to be defined per firewall.

Can't help you on the translation issue, doesn't seem to be a bundle-related issue to me.

xoniq commented 4 years ago

No, I will check it out by myself. Thanks for your help, and this great bundle!

mikey242 commented 4 years ago

Hi xoniq, I just came across this question as I am trying to do a similar thing by verifying the OTP before persisting the secret to the database. I am however having some difficulties with this as when I do $user->getGoogleAuthenticatorSecret() it comes up with nothing since I haven't yet persisted it to the database. Is it even possible to access this property before persisting it?

Also, thanks scheb for this bundle!

scheb commented 4 years ago

Set the secret value on the user entity, but don't persist the user entity until you got the code confirmed ;)

mikey242 commented 4 years ago

Hi scheb,

Thanks for the reply. I did that initially ($user->setGoogleAuthenticatorSecret) but the confirmation was being handled by another request and the result of getGoogleAuthenticationSecret was always blank, unless I persisted the value first. I solved it for now by creating a second field in the user entity and marking it as enabled/disabled and updating the isGoogleAuthenticatorEnabled method to take this into account. It all works fine but I can't help feeling I'm missing something obvious.

I should maybe mention this is all being handled by AJAX requests to my controller, one to get the QRCode and set the secret to the user, and the second to verify the OTP.

scheb commented 4 years ago

Ah, I see what the issue is. Your approach with isGoogleAuthenticatorEnabled seems good to me.