Skip to content

Instantly share code, notes, and snippets.

@ThomasLandauer
Last active August 19, 2022 14:19
Show Gist options
  • Save ThomasLandauer/668d7353dc5794da62be4cec9e8091ab to your computer and use it in GitHub Desktop.
Save ThomasLandauer/668d7353dc5794da62be4cec9e8091ab to your computer and use it in GitHub Desktop.
Magic Link login with Symfony to secure just one route
# config/packages/security.yaml
security:
providers:
user_provider: # just an arbitrary name - you won't need it anywhere
entity:
class: App\Entity\User
property: token # the name of the property (i.e. column) of the entity with the magic link
firewalls:
dev: # disables authentication for Symfony's assets and the profiler
pattern: ^/(_(profiler|wdt)|assets)/
security: false
main: # name of your 'firewall' (i.e. login system)
anonymous: lazy # new feature in Symfony 4.4: https://symfony.com/blog/new-in-symfony-4-4-lazy-firewalls For Symfony <4.4, use `anonymous: ~`
logout:
path: /logout
success_handler: App\Controller\SecurityController
<?php
// src/Controller/SecurityController.php
namespace App\Controller;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken;
use App\Entity\User;
class SecurityController extends AbstractController implements LogoutSuccessHandlerInterface
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
/**
* @Route("/{token}", name="Login")
*/
public function login(Request $request, GuardAuthenticatorHandler $guardAuthenticatorHandler, string $token)
{
// TODO: verify the `$token` provided by the user
// If the token is wrong, return:
$response = $this->render(...);
$response->setStatusCode(Response::HTTP_UNAUTHORIZED);
return $response;
// If the token is correct, continue:
$this->getUser(); // only required, if you have `anonymous: lazy` in `config/packages/security.yaml`, see https://github.com/symfony/symfony/issues/36208
// Log the user in:
$postAuthenticationGuardToken = new PostAuthenticationGuardToken($user, 'main', $user->getRoles()); // 'main' is the name of your firewall in `security.yaml`
$guardAuthenticatorHandler->authenticateWithToken($postAuthenticationGuardToken, $request, 'main');
// redirect the user to have the "magic link" disappear from the browser's url bar:
$response = $this->redirectToRoute('SecretUserProfile', ['id' => $user->getId()], Response::HTTP_SEE_OTHER);
$response->headers->set('X-Robots-Tag', 'noindex, nofollow'); // https://developers.google.com/search/reference/robots_meta_tag?hl=en
return $response;
}
/**
* @Route("/logout", name="Logout", methods={"GET"})
*/
public function logout()
{
// can be empty, see https://symfony.com/doc/4.4/security.html#logging-out
}
public function onLogoutSuccess(Request $request)
{
// Workaround for https://github.com/symfony/symfony/issues/36227
if ($this->getUser())
{
$this->tokenStorage->setToken(null);
}
return $this->render(...);
}
}
@ThomasLandauer
Copy link
Author

This is meanwhile obsolete, due to Symfony's "Authenticator-Based Security", introduced in Symfony 5.1: https://symfony.com/blog/new-in-symfony-5-1-updated-security-system

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment