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(...); | |
} | |
} |
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
A "magic link" is a password-less login system. The link looks something like
www.example.com/jruv8bk3c743c8345asgbu
. You send it to your users in an email, they click on the link, and they are logged in instantly.If you have to secure just one route, the solution presented here is way easier than what the Symfony docs https://symfony.com/doc/current/security.html are telling you. However, it does not scale. So if you need to protect a large portion of your website (i.e. many routes/controllers) behind a login system, do follow the Symfony docs and create Authenticators, Handlers, Voters etc.
I created this in Symfony 4.4, but it probably also works in Symfony 3.
Besides the above two files, you need the "user" entity which implements
Symfony\Component\Security\Core\User\UserInterface
. The easiest way is to use Symfony's MakerBundle:In the crated class, you can simplify
getRoles()
to:Now you just need to add a few lines to your "SecretUserProfileController":
To allow users to log out, just offer them a link to
/logout
.Clickable links:
anonymous: lazy
https://symfony.com/blog/new-in-symfony-4-4-lazy-firewalls$this->getUser();
symfony/symfony#36208'noindex, nofollow'
https://developers.google.com/search/reference/robots_meta_tag?hl=enlogout()
https://symfony.com/doc/4.4/security.html#logging-out$this->tokenStorage->setToken(null);
symfony/symfony#36227