Symfony 6.2 and json_login not save authenticated state - symfony

I have config (security.yaml):
firewalls:
login:
pattern: ^/api/login
json_login:
username_path: email
password_path: password
check_path: api_login
main:
login_throttling:
max_attempts: 3
lazy: true
provider: app_user_provider
custom_authenticator: App\Security\CustomAuthenticator
<?php
namespace App\Controller;
use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
#[Route('/api', name: 'api_')]
class ApiController extends AbstractController
{
#[Route('/login', name: 'login', methods: ['POST'])]
public function login(#[CurrentUser] ?User $user): Response
{
if (null === $user) {
return $this->json([
'message' => 'missing credentials',
], Response::HTTP_UNAUTHORIZED);
}
return $this->json(['user' => $user->getUserIdentifier()]);
}
}
In debug-panel I see Authenticated and i see real getUserIdentifier(). But if I reload the page, then I'm not logged in again. If you move json_login to the main block, then everything works. What is missing?
I tried different custom authorizers, but it didn't help. I also looked at the open repositories in the github but all examples are the same

You don't have to create separate firewall only for login path - add entries in your main firewall.
I cannot find it now, but I can bet that somewhere in Symfony Security docs there is information that login should not have separate firewall. But from docs:
Each firewall is like a separate security system, being authenticated in one firewall doesn't make you authenticated in another one.
So, you are authenticating in login firewall, but then navigating to any endpoint protected by main will not work.

Related

Symfony 4.4 User checker on login process not triggered

I am implementing user registration process on Symfony 4.4 (without bundle) and I am stuck at the last step.
So far, this is what I have done:
Registration form is created
When user submit registration form he is well added to the database. A field activation token in user entity is fullfill.
An email with the activation token as parameter is automatically send to the user to activate his account
If the user click on the link, activation token field is set to 'null' in the user entity
In a user checker I check if the activation token is null
If activation token is not null I need to refuse the login and redirect the user to homepage with a flash message :I am stuck on this part, my User checker is not triggered. User can login with token not null.
Here is my user checker:
namespace App\Security;
use App\Exception\AccountDeletedException;
use App\Security\User as AppUser;
use Symfony\Component\Security\Core\Exception\AccountExpiredException;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\DisabledException;
class UserChecker implements UserCheckerInterface
{
public function checkPreAuth(UserInterface $user)
{
if (!$user instanceof AppUser) {
return;
}
// User account is not validated
if ($user->getValidationToken() !== null) {
throw new DisabledException('User account is not activated');
}
}
public function checkPostAuth(UserInterface $user)
{
if (!$user instanceof AppUser) {
return;
}
// user account is expired, the user may be notified
if ($user->isExpired()) {
throw new AccountExpiredException('...');
}
}
}
Security.yalm:
security:
encoders:
App\Entity\User:
algorithm: auto
# 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
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: lazy
provider: app_user_provider
guard:
authenticators:
- App\Security\AppCustomAuthenticator
logout:
path: app_logout
user_checker: App\Security\UserChecker
Change :
use App\Security\User as AppUser
To
use App\Entity\User as AppUser;
(or whatever namespace you have on your User class)
Side note : on preAuth, you can check for the presence of the token and not if valid or not since the auth is not passed... this should be done on real Authentification, or post it...

[Symfony 5]Confirmation message after logout

On Symfony 5, using the built-in login system, it seems impossible to add a confirmation message after logout. I have followed strictly the steps described on the official website. Unfortunately, the method logout inside the SecurityController is useless. I'm redirected directly on the login page.
Here you will have my security.yaml file :
security:
encoders:
App\Entity\User:
algorithm: auto
# 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: lazy
provider: app_user_provider
guard:
authenticators:
- App\Security\LoginFormAuthenticator
logout:
path: logout
target: login
remember_me:
secret: '%kernel.secret%'
lifetime: 604800 # 1 week in seconds
path: home
always_remember_me: true
# 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: ^/logout$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: IS_AUTHENTICATED_FULLY }
- { path: ^/admin, roles: [IS_AUTHENTICATED_FULLY, ROLE_ADMIN] }
- { path: ^/profile, roles: [IS_AUTHENTICATED_FULLY, ROLE_USER] }
And the Controller :
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class SecurityController extends AbstractController
{
public function login(AuthenticationUtils $authenticationUtils): Response
{
if ($this->getUser()) {
return $this->redirectToRoute('home');
}
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
return $this->render('security/login.html.twig', ['last_username' => null, 'error' => $error]);
}
public function logout()
{
throw new \Exception('Don\'t forget to activate logout in security.yaml');
}
}
?>
Thank you for your help !
For anyone who's wondering how exactly they can implement the external redirect with the new logout customization:
As stated in the documentation, create a new CustomLogoutListener class and add it to your services.yml configuration.
The CustomLogoutListener class should implement the onSymfonyComponentSecurityHttpEventLogoutEvent method, which will receive the LogoutEvent as a parameter, that will allow you to set the response:
namespace App\EventListener;
use JetBrains\PhpStorm\NoReturn;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Event\LogoutEvent;
class CustomLogoutListener
{
/**
* #param LogoutEvent $logoutEvent
* #return void
*/
#[NoReturn]
public function onSymfonyComponentSecurityHttpEventLogoutEvent(LogoutEvent $logoutEvent): void
{
$logoutEvent->setResponse(new RedirectResponse('https://where-you-want-to-redirect.com', Response::HTTP_MOVED_PERMANENTLY));
}
}
# config/services.yaml
services:
# ...
App\EventListener\CustomLogoutListener:
tags:
- name: 'kernel.event_listener'
event: 'Symfony\Component\Security\Http\Event\LogoutEvent'
dispatcher: security.event_dispatcher.main
logout method in SecurityController won’t actually get hit because Symfony will intercept the request.
if you need to do something after logout you can use logout success handler
namespace App\Logout;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface;
class MyLogoutSuccessHandler implements LogoutSuccessHandlerInterface
{
/**
* {#inheritdoc}
*/
public function onLogoutSuccess(Request $request)
{
// you can do anything here
return new Response('logout successfully'); // or render a twig template here, it's up to you
}
}
and you can register your logout success handler to security.yaml
firewalls:
main:
anonymous: lazy
provider: app_user_provider
guard:
authenticators:
- App\Security\LoginFormAuthenticator
logout:
path: logout
success_handler: App\Logout\MyLogoutSuccessHandler # assume you have enable autoconfigure for servicess or you need to register the handler
As of version 5.1 LogoutSuccessHandlerInterface is deprecated, it is recommended to use LogoutEvent.
Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface is deprecated
But there are no examples or information about LogoutEvent in the official documentation
Thanks to Indra Gunawan, this solution works. My goal was to redirect to the login page with a message like "You've been successfully logged out".
In that case, the LogoutSuccessHandler must be adapted to route to the login page :
namespace App\Logout;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
class MyLogoutSuccessHandler extends AbstractController implements LogoutSuccessHandlerInterface
{
private $urlGenerator;
public function __construct(UrlGeneratorInterface $urlGenerator)
{
$this->urlGenerator = $urlGenerator;
}
public function onLogoutSuccess(Request $request)
{
return new RedirectResponse($this->urlGenerator->generate('login', ['logout' => 'success']));
}
}
The route login need to be defined in routes.yaml :
login:
path: /login
controller: App\Controller\SecurityController::login
logout:
path: /logout
methods: GET
In that case, when logout, you will be redirected on an url like : /login?logout=success
Lastely, you can catch logout parameter in twig template like :
{%- if app.request('logout') -%}
<div class="alert alert-success">{% trans %}Logout successful{% endtrans %}</div>
{%- endif -%}
Here is the doc for the LogoutEvent :
https://symfony.com/blog/new-in-symfony-5-1-simpler-logout-customization
You must create an event and implement the onSymfonyComponentSecurityHttpEventLogoutEvent's method.
Its work for me
If you are using Symfony 6 and above you can use as bellow
<?php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Event\LogoutEvent;
class LogoutListener implements EventSubscriberInterface
{
public function onLogout(LogoutEvent $logoutEvent): void
{
// Do your stuff
}
public static function getSubscribedEvents(): array
{
return [
LogoutEvent::class => 'onLogout',
];
}
}

symfony/skeleton logged in User persisted in session, but not available in getUser()

I've set up a simple symfony/skeleton application that contains user entity and authentication.
When after successful authentication I call a route that contains the following code:
public function index(Security $security)
{
$username = $security->getUser()->getUsername();
This results in an error:
Call to a member function getUsername() on null
So obviously the security component does not now about the currently logged in user.
The session however does contain the current UsernamePasswordToken (I checked using dump).
The same code works fine when I use website-skeleton.
What is missing in skeleton?
My firewall is setup like that:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
logout:
path: ^/user/logout
json_login:
check_path: /user/login
You can get the authenticated user from a controller that extends AbstractController just by using $this->getUser();.

How to forward to a named route?

How do I do forwarding a Symfony2 (2.2) class for which I do not know the name?
Please note, I do NOT want to redirect, since I must POST and I would prefer the POST parameters not to be sent via the browser.
The path's class is not obvious as it's the internal 'login_check'.
\\routing.yml
login:
pattern: /login
defaults: { _controller : MyAppBundle:Security:login }
login_check:
pattern: /login_check
logout:
pattern: /logout
If credentials are available from elsewhere, I want to just return a response as follows:
// SecurityController.php
...
class SecurityController extends Controller{
public function loginAction(){
if($test_if_credentials_present){
$response = $this->forward('login_check',
array('_username' => $username, '_password' => $password);
return $response;
}
}
}
This fails as login_check is a route, not a class.
Is there some way to use router to find what class login_check corresponds to?
I could hard-code it also by knowing what the class name is from router:debug
php app/console router:debug login_check
Output:
[router] Route "login_check"
Name login_check
Path /login_check
Host ANY
Scheme ANY
Method ANY
Class Symfony\Component\Routing\Route
Defaults
Requirements NO CUSTOM
Options compiler_class: Symfony\Component\Routing\RouteCompiler
Path-Regex #^/login_check$#s
Thus, no class listed under Defaults.

Changing default check_path behavior in Symfony2 [duplicate]

I need to disable redirection after login check, because I need to get only that the login was success or not. After submission /login_check url give me the right data, but keep redirecting to /login (on failure).
/login is blank after that.
I am trying to set up login form using extjs 4 so I need to validate trough an ajax post request.
login_check should authenticate, create user session and return whether it was success or failure, but no forwarding anywhere.
my login.html.twig looks like:
{% if is_granted("IS_AUTHENTICATED_REMEMBERED") %}
{ success:true }
{% else %}
{ success: false }
{% endif %}
and in security.yml:
firewalls:
main:
form_login:
provider: fos_userbundle
failure_path: null
failure_forward: false
Create an authentication handler:
namespace YourVendor\UserBundle\Handler;
// "use" statements here
class AuthenticationHandler
implements AuthenticationSuccessHandlerInterface,
AuthenticationFailureHandlerInterface
{
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
if ($request->isXmlHttpRequest()) {
$result = array('success' => true);
return new Response(json_encode($result));
} else {
// Handle non XmlHttp request here
}
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
if ($request->isXmlHttpRequest()) {
$result = array('success' => false);
return new Response(json_encode($result));
} else {
// Handle non XmlHttp request here
}
}
}
Register the handler as a service:
services:
authentication_handler:
class: YourVendor\UserBundle\Handler\AuthenticationHandler
Register the service in the firewall:
firewalls:
main:
form_login:
success_handler: authentication_handler
failure_handler: authentication_handler
This is a rough example to give you the general idea — you'll need to figure out the details by yourself. If you're stuck and need further clarifications, put your questions in the comments and I'll try to elaborate the example.
The normal symfony flow is to redirect you to a login page if you are not logged in, which works fine for humans. But you seems to be looking for a programmatic solution.
Have you tried setting _target_path in your form, to declare what the "next page" should be? Symfony is always going to forward you somewhere, but you can set that somewhere to wherever you want.
I found these two pages useful for describing the inner workings of the login form:
How to customize your form login (search for _target_path)
Security page in the handbook

Resources