Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error about "two-factor authentication is not in progress" #174

Closed
danielrhodeswarp opened this issue Feb 16, 2023 · 2 comments
Closed

Error about "two-factor authentication is not in progress" #174

danielrhodeswarp opened this issue Feb 16, 2023 · 2 comments
Labels

Comments

@danielrhodeswarp
Copy link

danielrhodeswarp commented Feb 16, 2023

Bundle version: 5.13.2
Symfony version: 4.4.49
PHP version: 7.4.33
Using authenticators (enable_authenticator_manager: true): YES / NO

Description

Trying to integrate Scheb 2FA "on top of" my existing Lexik JWT integration. This is for an app split into a Symfony API back-end and a JavaScript front-end.

Lexik itself is working standardly and hasn't been customised. I POST email and password credentials to the endpoint and this either fails or works. The front-end knows what to do in either case.

I'm following this page of the official bundle docs to complete my integration: https://symfony.com/bundles/SchebTwoFactorBundle/current/api.html

I am able to override Lexik's regular success handler to send a packet like {"login": "success": "2fa_complete": false} to the front-end.
This triggers the "You need to complete 2FA" form.

My issue after this is that, when submitting the user's one-time code to the configured /2fa_check endpoint for Scheb 2FA, I hit a 401 error:
"Tried to perform two-factor authentication, but two-factor authentication is not in progress".

What causes this error is the $token being null in TwoFactorListener::authenticate():

public function authenticate(RequestEvent $event): void
    {
        // When the firewall is lazy, the token is not initialized in the "supports" stage, so this check does only work
        // within the "authenticate" stage.
        $token = $this->tokenStorage->getToken();

        if (!($token instanceof TwoFactorTokenInterface) || $token->getProviderKey(true) !== $this->twoFactorFirewallConfig->getFirewallName()) {
            // This should only happen when the check path is called outside of a 2fa process and not protected via access_control
            // or when the firewall is configured in an odd way (different firewall name)
            throw new AuthenticationServiceException('Tried to perform two-factor authentication, but two-factor authentication is not in progress.');
        }

        $response = $this->attemptAuthentication($event->getRequest(), $token);
        $event->setResponse($response);
    }

Is this something dopey and obvious that I've missed? Or is it something more low-level and sinister? I'm not super knowledgeable about Symfony under the hood but I'm thinking things like:

[] Do I need to store the temporary token ("valid user but not completed 2FA") on the front-end and send that with the request to /2fa_check ?

[] Should my Symfony be doing the above automatically (cookies?) but my configuration is borked ?

It seems like my subsequent request to check the 2FA code isn't being recognised as belonging to the just-authenticated user.

Additional Context

SECURITY.YAML

# the main event!
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        login:
            pattern:  ^/authenticate
            stateless: false
            #true or 'lazy'?
            anonymous: lazy
                #means our Lexik JWT
            json_login:
                username_path: email
                check_path: api_login_check    #/authenticate/login_check
                success_handler: App\Security\JWTAuthenticationSuccessHandler
                #success_handler: lexik_jwt_authentication.handler.authentication_success
                failure_handler: lexik_jwt_authentication.handler.authentication_failure
                #Scheb 2FA
            two_factor:
                prepare_on_login: true
                prepare_on_access_denied: true
                auth_form_path: 2fa_login    #/authenticate/2fa
                check_path: 2fa_login_check    #/authenticate/2fa_check
                post_only: true 
                authentication_required_handler: App\Security\TwoFactorAuthenticationRequiredHandler
                success_handler: App\Security\TwoFactorAuthenticationSuccessHandler
                failure_handler: App\Security\TwoFactorAuthenticationFailureHandler
                auth_code_parameter_name: oneTimeCode

        api:
            pattern: ^/
            stateless: false
            provider: app_user_provider
            guard:
                authenticators:
                    - app.jwt_token_authenticator
    access_control:
        - { path: ^/authenticate/2fa, role: IS_AUTHENTICATED_2FA_IN_PROGRESS }
        - { path: ^/authenticate, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/logout, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/, roles: IS_AUTHENTICATED_FULLY }
@danielrhodeswarp
Copy link
Author

Yup, this issue was due to my JavaScript front-end not catching, and re-sending to /2fa_check, the intermediary "logged in but still need 2FA" cookie that Scheb 2FA and Symfony dutifully send back to me when I pass the first traditional sign-in attempt.

Although I am very interested in any ways to send, catch and re-send the intermediary token that aren't cookies :-D

So don't mind me any more, nothing to see here folks.

It's a very interesting bundle, many thanks.

@scheb
Copy link
Owner

scheb commented Feb 23, 2023

So I guess this can be closed now

@scheb scheb closed this as completed Feb 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants