How to forward to a named route? - symfony

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.

Related

Symfony 6.2 and json_login not save authenticated state

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.

[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',
];
}
}

User object is not accessable in the controller in symfony

I'm trying to use auto login feature in symfony something like firing the login event and setting the user object. The user object is available in that controller but when I try to use other controller the user object says annon instead of showing the logged in user info
Controller A
private function autoLogin($request, $username)
{
$em = $this->getDoctrine()->getManager();
if (!$usr = $em->getRepository('AppBundle:User')->findOneBy(['username' => $username])) {
throw $this->createNotFoundException('User does not exist');
}
$token = new UsernamePasswordToken($usr, $usr->getPassword(), "secured_area", $usr->getRoles());
$this->get('security.token_storage')->setToken($token);
$loginEvent = new InteractiveLoginEvent($request, $token);
$this->get("event_dispatcher")->dispatch("security.interactive_login", $loginEvent);
$user = $this->get('security.token_storage')->getToken()->getUser();
dump($user); // can see user object without any issue
if (!$this->get('security.authorization_checker')->isGranted('ROLE_ADMIN')) {
throw new AccessDeniedException();
}
return $usr;
}
Controller B
public function editAction(Request $request)
{
$user = $this->get('security.token_storage')->getToken()->getUser();
print_r($user); // result is annon.
}
security.yml
security:
encoders:
AppBundle\Entity\User:
algorithm: bcrypt
providers:
doctrine_provider:
entity:
class: AppBundle:User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
secured_area:
anonymous: ~
provider: doctrine_provider
pattern: ^/
form_login:
login_path: security_login
check_path: security_login
csrf_token_generator: security.csrf.token_manager
logout:
path: /logout
target: /login
access_control:
- { path: ^/.*, roles: IS_AUTHENTICATED_ANONYMOUSLY }
I'd assume that you're not using any security setup and the second controller is called after user refreshes the page.
The most likely problem is that your user is not persisted into the session. Symfony\Component\Security\Http\Firewall\ContextListener is responsible for that. If you have a look at onKernelResponse() method you can find out how it does it. Basically it gets token from token storage, serialize it and stores to the session. On the request it does opposite: gets token from session and puts it to token storage.
I'd suggest that you play with the configuration of firewall and set up something like this:
firewalls:
autologin:
pattern: /autologinUrl/
context: autologing
In this case context listener will be called doing session-related stuff and your code should work.

Symfony2 Getting the Current User

I'm developing an api, and I can't get the current user when I try the BasicAuth :
FatalErrorException: Error: Call to a member function getUser() on a non-object
Below you'll find the security.yml part that cause the error (I think it does) :
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
anonymous: true
stateless: true
http_basic:
realm: "REST Service Realm"
provider: fos_userbundle
access_control:
- { path: ^/users/me, role: IS_AUTHENTICATED_FULLY }
I just put the path in access_control I'm testing. I've several more.
I'm using FOSRestBundle as well as FOSUserBundle (as you can see) and I didn't want to put a prefix, because it would be redundant :
// Routing.yml
rest :
type : rest
resource : "routing_rest.yml"
Now the part where the error occur is in the UserController (extending the FOSUser one) :
private function response($data, $status, $groups = array())
{
$currentUser = $this->container->get('security.context')->getToken()->getUser();
if (!$currentUser)
$groups = array("anon");
else
{
if ($currentUser->hasRole("ROLE_SUPER_ADMIN"))
array_push($groups, "admin");
else if ($currentUser->hasRole("ROLE_ADMIN"))
array_push($groups, "admin");
else if ($currentUser->hasRole("ROLE_USER"))
array_push($groups, "user");
}
return $this->view($data, $status)->setSerializationContext(SerializationContext::create()->setGroups($groups));
}
Any idea what's wrong ?
The problem is that your token is null. You have an unknown user if your token does not exist.
$token = $this->container->get('security.context')->getToken();
if (!$token) {
return ('anon.');
}
$user = $token->getUser();

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