Symfony2 Security log user manually - security

I want to log user manually in Symfony2. (I use fosuserbundle).
The authentication will be triggered in custom route like this /login/auto
Here is my controller code which match with /login/auto
public function loginAction(){
$em = $this->container->get('doctrine')->getManager();
$users = $em->getRepository('MybundleMainBundle:User');
$user = $users->findOneByEmail("user#user.com");
$securityContext = $this->get('security.context');
$token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
$securityContext->setToken($token);
$this->get('session')->set('_security_'.'main', serialize($token));
return new RedirectResponse($this->generateUrl('home'));
}
But after the redirection, I'm redirected automatically to /login and not /home so the authentification failed
Here is my security file config :
security:
providers:
fos_userbundle:
id: fos_user.user_provider.username
encoders:
FOS\UserBundle\Model\UserInterface: sha512
firewalls:
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
always_use_default_target_path: true
logout: true
anonymous: true
switch_user: true
remember_me:
key: %secret%
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
access_control:
- { path: ^/login, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, role: ROLE_USER }
role_hierarchy:
ROLE_USER: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
Thanks for your help

Looks very similar to mine. Maybe you should not write into the session. Or the token needs the (hashed) password. Try it, here's a working code ;)
public function demologinAction(Request $request)
{
$dm = $this->get('doctrine.odm.mongodb.document_manager');
$repo = $dm->getRepository('AcmeUserBundle:User');
$user = $repo->findOneByUsername('demo');
if (!$user) {
throw $this->createNotFoundException('No demouser found!');
}
$token = new UsernamePasswordToken($user, $user->getPassword(), 'main', $user->getRoles());
$context = $this->get('security.context');
$context->setToken($token);
$router = $this->get('router');
$url = $router->generate('dashboard_show');
return $this->redirect($url);
}

Why are you trying to log a user in manually?
I may be wrong, but if the reason is so that you can run some postLogin code, it would be easier to use the built in login functionality, but setup a listener on the login action as a service. Then add your login code in there.
Sevice definition would be:
user.login:
class: You\Bundle\EventListener\EventListener
arguments: [#doctrine.orm.entity_manager, #service_container]
tags:
- { name: kernel.event_listener, event: security.interactive_login, method: onLogin }
And your event listener might be:
public function onLogin(InteractiveLoginEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
$user->setLastLoggedInAt(new \DateTime());
$user->setLoginCount($user->getLoginCount() + 1);
$this->manager->flush();
}

Related

Two Login in Symfony 6

I had problems with tow login in Symfony 6. When I access to /admin/login I get the error "Class App\Controller\AuthenticationUtils does not exist"
here is my security.yaml
security:
encoders:
App\Entity\User:
algorithm: auto
App\Entity\AdminUser:
algorithm: auto
enable_authenticator_manager: true
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
App\Entity\User:
algorithm: auto
App\Entity\AdminUser:
algorithm: auto
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: email
admin_user_provider:
entity:
class: App\Entity\AdminUser
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
admin:
pattern: ^/admin
lazy: true
provider: admin_user_provider
custom_authenticator: App\Security\AdminLoginFormAuthenticator
logout:
path: admin_logout
# where to redirect after logout
# TODO target: app_any_route
main:
lazy: true
provider: app_user_provider
custom_authenticator: App\Security\LoginFormAuthenticator
logout:
path: app_logout
# where to redirect after logout
# TODO target: app_any_route
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
# 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:
# TODO - { path: ^/admin, roles: ROLE_ADMIN }
# TODO - { path: ^/profile, roles: ROLE_USER }
And it is my AdminSecurityController.php
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class AdminSecurityController extends AbstractController
{
/**
* #Route("/admin/security", name="admin_security")
*/
public function index(): Response
{
return $this->render('admin_security/index.html.twig', [
'controller_name' => 'AdminSecurityController',
]);
}
/**
* #Route("/admin/login", name="admin_login")
*/
public function login(AuthenticationUtils $authenticationUtils): Response
{
// get the login error if there is one
// $error = $authenticationUtils->getLastAuthenticationError();
// // last username entered by the user
// $lastUsername = $authenticationUtils->getLastUsername();
return $this->render('admin_login/index.html.twig'); //, ['last_username' => $lastUsername, 'error' => $error]
}
/**
* #Route("/admin/logout", name="admin_logout")
*/
public function logout()
{
throw new \Exception('This method can be blank - it will be intercepted by the logout key on your firewall');
}
}
I attach some images with information about the error
I have no idea about the problem, hope you can help me!!
Thank you !!
You're missing your import of the
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
at the top of your controller

Symfony 4.4 Security / Session is saved but not used

Full config files are available bellow.
My website is using 2 guards at differents entries :
lexik_jwt_authentication.jwt_token_authenticator to access the ^/api routes
App\Security\LoginAuthenticator to access ^/secured routes
The JWT authetification is working well, allowing users to call a API plateform routes such as localhost/api/types
The problem is about using the LoginAuthenticator session. This is the basic symfony configuration (auto-generated files with php bin/console make:auth)
When login with correct user/password, the guard save a session to app/var/sessions/dev/sess_ras4up86e1c8a1bs9khr5t7scg
When the session is saved and set, the LoginAuthenticator trigger onAuthenticationSuccess function redirecting the user to route name `test.
But the testController is not working, redirecting the user to login page (302 HTTP code). It also throwing the following error in var/log/dev/dev.log :
[2021-07-08 13:07:40] request.INFO: Matched route "app_login". {"route":"app_login","route_parameters":{"_route":"app_login","_controller":"App\\Controller\\SecurityController::login"},"request_uri":"https://krang.local/login","method":"POST"} []
[2021-07-08 13:07:40] security.DEBUG: Checking for guard authentication credentials. {"firewall_key":"login","authenticators":1} []
[2021-07-08 13:07:40] security.DEBUG: Checking support on guard authenticator. {"firewall_key":"login","authenticator":"App\\Security\\LoginAuthenticator"} []
[2021-07-08 13:07:40] security.DEBUG: Calling getCredentials() on guard authenticator. {"firewall_key":"login","authenticator":"App\\Security\\LoginAuthenticator"} []
[2021-07-08 13:07:40] security.DEBUG: Passing guard token information to the GuardAuthenticationProvider {"firewall_key":"login","authenticator":"App\\Security\\LoginAuthenticator"} []
[2021-07-08 13:07:40] doctrine.DEBUG: SELECT t0.id AS id_1, t0.username AS username_2, t0.password AS password_3, t0.is_active AS is_active_4, t0.roles AS roles_5, t0.customer_id AS customer_id_6 FROM users t0 WHERE t0.username = ? LIMIT 1 ["matthieu"] []
[2021-07-08 13:07:40] security.INFO: Guard authentication successful! {"token":"[object] (Symfony\\Component\\Security\\Guard\\Token\\PostAuthenticationGuardToken: PostAuthenticationGuardToken(user=\"matthieu\", authenticated=true, roles=\"ROLE_USER, ROLE_ADMIN\"))","authenticator":"App\\Security\\LoginAuthenticator"} []
[2021-07-08 13:07:40] security.DEBUG: Guard authenticator set success response. {"response":"[object] (Symfony\\Component\\HttpFoundation\\RedirectResponse: HTTP/1.0 302 Found\r\nCache-Control: no-cache, private\r\nDate: Thu, 08 Jul 2021 11:07:40 GMT\r\nLocation: /secured/backmarketProducts\r\n\r\n<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"UTF-8\" />\n <meta http-equiv=\"refresh\" content=\"0;url='/secured/backmarketProducts'\" />\n\n <title>Redirecting to /secured/backmarketProducts</title>\n </head>\n <body>\n Redirecting to /secured/backmarketProducts.\n </body>\n</html>)","authenticator":"App\\Security\\LoginAuthenticator"} []
[2021-07-08 13:07:40] security.DEBUG: Remember me skipped: it is not configured for the firewall. {"authenticator":"App\\Security\\LoginAuthenticator"} []
[2021-07-08 13:07:40] security.DEBUG: The "App\Security\LoginAuthenticator" authenticator set the response. Any later authenticator will not be called {"authenticator":"App\\Security\\LoginAuthenticator"} []
[2021-07-08 13:07:40] request.INFO: Matched route "test". {"route":"test","route_parameters":{"_route":"test","_controller":"App\\Controller\\BackMarketController::productsList"},"request_uri":"https://krang.local/secured/backmarketProducts","method":"GET"} []
[2021-07-08 13:07:40] security.DEBUG: Checking for guard authentication credentials. {"firewall_key":"secured","authenticators":1} []
[2021-07-08 13:07:40] security.DEBUG: Checking support on guard authenticator. {"firewall_key":"secured","authenticator":"App\\Security\\LoginAuthenticator"} []
[2021-07-08 13:07:40] security.DEBUG: Guard authenticator does not support the request. {"firewall_key":"secured","authenticator":"App\\Security\\LoginAuthenticator"} []
[2021-07-08 13:07:40] security.INFO: An AuthenticationException was thrown; redirecting to authentication entry point. {"exception":"[object] (Symfony\\Component\\Security\\Core\\Exception\\AuthenticationCredentialsNotFoundException(code: 0): A Token was not found in the TokenStorage. at /home/matthieu/krang/webservice/vendor/symfony/security-http/Firewall/AccessListener.php:69)"} []
[2021-07-08 13:07:40] security.DEBUG: Calling Authentication entry point. [] []
[2021-07-08 13:07:40] request.INFO: Matched route "app_login". {"route":"app_login","route_parameters":{"_route":"app_login","_controller":"App\\Controller\\SecurityController::login"},"request_uri":"https://krang.local/login","method":"GET"} []
[2021-07-08 13:07:40] security.DEBUG: Checking for guard authentication credentials. {"firewall_key":"login","authenticators":1} []
[2021-07-08 13:07:40] security.DEBUG: Checking support on guard authenticator. {"firewall_key":"login","authenticator":"App\\Security\\LoginAuthenticator"} []
[2021-07-08 13:07:40] security.DEBUG: Guard authenticator does not support the request. {"firewall_key":"login","authenticator":"App\\Security\\LoginAuthenticator"} []
[2021-07-08 13:07:40] security.INFO: Populated the TokenStorage with an anonymous Token. [] []
[2021-07-08 13:07:40] request.INFO: Matched route "_wdt". {"route":"_wdt","route_parameters":{"_route":"_wdt","_controller":"web_profiler.controller.profiler::toolbarAction","token":"ad25b7"},"request_uri":"https://krang.local/_wdt/ad25b7","method":"GET"} []
We can see this error :
[2021-07-08 13:07:40] security.DEBUG: Guard authenticator does not support the request. {"firewall_key":"secured","authenticator":"App\\Security\\LoginAuthenticator"} []
[2021-07-08 13:07:40] security.INFO: An AuthenticationException was thrown; redirecting to authentication entry point. {"exception":"[object] (Symfony\\Component\\Security\\Core\\Exception\\AuthenticationCredentialsNotFoundException(code: 0): A Token was not found in the TokenStorage. at /home/matthieu/krang/webservice/vendor/symfony/security-http/Firewall/AccessListener.php:69)"} []
Guard authenticator does not support the request
What does this mean ? I give you some useful code to checkout :
security.yaml : (full file)
security:
encoders:
App\Entity\User:
algorithm: auto
role_hierarchy:
ROLE_USER : "ROLE_USER"
ROLE_ADMIN : "ROLE_ADMIN"
ROLE_SUPERADMIN : "ROLE_SUPERADMIN"
providers:
entity_provider:
entity:
class: App\Entity\User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login:
pattern: ^/login
stateless: true
anonymous: true
json_login:
check_path: /login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
guard:
authenticators:
- App\Security\LoginAuthenticator
logout:
path: app_logout
# where to redirect after logout
# target: app_any_route
docs:
pattern: ^/docs
stateless: true
anonymous: true
secured:
pattern: ^/secured
stateless: true
provider: entity_provider
guard:
authenticators:
- App\Security\LoginAuthenticator
api:
pattern: ^/api
stateless: true
anonymous: false
provider: entity_provider
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
- { path: ^/docs, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/secured, roles: IS_AUTHENTICATED_FULLY }
SecurityController: (unchanged from default)
class SecurityController extends AbstractController
{
/**
* #Route("/login", name="app_login")
*/
public function login(AuthenticationUtils $authenticationUtils): Response
{
if ($this->getUser()) {
die();
}
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig', ['last_username' => $lastUsername, 'error' => $error]);
}
/**
* #Route("/logout", name="app_logout")
*/
public function logout()
{
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
}
}
LoginAuthenticator: (src/Security/LoginAuthenticator.php)
class LoginAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
{
use TargetPathTrait;
public const LOGIN_ROUTE = 'app_login';
private $entityManager;
private $urlGenerator;
private $csrfTokenManager;
private $passwordEncoder;
public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
{
$this->entityManager = $entityManager;
$this->urlGenerator = $urlGenerator;
$this->csrfTokenManager = $csrfTokenManager;
$this->passwordEncoder = $passwordEncoder;
}
public function supports(Request $request)
{
return self::LOGIN_ROUTE === $request->attributes->get('_route')
&& $request->isMethod('POST');
}
public function getCredentials(Request $request)
{
$credentials = [
'username' => $request->request->get('username'),
'password' => $request->request->get('password'),
'csrf_token' => $request->request->get('_csrf_token'),
];
$request->getSession()->set(
Security::LAST_USERNAME,
$credentials['username']
);
return $credentials;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
$user = $this->entityManager->getRepository(User::class)->findOneBy(['username' => $credentials['username']]);
if (!$user) {
throw new UsernameNotFoundException('Username could not be found.');
}
return $user;
}
public function checkCredentials($credentials, UserInterface $user)
{
return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
}
/**
* Used to upgrade (rehash) the user's password automatically over time.
*/
public function getPassword($credentials): ?string
{
return $credentials['password'];
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse($this->urlGenerator->generate('test'));
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate(self::LOGIN_ROUTE);
}
}
BackMarketController (just a function)
function called on authentification success
/**
* #return JsonResponse
* #Route("/secured/backmarketProducts", name="test")
*/
public function productsList(Request $request)
{
return $this->render('back_market/backmarketProducts.html.twig');
}
About the webserver :
Apache2
Debian 8
using HTTPS
Symfony 4.4
I can give you more informations of course, just ask for it.
What i tried by looking on other similar subjects :
Symfony 4 login Guard authenticator errors (save the session information elsewhere from default)
update project components
Symfony 4 login form : authenticating successfully, but authentication immediately lost after redirect (add EquatableInterface to User entity)
Kill onAuthenticationSuccess BEFORE REDIRECTION show on the profiler that a session is created but cant follow redirection (look at the error above)
UPDATE 1 (3 days later)
I found a way to allow jwt token and securityauthentificator to work together.
There is the security.yaml :
security:
encoders:
App\Entity\User:
algorithm: auto
role_hierarchy:
ROLE_USER : "ROLE_USER"
ROLE_ADMIN : "ROLE_ADMIN"
ROLE_SUPERADMIN : "ROLE_SUPERADMIN"
providers:
entity_provider:
entity:
class: App\Entity\User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
guard:
entry_point: lexik_jwt_authentication.jwt_token_authenticator
authenticators:
- App\Security\SecurityAuthenticator
- lexik_jwt_authentication.jwt_token_authenticator
logout:
path: app_logout
json_login:
check_path: /login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
login:
pattern: ^/login
stateless: true
anonymous: true
json_login:
check_path: /login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
docs:
pattern: ^/docs
stateless: true
anonymous: true
api:
pattern: ^/api
stateless: true
anonymous: false
provider: entity_provider
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
secured:
pattern: ^/secured
anonymous: false
guard:
authenticators:
- App\Security\SecurityAuthenticator
logout:
path: app_logout
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
- { path: ^/docs, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/secured, roles: IS_AUTHENTICATED_FULLY }
I think that block solve the issue, the symfony security compenent got both guards declared on main firewall, using jwt by default.
To use the basic symfony auth system (securityAuthentificator) i just added a secured firewall providing SecurityAuthenticator
Is there a good way to work with security ? Does this trick seems legit ? Im not sure im providing a good solution.
Nobody seems able to help me. In any case I solved the problem on my own.
There is my full functionnal security.yaml :
This config is working, making symfony able to use API PLATEFORM with JWT tokens and to log user on a simple back office application.
security:
encoders:
App\Entity\User:
algorithm: auto
role_hierarchy:
ROLE_USER : "ROLE_USER"
ROLE_ADMIN : "ROLE_ADMIN"
ROLE_SUPERADMIN : "ROLE_SUPERADMIN"
providers:
entity_provider:
entity:
class: App\Entity\User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
guard:
entry_point: lexik_jwt_authentication.jwt_token_authenticator
authenticators:
- App\Security\SecurityAuthenticator
- lexik_jwt_authentication.jwt_token_authenticator
logout:
path: app_logout
json_login:
check_path: /login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
login:
pattern: ^/login
stateless: true
anonymous: true
json_login:
check_path: /login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
docs:
pattern: ^/docs
stateless: true
anonymous: true
api:
pattern: ^/api
stateless: true
anonymous: false
provider: entity_provider
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
secured:
pattern: ^/secured
anonymous: false
guard:
authenticators:
- App\Security\SecurityAuthenticator
logout:
path: app_logout
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
- { path: ^/docs, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/secured, roles: IS_AUTHENTICATED_FULLY }

FOSUserbundle + Additional HTTP auth without user getting ROLE_USER

I have a "little" problem with the Symfony2 security system. This is what I need to achieve:
/ needs a HTTP auth BEFORE any page can be seen. I want to protect the whole page with a constant user / pass pair. After the user has entered the right pair, he should be a guest (and not ROLE_USER) und be able to login via the FOSUserBundle form
/api needs a separate login via HTTP auth, independent from FOSUserBundle and the other HTTP auth
I already managed to provide a separate login for the API. This is my complete security.yml:
security:
encoders:
FOS\UserBundle\Model\UserInterface: sha512
Symfony\Component\Security\Core\User\User: plaintext
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
in_memory_http:
memory:
users:
User1: { password: PW1, roles: ROLE_HTTP }
in_memory_api:
memory:
users:
User2: { password: PW2, roles: ROLE_API }
fos_userbundle:
id: fos_user.user_provider.username_email
firewalls:
api:
pattern: ^/api
http_basic:
provider: in_memory_api
realm: "API login"
http:
pattern: ^/
provider: in_memory_http
http_basic:
realm: "Hello"
context: primary_auth
main:
pattern: ^/
form_login:
provider: fos_userbundle
login_path: fos_user_security_login
csrf_provider: form.csrf_provider
check_path: fos_user_security_check
logout:
path: fos_user_security_logout
target: home
anonymous: true
context: primary_auth
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login:
security: false
secured_area:
anonymous: ~
access_control:
- { path: ^/api, roles: ROLE_API }
- { path: ^/user/login.html, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/user/logout.html, roles: IS_AUTHENTICATED_REMEMBERED }
This works nearly as expected, but unfortunately not completely...
The /api part works like I wanted it to work. Nothing to do here, I hope.
But when I navigate to /, enter User1/PW1 and send the credentials, I get access to the page, just like expected. The only problem is that the User1 gets logged in! But I want User1 not to be handled like a normal user. He should just be required to access the normal login form and the rest of / except of /api. I can't even log out this user. If I navigate to /user/login.html while User1 is logged in (due to the required http auth) and enter valid user data of a real fosuserbundle user, I get: "You must configure the check path to be handled by the firewall using form_login in your security firewall configuration."
If I want to log out, I get: "You must activate the logout in your security firewall configuration."
What I want is kind of a two step authentication.
First HTTP Auth, then the FOSUserBundle form.
Can somebody help me? :) The documentation is not very good at this point...
So.....
After several hours of pure pain I gave up on trying it with the Symfony2 Security Component. I also did not manage to realize what I want with Apache (Tried SetEnvIf and FilesMatch).
So I wrote a request listener that does what I want. If anybody has the same problem, here is my solution!
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpFoundation\Response;
class RequestListener
{
/**
* Returns true iff the specified $password belongs to the $user and the $user has access to the specified $area
*/
private function hasAccess($user, $password, $area) {
$users = array("User1" => array("password" => "PW1",
"areas" => array("portal")),
"User2" => array("password" => "PW2",
"areas" => array("API", "portal"))
);
return $user
&& array_key_exists($user, $users)
&& $users[$user]["password"] == $password
&& in_array($area, $users[$user]["areas"]);
}
/**
* Extracts the area out of the $path
*/
public function getArea($path) {
if (substr($path, 0, 4) == "/api") {
return "API";
} else {
return "portal";
}
}
/**
* Service handler
*/
public function onKernelRequest(GetResponseEvent $event) {
$request = $event->getRequest();
# $path cointains the path to the virtual resource the user requested!
# If the user calls app_dev.php/api/users, then $path is "/api/users"
# If the user calls app.php/api/users, then $path is "/api/users"
# If the user calls /api/users, then $path is "/api/users"
# If the user calls /app_dev.php, then $path is "/"
# If the user calls /, then $path is "/"
# and so on
#
# ==> $path abstracts the front controller away
$path = $request->getPathInfo();
$area = $this->getArea($path);
# $user, $password are null if no AUTH data is sent
$user = $request->server->get("PHP_AUTH_USER");
$password = $request->server->get("PHP_AUTH_PW");
# If the user has no access, he must log in as another user
if (!$this->hasAccess($user, $password, $area)) {
# In case the response already exists (in most cases not) we use it and modify it
$response = $event->hasResponse() ? $event->getResponse() : new Response();
$response->setStatusCode(Response::HTTP_UNAUTHORIZED); # Code 401
$response->headers->set("WWW-Authenticate", "Basic realm=\"".$area."\"");
$response->setContent("Please provide valid data");
$response->send();
die(); # To make sure the page is not shown!
}
}
}
Now everything seems to work...

How to change Symfony2 security plaintext encoders from User to User Interface

I'm trying to crate a chain provider for login form in my Symfony2 application (version 2.3) - this is my security Yaml:
jms_security_extra:
secure_all_services: false
expressions: true
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext
FOS\UserBundle\Model\UserInterface: sha512
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
chain_provider:
chain:
providers: [in_memory, fos_userbundle]
fos_userbundle:
id: fos_user.user_provider.username
in_memory:
memory:
users:
admin: { password: god, roles: [ 'ROLE_ADMIN' ] }
firewalls:
main:
pattern: ^/
form_login:
provider: chain_provider
csrf_provider: form.csrf_provider
logout: true
anonymous: true
security: true
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
#- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/, role: ROLE_ADMIN }
- { path: ^/group/, role: ROLE_ADMIN }
As you can see, I'm using FOSUserBundle (great stuff btw).
The problem is that after I login with in_memory admin user, I can't get to the /profile/ URL. I'm getting this error msg:
AccessDeniedHttpException: This user does not have access to this section
I found a cause to this - the problem is in FOS\UserBundle\Controller\ProfileController class:
public function showAction()
{
$user = $this->container->get('security.context')->getToken()->getUser();
if (!is_object($user) || !$user instanceof UserInterface) {
throw new AccessDeniedException('This user does not have access to this section.');
}
return $this->container->get('templating')->renderResponse('FOSUserBundle:Profile:show.html.'.$this->container->getParameter('fos_user.template.engine'), array('user' => $user));
}
The Controller is checking if $user object is an instance of UserInterface, which is not, because it is instance of Symfony\Component\Security\Core\User\User (plaintext encoder class).
I tried to change encoder configuration to this:
security:
encoders:
Symfony\Component\Security\Core\User\UserInterface: plaintext
But it didn't work. I found that the User class is hardcoded in numerous places with the Symfony engine.
So my question is: how to change that behaviour from security yaml? Am I missing something?
PHP documentation:
instanceof is smart enough to know that a class that implements an
interface is an instance of the interface
Symfony\Component\Security\Core\User\UserInterface implements AdvancedUserInterface which implements UserInterface.
Your problem is not the typeof check in FOS\UserBundle\Controller\ProfileController but a wrong firewall configuration or the in-memory user not receiving it's roles correctly!
Sorry, but you cant see the profile of memory user's. Profile is made only for FOS users. Thats why it must implement FOS\UserBundle\Model\UserInterface.

Symfony2 version 2.0.23, login with users from database always return: 'The presented password is invalid'

I'm trying to set up 2 firewall with 2 different providers and encoders in my security.yml that looks like this:
security:
encoders:
Devsign\UserBundle\Entity\AgentUser:
algorithm: sha512
iterations: 5000
encode_as_base64: true
Devsign\UserBundle\Entity\PressUser:
algorithm: sha512
iterations: 5000
encode_as_base64: true
providers:
agent_secured_area:
entity: {class: Devsign\UserBundle\Entity\AgentUser } # using a custom repository to login with username or email, details in AgentUserRepository.php
press_secured_area:
entity: {class: Devsign\UserBundle\Entity\PressUser, property: username }
firewalls:
agent_secured_area:
pattern: /(it|en)/reserved/
provider: agent_secured_area
anonymous: ~
form_login:
check_path: /it/reserved/login-check
login_path: /reserved/login
logout:
path: /reserved/logout
target: /
press_secured_area:
pattern: /(it|en)/press/
provider: press_secured_area
anonymous: ~
form_login:
check_path: /it/press/login-check
login_path: /press/login
logout:
path: /press/logout
target: /
access_control:
agent_login:
path: /reserved/login
roles: IS_AUTHENTICATED_ANONYMOUSLY
agent_register:
path: /reserved/register
roles: IS_AUTHENTICATED_ANONYMOUSLY
agent_area:
path: /(it|en)/reserved/.*
roles: ROLE_AGENT
press_login:
path: /press/login
roles: IS_AUTHENTICATED_ANONYMOUSLY
press_register:
path: /press/register
roles: IS_AUTHENTICATED_ANONYMOUSLY
press_area:
path: /(it|en)/press/.*
roles: ROLE_PRESS
I get no exception but when I try to login against agent_secured_area i get always: 'The presented password is invalid'.
I created the first user password and salt using this code in a controller:
$factory = $this->get('security.encoder_factory');
$user = new \Devsign\UserBundle\Entity\AgentUser();
$encoder = $factory->getEncoder($user);
$salt = $user->getSalt();
$password = $encoder->encodePassword('grab', $salt);
die("pwd: $password - salt: $salt");
And then i fill the database field password and salt with the echoed values.
Can someone spot the error?
UPDATE 1
I made some test setting in the config_dev.yml:
web_profiler:
toolbar: true
intercept_redirects: true
verbose: true
1) I try to go to /it/reserved/info but it is access protected so I'm redirected correctly to /it/reserved/login
2) I try to login from /it/reserved/login posting the form to /it/reserved/login-check
3) Thanks to web_profile: intercepts_redirects: true I can see the debug toolbar in /it/reserved/login-check and I'm correctly authenticated with the correct Role: ROLE_AGENT. By the way looking in the doctrine section of the profiler I see two queries against my user table, the first one with parameter username NULL and a second one with the correct username.
4) Then I'm redirected to /it/reserved/info where for some reason I see a single query against my user table with parameter username NULL. Infact in /it/reserved/info I'm not authenticated anymore. So I'm redirected again to /it/reserved/login.
So I think the problem is that query with parameter username null, someone knows where it's coming from? Maybe from some misconfiguration on security.yml?

Resources