Reputation: 105
I have implemented two-factor authentication based on email code in my app. After user enter his email and password, system redirects him to /2fa route where he need to enter code from email. Everything works great. But I don't understand how to implement resend code functionality. The small advice in doc isn't enough for me. Please help.
Bundle version: scheb/2fa-bundle: 5.10.1, scheb/2fa-email: 5.10.1
Symfony version: 5.2.10
PHP version: 8.0.3
config/packages/security.yaml
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: email
# used to reload user from session & other features (e.g. switch_user)
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
lazy: true
provider: app_user_provider
guard:
authenticators:
- App\Security\LoginFormAuthenticator
logout:
path: app_logout
# where to redirect after logout
# target: app_any_route
access_denied_handler: App\Security\AccessDeniedHandler
two_factor:
auth_form_path: 2fa_login # The route name you have used in the routes.yaml
check_path: 2fa_login_check # The route name you have used in the routes.yaml
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#firewalls-authentication
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
- { path: '^/2fa', roles: IS_AUTHENTICATED_2FA_IN_PROGRESS }
- { path: '^/login', roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: '^/logout', roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: '^/register', roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: '^/reset-password', roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: '^/verify/email', roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: '^/', roles: ROLE_USER }
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
config/packages/scheb_2fa.yaml
scheb_two_factor:
security_tokens:
- Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken
email:
enabled: true
mailer: 2fa_mailer
template: security/2fa_form.html.twig
digits: 6
config/routes.yaml
2fa_login:
path: /2fa
defaults:
_controller: "scheb_two_factor.form_controller:form"
2fa_login_check:
path: /2fa_check
Upvotes: 1
Views: 1179
Reputation: 11
For anybody who needs a complete example, look at this suggestion:
Versions:
Symfony Controller:
<?php
namespace App\Controller\Security;
use Scheb\TwoFactorBundle\Security\Authentication\Token\TwoFactorTokenInterface;
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Email\Generator\CodeGeneratorInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface;
class AdminTwoFactorController extends AbstractController
{
public function __construct(
private readonly CodeGeneratorInterface $codeGenerator,
private readonly TokenStorageInterface $tokenStorage,
) {
}
#[Route('/admin/2fa/resend-code', name: 'admin_2fa_resend_code')]
public function resendCode(): RedirectResponse
{
$token = $this->tokenStorage->getToken();
$user = $token?->getUser();
if ($token instanceof TwoFactorTokenInterface && $user instanceof TwoFactorInterface) {
$this->codeGenerator->generateAndSend($user);
$this->addFlash('success', 'Code sent.');
} else {
$this->addFlash('danger', 'Error sending code.');
}
return $this->redirectToRoute('admin_2fa_login');
}
}
I am using
$this->codeGenerator->generateAndSend($user);
on purpose to not just reSend the code, but to create a new code and send it.
For just resending the existing code, use this:
$this->codeGenerator->reSend($user);
Then I included this link into the 2fa_form.html.twig template, so in case the user did not receive the email, it will be resent.
<a href="{{ path('admin_2fa_resend_code') }}">Send code</a>
My routes/scheb_2fa.yaml file looks like this:
admin_2fa_login:
path: /admin/2fa/login
defaults:
_controller: "scheb_two_factor.form_controller::form"
admin_2fa_login_check:
path: /admin/2fa/check
And in security.yaml I also added the route at the access_control:
access_control:
- { path: ^/admin/2fa/resend-code, role: IS_AUTHENTICATED_2FA_IN_PROGRESS }
Hope this helps.
Upvotes: 1
Reputation: 2991
To resend a 2-factor-auth-code feel free to inject
Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Email\Generator\CodeGeneratorInterface $codeGenerator
to your Controller or Service.
After this, you can call:
$codeGenerator->reSend($user)
Feel free to see the documentation: https://github.com/scheb/two-factor-bundle/blob/4.x/Resources/doc/providers/email.md#re-send-authentication-code
Upvotes: 1