I'm trying to split the authentication of a user from the authentication of an admin.
So I created 2 firewalls and 2 different access controls.
My security.yaml looks like that:
enable_authenticator_manager: true
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
providers:
owner_authentication:
entity:
class: App\Entity\Merchant\Owner
property: emailAddress
user_authentication:
entity:
class: App\Entity\User\User
property: emailAddress
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
user:
lazy: true
pattern: ^/admin/login/
provider: user_authentication
user_checker: App\Security\AuthentificableModelChecker
form_login:
provider: user_authentication
default_target_path: app.dashboard.index
use_referer: true
use_forward: false
login_path: app.authorization.admin_login
check_path: app.authorization.admin_login
username_parameter: login[emailAddress]
password_parameter: login[password]
logout:
path: app.authorization.logout
target: app.authorization.admin_login
main:
lazy: true
pattern: ^/
provider: owner_authentication
user_checker: App\Security\AuthentificableModelChecker
form_login:
provider: owner_authentication
default_target_path: app.dashboard.index
use_referer: true
use_forward: false
login_path: app.authorization.login
check_path: app.authorization.login
username_parameter: login[emailAddress]
password_parameter: login[password]
logout:
path: app.authorization.logout
target: app.authorization.login
access_control:
- { path: ^/admin/login, roles: PUBLIC_ACCESS}
- { path: ^/login, roles: PUBLIC_ACCESS }
- { path: ^/, roles: ROLE_USER }
Everything works fine on the main firewall, but when I submit the button using user (admin) firewall, the login page refreshes itself and nothing happens. I don't have any error.
** If I add user(admin) login on the main firewall, then /admin/login will work fine and the other one won't work anymore.
When I call $authenticationUtils->getLastAuthenticationError() I don't get any error. But the validations don't work either.
This is how my Controller looks like:
public function adminLogin(AuthenticationUtils $authenticationUtils): Response
{
if ($this->getUser()) {
return $this->redirectToRoute('app.dashboard.index');
}
$loginForm = $this->createForm(LoginType::class, ['emailAddress' => $authenticationUtils->getLastUsername()]);
return $this->renderForm('app/pages/authorization/admin_login.html.twig', [
'title' => 'Log in',
'login_form' => $loginForm,
'error' => $authenticationUtils->getLastAuthenticationError()?->getMessageKey(),
]);
}
It's the same problem this guy had: https://grafikart.fr/forum/35234 but I can't find any solution for this.
Finally, I found the answer so I'll post it here: https://stackoverflow.com/a/42352112/8003007
What I had to do was to add a context: my_context in both firewalls.
It was a difficult option to identify because it doesn't appear in the official and current Symfony documentation but only in the previous ones, like Symfony 3.4.
Related
I am writing this because previous answers to Using multiple firewalls cause ERR_TOO_MANY_REDIRECTS in Symfony 2 have not been helpful. "Main" firewall seem to work just fine, "Admin" is the one causing problems. Every time I try to enter the path "http://localhost:8000/admin" it redirects to"http://localhost:8000/admin_login" as it should, but goes into redirect loop and crashes with the error named above.
security.yaml
security:
encoders:
App\Entity\User:
algorithm: bcrypt
Symfony\Component\Security\Core\User\User: plaintext
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
providers:
chain_provider:
chain:
providers: [in_memory, db_provider]
in_memory:
memory:
users:
theadmin:
password: iamadmin
roles: 'ROLE_SUPER_ADMIN'
db_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
admin:
pattern: /admin
anonymous: ~
form_login:
username_parameter: _username
login_path: /admin_login
check_path: /admin_login
provider: in_memory
default_target_path: admin
logout:
path: /admin_logout
target: /
main:
pattern: /
anonymous: ~
form_login:
username_parameter: _email
login_path: /login
check_path: /login
provider: db_provider
default_target_path: welcome
logout:
path: /logout
target: /
access_control:
- { path: ^/welcome, roles: ROLE_USER }
- { path: ^/admin, roles: ROLE_SUPER_ADMIN }
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin_login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
AdminSecurityController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class AdminSecurityController extends AbstractController
{
/**
* #Route("/admin_login", name="admin_login")
*/
public function admin_login(Request $request, AuthenticationUtils $utils)
{
$error = $utils->getLastAuthenticationError();
$auth_checker = $this->get('security.authorization_checker');
if ($auth_checker->isGranted('ROLE_SUPER_ADMIN')) {
return $this->render('admin/dashboard.html.twig', [
'controller_name' => 'AdminController',
]);
} else{
return $this->render('admin_security/admin_login.html.twig', [
'error' => $error
]);
}
}
/**
* #Route("/admin_logout", name="admin_logout")
*/
public function admin_logout()
{
}
}
Access control entries are analyzed from the top to the bottom. So, you need to place the ^/admin_login entry before the ^/admin.
Imagine how the security component is currently set:
You visit the login form then press submit
You (guest) are redirected to the /admin_login path
Security component goes through the entries and matches the /admin_login to the ^/admin entry
Because it requires ROLE_SUPER_ADMIN, you thus end up with a loop
Remember to clear the cache afterwards.
Trying to use the tutorial on this pages
The problem is that i'm always going back to login form, and can't finf what i am missing.
I'm using Synfony 3 version.
Thanks you for your help :)
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext
providers:
in_memory:
memory:
users:
ryan:
password: ryanpass
roles: 'ROLE_USER'
admin:
password: admin
roles: 'ROLE_USER'
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
home:
pattern: ^/home$
form_login:
login_path: login
check_path: login
always_use_default_target_path: true
main:
anonymous: ~
=========== routing
login:
path: /login
defaults: { _controller: AppBundle:Security:login }
btw_user:
resource: "#BTWUserBundle/Resources/config/routing.yml"
prefix: /
btw_menus:
resource: "#BTWMenusBundle/Resources/config/routing.yml"
prefix: /
app:
resource: "#AppBundle/Controller/"
type: annotation
btw_home:
resource: "#BTWHomeBundle/Resources/config/routing.yml"
prefix: /
============ controller
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class SecurityController extends Controller {
public function loginAction(Request $request) {
//var_dump($request);
$authenticationUtils = $this->get("security.authentication_utils");
//var_dump($_POST);
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError ();
var_dump($error);
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
var_dump($lastUsername);
return $this->render ( 'security/login.html.twig',
array(
// last username entered by the user
'last_username' => $lastUsername,
'error' => $error,
));
}
}
I had the same issue too, this solve my problem:
context: session_key Read more
AND creating additional form_login at admin_login yaml block.
This is not really best way to fix this problem and I would make tests for avoiding this, but it works!
Usage:
admin_login:
pattern: ^/admin/login$
anonymous: ~
provider: user_provider
context: session_key
form_login:
login_path: admin_login
check_path: admin_login
default_target_path: admin_category_index
admin:
pattern: ^/admin
provider: user_provider
context: session_key
form_login:
login_path: admin_login
check_path: admin_login
default_target_path: admin_category_index
logout:
path: /logout
target: /
invalidate_session: false
I am using Symfony 2.7, installed FOSuserbundle, everything is working fine. My users are being created and I can login with them, but logged in users can enter the log in page, which doesn't seem logic to me. I've looked a bit for answers and found out I have to configure my security.yml file, but it still doesn't work, anyone can enter the login page.
I found that I have to set
- { path: ^/, role: ROLE_USER}
but that gives me a redirect loop.
Here is what I have in it
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: security.csrf.token_manager # Use form.csrf_provider instead for Symfony <2.4
logout: true
anonymous: true
# activate different ways to authenticate
# http_basic: ~
# http://symfony.com/doc/current/book/security.html#a-configuring-how-your-users-will-authenticate
# form_login: ~
# http://symfony.com/doc/current/cookbook/security/form_login_setup.html
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/profile, role: ROLE_USER }
- { path: ^/admin/, role: ROLE_ADMIN }
Override the loginAction of tge SecurityController, this way:
class SecurityController extends BaseSecurityController
{
public function loginAction(Request $request)
{
if( $this->container->get('security.context')->isGranted('IS_AUTHENTICATED_FULLY') {
return $this->redirect($this->generateUrl('any_route_you_want'))
}
return parent::loginAction($request);
}
}
Edit : To learn how to override any part of a Bundle, this would be helpful
Allready tryed this Solution ?
FOSUserBundle redirect from login page after logged in
PHP:
You can override FOSUserBundle\Controller\SecurityController and add the following code at the top of loginAction.
use Symfony\Component\HttpFoundation\RedirectResponse;
// ...
public function loginAction(Request $request)
{
$securityContext = $this->container->get('security.context');
$router = $this->container->get('router');
if ($securityContext->isGranted('ROLE_ADMIN')) {
return new RedirectResponse($router->generate('admin_home'), 307);
}
if ($securityContext->isGranted('ROLE_USER')) {
return new RedirectResponse($router->generate('user_home'), 307);
}
// ...
yaml:
The easier solution is to add these two lines to your app/config/security.yml:
always_use_default_target_path & default_target_path, e.g.:
firewalls:
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
login_path: /login
check_path: /login_check
always_use_default_target_path: false
default_target_path: /your/start/path/
Is it possible to require a sandbox login with a parameter sandbox: true and not when sandbox: false? The closest I can get is creating a voter that does this:
voter snippet:
if (!$user instanceof UserInterface && $this->sandbox) {
return VoterInterface::ACCESS_DENIED;
}
return VoterInterface::ACCESS_GRANTED;
But this is not sufficient because it needs
* #Security("has_role('IS_AUTHENTICATED_ANONYMOUSLY')")
at the top of a controller to be triggered. And then even when sandbag: false, login is required!
security.yml
firewalls:
main:
pattern: ^/
access_denied_url: /login
form_login:
provider: fos_userbundle
failure_path: /message/inactive
failure_forward: true
logout: true
anonymous: ~
access_control:
- { path: ^/, role: IS_AUTHENTICATED_ANONYMOUSLY }
So does the sandbox switch need to get inside security.yml? If so, how?
I am having this rather common problem of Symfony login failing with this error message:
Unable to find the controller for path "/auth/login_check".
The common answer to this problem is that the login_check route isn't behind the firewall, but in my case it is! Here are my config files:
app/config/security.yml
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
auth_area:
pattern: ^/auth/(login|login_check)
anonymous: ~
secured_area:
pattern: ^/
form_login:
login_path: /auth/login
check_path: /auth/login_check
logout:
path: /auth/logout
target: /
app/config/routing.yml
login:
pattern: /auth/login
defaults: { _controller: AuthBundle:Default:login }
login_check:
pattern: /auth/login_check
logout:
pattern: /auth/logout
AuthBundle/Controller/DefaultController.php
public function loginAction()
{
$form = $this->createForm(new UserType(), new User());
return $this->render('AuthBundle:Default:login.html.twig', array(
'form' => $form->createView(),
'action' => $this->generateUrl('login_check'),
));
}
AuthBundle/Form/Type/UserType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('username', 'text', array('label' => 'Login:'));
$builder->add('password', 'password', array('label' => 'Password:'));
}
AuthBundle/Resources/views/Default/login.html.twig
...
<form action="{{ action }}" method="POST">
{{ form_widget(form) }}
<input type="submit" value="Login" />
</form>
...
My form is created with the route login_check as target (/auth/login_check URL).
The secured_area firewall is supposed to match all URLs, so /auth/login_check should be in it. Yet, I keep getting this error. What am I doing wrong?
The login_check url must behind the same firewall as the using firewall. use ACLs instead.
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
secured_area:
pattern: ^/
form_login:
login_path: /auth/login
check_path: /auth/login_check
logout:
path: /auth/logout
target: /
anonymous: ~
access_control:
- { path: ^/auth/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, role: ROLE_USER }
app/config/security.yml
secured_area:
pattern: ^/
form_login:
login_path: login #this is routing name
check_path: login_check #this is routing name
logout:
path: logout #this is routing name
target: /
Edit
[1] you can try delete this:
auth_area:
pattern: ^/auth/(login|login_check)
anonymous: ~
[2] your path better that:
<form action="{{ path('login_check') }}" method="POST">
Edit
It is example for login
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
secured_area:
pattern: ^/
anonymous: ~
form_login:
login_path: login
check_path: login_check
always_use_default_target_path: true
default_target_path: /top
logout:
path: /user_logout
target: /
routing.yml
login_check:
pattern: /login_check
defaults: { _controller: AcmeUserBundle:Default:loginCheck }
DefaultController.php
public function loginCheckAction()
{
return $this->redirect($this->generateUrl('login'));
}