Last active
August 19, 2022 14:19
-
-
Save ThomasLandauer/668d7353dc5794da62be4cec9e8091ab to your computer and use it in GitHub Desktop.
Magic Link login with Symfony to secure just one route
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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(...); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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