scheb / 2fa

Two-factor authentication for Symfony applications πŸ”
MIT License
495 stars 72 forks source link

How to have Google authenticator required to be setup even after first login ? #203

Closed allan-simon closed 10 months ago

allan-simon commented 11 months ago

I've checked https://symfony.com/bundles/SchebTwoFactorBundle/current/troubleshooting.html

Bundle version: 6.9.0 Symfony version: 6.3.5 PHP version: 8.1.20 Using authenticators (enable_authenticator_manager: true): YES (mandatory in 6.2+)

Description

I want Google Authenticator to be mandatory on my application.

Hence I thought about doing the following flow:

  1. you login for the first time (the email / password is already set by an admin, so you don't need to register first, you directly login)
  2. if you try to access any "authenticated user only" page and you have not it setup yet, you a redirected to the page asking you to configure it.

I block at the step 2.

While I could do a hackish check in the /dashboard method to check if there's a google secret, and if not redirecting it would only be a security theater as other pages will not have the check (I could do it for every single page but ....)

I've tried to do

public function isGoogleAuthenticatorEnabled(): bool
{
   return true;
}

but it fails with User has to provide a secret code for Google Authenticator authentication.

Which I think is normal because this condition is supposed to be used to enter a code for an existing authenticator

As I'm not familiar enough with Symfony's internal I'm not sure if the best solution is

  1. to create my owner GoogleRequiredAuthenticator that either display a "setup" form or a "enter code" form ?
  2. Have my own listener (which kind of listener on which event ?) to intercept the event before and redirect ?
  3. something else ?

Additional Context

my package config:

scheb_two_factor:
    google:
        enabled: true
    security_tokens:
        - Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken
        - Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken
        - Symfony\Component\Security\Core\Authentication\Token\RememberMeToken
        company_admins:
            host: companies.*
            pattern: ^/
            two_factor:
               auth_form_path: company_admin_2fa_login
               check_path: company_admin_2fa_login_check
            form_login:
                login_path: company_admins_login
                check_path: company_admins_login
                default_target_path: company_admins_dashboard
                enable_csrf: true
                use_referer: true
            entry_point: form_login
    access_control:
        # allow unauthenticated users to access the login form
        - { host: companies.*, path: ^/login, roles: PUBLIC_ACCESS }
        # This ensures that the form can only be accessed when two-factor authentication is in progress.
        - { host: companies.*, path: ^/2fa, roles: IS_AUTHENTICATED_2FA_IN_PROGRESS }
        - { host: companies.*, path: ^/, roles: ROLE_COMPANY_ADMIN}
alexbaileyuk commented 11 months ago

I've also come across this issue today but with TOTP authentication. I guess the type of authentication doesn't really matter here.

Essentially it looks like this bundle doesn't support requiring MFA on first login out-of-the-box.

I don't see a way around this at the moment so I've install email authentication (since all my users require email to be verified) and I've updated my code to automatically enable email MFA for the user following the guide here: https://symfony.com/bundles/SchebTwoFactorBundle/6.x/providers/email.html. Then, once logged in with email MFA the user can add additional options.

I think it'd be great if there was an option in the bundle to require authentication on first login in the future. However, it might require substantial changes. A lot of this seems to boil down to logic in TotpAuthenticatorTwoFactorProvider::beginAuthentication which requires TOTP values to be set before authentication can be made.

allan-simon commented 11 months ago

I'm thinking maybe I need to add a "firewall" event that would fire a "NeedToSetupGoogleAuthenticatorException" ? :thinking:

scheb commented 11 months ago

I've tried to do [...] but it fails with User has to provide a secret code for Google Authenticator authentication. Which I think is normal because this condition is supposed to be used to enter a code for an existing authenticator

Correct. You cannot have Google Authenticator enabled, when you don't have a secret code configured. That's why the docs are recommending return null !== $this->googleAuthenticatorSecret; for isGoogleAuthenticatorEnabled().

I've also come across this issue today but with TOTP authentication. I guess the type of authentication doesn't really matter here.

Correct. The 2fa provider doesn't matter for the implementation.

Essentially it looks like this bundle doesn't support requiring MFA on first login out-of-the-box.

Correct and that's how it's supposed to be. Everyone has their own expectations how a forced 2fa should work, so it would be impossible to provide an out-of-the-box solution that makes everyone happy.


Actually I've never seen how people are solving this problem in the wild 🀷. How I'd recommend to handle this:

Ideally: Force people to setup 2fa during the registration process, so you know they have set it up once they login. You don't need to bother with it πŸ˜‰.

If you can't do this - e.g. accounts are automatically created for users and therefore you can't setup 2fa for them - then you have to go the route "force them to set it up on the first login". For that, you'd need to inject a check into the login process, where you check if the user account has 2fa setup, if not force them to set it up.

I'd recommend to have a look at the AuthenticationTokenCreatedEvent event, which is dispatched right after creating the security token object, so by that point, the authentication process was successful and you can get the user object from the security token. But at that point the authentication process hasn't been completed yet. The Symfony security layer does not know about that security token yet, so it's a great point to "do stuff" right in the middle of the login process πŸ˜„.

The 2fa bundle is actually using that event to inject its magic into the authentication process: intercept the security token, wrap it with 2fa magic, before it's announced to the security layer. Code for reference: https://github.com/scheb/2fa/blob/6.x/src/bundle/Security/TwoFactor/Event/AuthenticationTokenListener.php

stale[bot] commented 10 months ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

allan-simon commented 10 months ago

I will soon get to work on this again, I will keep posted if I got a solution ,but yes I think i I will check the AuthenticationTokenCreatedEvent

If you can't do this - e.g. accounts are automatically created for users and therefore you can't setup 2fa for them - t

yes these users are coming from a SamlV2 connection

but your explanation makes sense on why this bundle does not provide a "built-in" solution