2

I made the security process in my symfony app.

That allready works, but if the email-verify link is expired, how to make a new link "request a new link." ? Like is asked on this picture : The link to verify your email has expired

I made the security of symfony, everything works, but, I don't know where I can find how to make the "request a new link" when the link is expired.

Here is the security YAML

    security:
        # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
        password_hashers:
            Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
        # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
        providers:
            # used to reload user from session & other features (e.g. switch_user)
            app_user_provider:
                entity:
                    class: App\Entity\User
                    property: email
        firewalls:
            dev:
                pattern: ^/(_(profiler|wdt)|css|images|js)/
                security: false
            main:
                lazy: true
                provider: app_user_provider
                login_throttling:
                    max_attempts: 3
                logout:
                    path: app_logout
                form_login:
                    login_path: app_login
                    check_path: app_login
                    default_target_path: app_member
                remember_me:
                    secret:   '%kernel.secret%' # required
                    lifetime: 604800 # 1 week in seconds

        access_control:
            # - { path: ^/admin, roles: ROLE_ADMIN }
            - { path: ^/member, roles: ROLE_USER }

Here is the RegistrationController <?php

    namespace App\Controller;

    use App\Entity\User;
    use App\Form\RegistrationFormType;
    use App\Repository\UserRepository;
    use App\Security\EmailVerifier;
    use Doctrine\ORM\EntityManagerInterface;
    use Symfony\Bridge\Twig\Mime\TemplatedEmail;
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\Mime\Address;
    use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
    use Symfony\Component\Routing\Annotation\Route;
    use Symfony\Contracts\Translation\TranslatorInterface;
    use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;

    class RegistrationController extends AbstractController
    {
        private EmailVerifier $emailVerifier;

        public function __construct(EmailVerifier $emailVerifier)
        {
            $this->emailVerifier = $emailVerifier;
        }

        #[Route('/register', name: 'app_register')]
        public function register(Request $request, UserPasswordHasherInterface $userPasswordHasher, EntityManagerInterface $entityManager): Response
        {
            $user = new User();
            $form = $this->createForm(RegistrationFormType::class, $user);
            $form->handleRequest($request);

            if ($form->isSubmitted() && $form->isValid()) {
                // encode the plain password
                $user->setPassword(
                    $userPasswordHasher->hashPassword(
                        $user,
                        $form->get('plainPassword')->getData()
                    )
                );

                $entityManager->persist($user);
                $entityManager->flush();

                // generate a signed url and email it to the user
                $this->emailVerifier->sendEmailConfirmation('app_verify_email', $user,
                    (new TemplatedEmail())
                        ->from(new Address('[email protected]', 'Account Confirmation'))
                        ->to($user->getEmail())
                        ->subject('Please Confirm your Email')
                        ->htmlTemplate('registration/confirmation_email.html.twig')
                );
                // do anything else you need here, like send an email

                return $this->redirectToRoute('app_login');
            }

            return $this->render('registration/register.html.twig', [
                'registrationForm' => $form->createView(),
            ]);
        }

        #[Route('/verify/email', name: 'app_verify_email')]
        public function verifyUserEmail(Request $request, TranslatorInterface $translator, UserRepository $userRepository): Response
        {
            $id = $request->query->get('id');

            if (null === $id) {
                return $this->redirectToRoute('app_register');
            }

            $user = $userRepository->find($id);

            if (null === $user) {
                return $this->redirectToRoute('app_register');
            }

            // validate email confirmation link, sets User::isVerified=true and persists
            try {
                $this->emailVerifier->handleEmailConfirmation($request, $user);
            } catch (VerifyEmailExceptionInterface $exception) {
                $this->addFlash('verify_email_error', $translator->trans($exception->getReason(), [], 'VerifyEmailBundle'));

                return $this->redirectToRoute('app_register');
            }

            // @TODO Change the redirect on success and handle or remove the flash message in your templates
            $this->addFlash('success', 'Your email address has been verified.');

            return $this->redirectToRoute('app_register');
        }
    }

Here is the EmailVerifier

    <?php

    namespace App\Security;

    use Doctrine\ORM\EntityManagerInterface;
    use Symfony\Bridge\Twig\Mime\TemplatedEmail;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\Mailer\MailerInterface;
    use Symfony\Component\Security\Core\User\UserInterface;
    use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
    use SymfonyCasts\Bundle\VerifyEmail\VerifyEmailHelperInterface;

    class EmailVerifier
    {
        public function __construct(
            private VerifyEmailHelperInterface $verifyEmailHelper,
            private MailerInterface $mailer,
            private EntityManagerInterface $entityManager
        ) {
        }

        public function sendEmailConfirmation(string $verifyEmailRouteName, UserInterface $user, TemplatedEmail $email): void
        {
            $signatureComponents = $this->verifyEmailHelper->generateSignature(
                $verifyEmailRouteName,
                $user->getId(),
                $user->getEmail(),
                ['id' => $user->getId()]
            );

            $context = $email->getContext();
            $context['signedUrl'] = $signatureComponents->getSignedUrl();
            $context['expiresAtMessageKey'] = $signatureComponents->getExpirationMessageKey();
            $context['expiresAtMessageData'] = $signatureComponents->getExpirationMessageData();

            $email->context($context);

            $this->mailer->send($email);
        }

        /**
         * @throws VerifyEmailExceptionInterface
         */
        public function handleEmailConfirmation(Request $request, UserInterface $user): void
        {
            $this->verifyEmailHelper->validateEmailConfirmation($request->getUri(), $user->getId(), $user->getEmail());

            $roles = [];
            $roles[] = "ROLE_MEMBER";
            $user->setRoles($roles);
            $user->setIsVerified(true);

            $this->entityManager->persist($user);
            $this->entityManager->flush();
        }
    }
1
  • Please provide enough code so others can better understand or reproduce the problem. Commented Aug 18, 2023 at 22:01

1 Answer 1

1

Create a new action which will: a) Check if user is still not verified b) Regenerate signed email and send it once again

Something like this in the RegistrationController:

    #[Route('/verify/request', name: 'app_request_verification_email')]
    public function requestVerificationEmail(Request $request, TranslatorInterface $translator, UserRepository $userRepository): Response
    {
        $id = $request->query->get('id');

        if (null === $id) {
            return $this->redirectToRoute('app_register');
        }

        $user = $userRepository->find($id);

        if (null === $user) {
            return $this->redirectToRoute('app_register');
        }

        if ($user->isVerified() && $user->hasRole("ROLE_MEMBER")) {
            $this->addFlash('already_verified', $translator->trans($exception->getReason(), [], 'VerifyEmailBundle'));

            return $this->redirectToRoute('app_register');
        }

        $this->emailVerifier->sendEmailConfirmation('app_verify_email', $user,
            (new TemplatedEmail())
                ->from(new Address('[email protected]', 'Account Confirmation'))
                ->to($user->getEmail())
                ->subject('Please Confirm your Email')
                ->htmlTemplate('registration/confirmation_email.html.twig')
            );

        $this->addFlash('success', 'New verification link has been sent');

        return $this->redirectToRoute('app_register');
    }
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.