The default value for remember me token inside Symfony is 1209600second = 14 days. I wanted to change that. I do this in security.yml:
firewalls:
main:
remember_me:
secret: '%env(LION_SECRET)%'
lifetime: 2592000 # 30 days
and then inside services.yml
services:
security.authentication.provider.rememberme.main:
class: Symfony\Component\Security\Http\RememberMe\PersistentTokenBasedRememberMeServices
arguments:
- 'main'
Then i tried to login, it works without errors, but still my lifetime value is not changed, it still 1209600 instead 2592000, what i did wrong, any ideas?
Related
I'm working on a legacy project with a lot of ancient staffs. There're huge numbers of actions which are used really rare. Half a year ago we upgraded from Symfony 2.8 to Symfony 4.4. All worked pretty well until a manager tried to use one of the old action that now returns AccessDeniedException: Access Denied.
I've checked Symfony documentation and all seems pretty straightforward for me.
The documentation says:
Checking to see if a User is Logged In (IS_AUTHENTICATED_FULLY)
If you only want to check if a user is logged in (you don’t care about roles), you have two options. First, if you’ve given every user ROLE_USER, you can check for that role.
There is app/config/security.yml with the next configuration:
security:
access_decision_manager:
strategy: unanimous
allow_if_all_abstain: true
encoders:
FOS\UserBundle\Model\UserInterface:
algorithm: sha512
encode_as_base64: false
iterations: 1
role_hierarchy:
ROLE_CUSTOMER_ADMIN: ROLE_USER
ROLE_ADMIN: ROLE_USER
ROLE_ADMIN_CAN_EDIT_PERMISSIONS: ROLE_ADMIN
ROLE_SUPER_ADMIN: ROLE_ADMIN_CAN_EDIT_PERMISSIONS
providers:
fos_user:
id: fos_user.user_provider.username
firewalls:
main:
pattern: ^/
form_login:
provider: fos_user
default_target_path: /user-post-login
always_use_default_target_path: true
login_path: user_security_login
check_path: fos_user_security_check
logout:
path: /logout
target: /login
handlers: [mp.logout_handler]
invalidate_session: false
anonymous: ~
remember_me:
secret: "%secret%"
lifetime: 31536000 # 365 days in seconds
path: /
domain: ~ # Defaults to the current domain from $_SERVER
access_control:
- { path: ^/login, role: IS_AUTHENTICATED_ANONYMOUSLY }
...
- { path: ^/api, role: IS_AUTHENTICATED_ANONYMOUSLY }
...
- { path: ^/admin/select-customer-status, role: [ROLE_CUSTOMER_ADMIN, ROLE_SUPER_ADMIN] }
...
- { path: ^/admin, role: ROLE_USER }
My current user has the role ROLE_SUPER_ADMIN, according to role_hierarchy this role has ROLE_USER in ancestors, but when I try to open http://localhost:8080/admin/select-customer-status/1 I get this Access Denied exception.
I tried to debug and figured out this exception arose in Symfony\Component\Security\Http\Firewall\AccessListener
But the real problem is that Voter is checking for IS_AUTHENTICATED_FULLY under the hood, but this one isn't present in $attributes.
Another interesting thing is when I add this configuration directly to Action it works as expected and no exception is thrown:
/**
* #Route("/admin/update-user-permissions/{id}", name="update_user_permissions")
* #Method({"POST"})
*
* #IsGranted("ROLE_SUPER_ADMIN")
* #IsGranted("ROLE_CUSTOMER_ADMIN")
*/
Can anybody help with this strange behaviour?
P.S. There is a similar question, but it's for Symfony 2 and not reliable for me.
what apparently happens, is, that all of the roles mentioned are required, i.e. both ROLE_SUPER_ADMIN as well as ROLE_CUSTOMER_ADMIN. As far as I understand your problem description, your user is either, but not both.
The screenshot you provided shows the access manager handling the request at the bottom:
which clearly shows, that the AuthenticatedVoter is NOT the problem, because it abstains (since it only votes on IS_AUTHENTICATED_* roles (for example IS_AUTHENTICATED_FULLY), which is also easily seen in its code.
However, as can be easily seen as well, both roles are checked separately(!). The RoleHierarchyVoter now tries to see, if the ROLE_CUSTOMER_ADMIN is granted, and I assume your user has the ROLE_SUPER_ADMIN, which the RoleHierarchyVoter will expand by using the hierarchy to ROLE_SUPER_ADMIN,ROLE_ADMIN_CAN_EDIT_PERMISSIONS, ROLE_ADMIN and ROLE_USER. None of which is ROLE_CUSTOMER_ADMIN.
As I have suggested in a comment, you might want to choose to add a new ROLE that both extended admin roles hold. if ROLE_ADMIN is not appropriate, maybe a ROLE_EXTENDED_ADMIN or something.
ROLE_CUSTOMER_ADMIN: [ROLE_ADMIN, ROLE_EXTENDED_ADMIN]
ROLE_SUPER_ADMIN: [ROLE_ADMIN_CAN_EDIT_PERMISSIONS, ROLE_EXTENDED_ADMIN]
and then in access control:
- { path: ^/admin/select-customer-status, roles: ROLE_EXTENDED_ADMIN }
you can use
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
/**
* #Security("has_role('ROLE_SUPER_ADMIN') or has_role('ROLE_CUSTOMER_ADMIN')")
*/
that's all
How can we configure Symfony 4 Remember me functionality to use email instead of username (as set by default) when creating the cookie and storing it in the browser?
My issue is that by using email to authenticate in S4, the cookie is created with the username instead of the email in its hash, stored in the browser but when S4 check my cookie to see if IS_AUTHENTICATED_REMEMBERED is true, it checks it against the username stored in the DB which doesn’t make sens. It should check it against email. So my remember me functionality doesn’t work.
If I use the username to login, then it works, but that’s not what I want, I’d like my users to log in with their email address.
I’ve configurered the login to work with email instead of the default username behavior, but I can’t have remember me working that way.
I tried the following in my security.yaml
security:
encoders:
App\Entity\User:
algorithm: bcrypt
providers:
user_provider:
entity:
class: App\Entity\User
property: email
in_memory: { memory: ~ }
our_db_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
http_basic: ~
provider: our_db_provider
anonymous: ~
form_login:
login_path: login
check_path: login
default_target_path: dashboard
username_parameter: email
password_parameter: password
remember_me: true
remember_me:
secret: '%kernel.secret%'
lifetime: 31536000 # 1 week in seconds
path: /
domain: ~
secure: true
name: REMEMBERME
remember_me_parameter: remember_me
always_remember_me: true
logout:
path: /logout
target: /
but this doesn’t let you parameter what field remember is using to generate the hash stored in the cookie.
If you’ve managed to set up your login / authentication & remember me working with a field different than username, please share :)
UPDATE: I tried Ahmed answer with the following lines on services but it’s not working:
App\Security\TokenBasedRememberMeServices:
decorates: Symfony\Component\Security\Http\RememberMe\TokenBasedRememberMeServices
it says You have requested a non-existent service "Symfony\Component\Security\Http\RememberMe\TokenBasedRememberMeServices”.
The problem that security component use getUsername() getter to build the token https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php#L74
So we need to overide the service responosible for creating the remember-me cookie, wish is security.authentication.rememberme.services.simplehash.class.
Firstable: Update the onLoginSuccess method L74 so it uses the email instead of the username.
namespace App\Security;
...
class TokenBasedRememberMeServices extends AbstractRememberMeServices
{
....
protected function onLoginSuccess(Request $request, Response $response, TokenInterface $token)
{
...
$value = $this->generateCookieValue(\get_class($user), $user->getEmail(), $expires, $user->getPassword());
...
}
...
Second: Register your class as a service.
App\Security\TokenBasedRememberMeServices:
decorates: 'security.authentication.rememberme.services.simplehash.class'
Or you can flow the contract under UserInterface It define and returns the username used to authenticate the user. In our case it's the email property.
public function getUsername()
{
return $this->email;
}
I don't have enough SO reputation to add a comment for answer upon, so I write it here.
Instead of decorating service:
security.authentication.rememberme.services.simplehash.class
Decorate:
security.authentication.rememberme.services.simplehash
It works on Symfony 4.4
I use this in my navbar on my web site
{{ render(controller('FOSUserBundle:Security:login')) }}
And i put the login form in my navbar where my login work when i put the correct data into.. my problem come when the data is incorrect because the render redirect me on the login.html.twig page for say me the error message. I think my problem is in this lines
protected function renderLogin(array $data)
{
return $this->render('FOSUserBundle:Security:login.html.twig', $data);
}
I try to change this, with the initial page or the directory of navbar but is not the good way..
How can i do??? I want the error message in the index and I do not want to redirect the login page..
if you start with FOSUserBundle you learn to import the bundle's routing files:
# app/config/routing.yml
fos_user:
resource: "#FOSUserBundle/Resources/config/routing/all.xml"
Now take a look here and see how you can import the different sections.
Take them over in place of the one you had first to import them all but skip the first one (security.xml)
Next open vendor/friendsofsymfony/user-bundle/Resources/config/routing/security.xml and copy it to your own project. Change the /login path to your own path /.
OR
Overwrite the symfony login route:
# app/config/routing.yml
fos_user:
resource: "#FOSUserBundle/Resources/config/routing/all.xml"
fos_user_security_login:
path: /
defaults: { _controller: AppBundle:Default:index }
add the login_path: / option in security.yml
# app/config/security.yml
security:
firewalls:
main:
form_login:
login_path: /
add the following to security.yml:
//...
firewalls:
main:
// ...
form_login:
login_path: /
// ...
i edited my first post.
Just recently I got into testing pieces of a symfony application... For starters, I thought I would try and test a login form that should grant access to a private area.
The users for this private area are correctly persisted to the database and have been tried on the development enviroment. The config_test.yml file goes like this:
imports:
- { resource: config_dev.yml }
framework:
test: ~
session:
storage_id: session.storage.mock_file
profiler:
collect: false
web_profiler:
toolbar: false
intercept_redirects: false
swiftmailer:
disable_delivery: true
So it should use the database configuration exposed in config_dev.yml.
There's nothing special about my user provider:
providers:
users:
entity:
class: MyBundle:User
property: login
As requested in the comments section, here's the security info. Please note that all namespaces, class names and relevant data have been changed, as I can't publish the real thing. If you spot any mistake it must have been me changing the relevant code.
security:
encoders:
MyBundle\Entity\User:
algorithm: bcrypt
cost: 12
role_hierarchy:
ROLE_CUSER: ROLE_USER
ROLE_CUSER_A: ROLE_CUSER
ROLE_CUSER_B: ROLE_CUSER
ROLE_CUSER_C: ROLE_CUSER
ROLE_CUSER_D: ROLE_CUSER
ROLE_CUSER_E: ROLE_CUSER
providers:
users:
entity:
class: MyBundle:User
property: login
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
user_tools_login_firewall:
pattern: ^/user_tools/login$
anonymous: ~
user_tools:
pattern: ^/user_tools
http_basic: ~
provider: users
form_login:
login_path: /user_tools/login
check_path: /user_tools/login_check
logout:
path: /user_tools/logout
target: /user_tools/home
invalidate_session: false
access_control:
- {path: /user_tools/login, roles: IS_AUTHENTICATED_ANONYMOUSLY}
- {path: ^/user_tools/users/, roles: ROLE_CUSER_A}
- {path: ^/user_tools/news/, roles: ROLE_CUSER_B}
Let's take a look at the testing unit...
class SecurityControllerTest extends WebTestCase
{
const LOGIN_ERROR='this_is_a_non_existing_user';
const PASS_ERROR='with_a_non_existing_pass';
const LOGIN='this_is_a_valid_user';
const PASS='with_a_valid_pass';
const PASS_CRYPT='##and_this_is_the_encripted_pass_as_pasted_from_the_database##';
const ID_SUBMIT='btn_submit';
private $client;
private $crawler;
public function testIndex()
{
$this->client=static::createClient();
$this->crawler=$this->client->request('GET', '/route_to_login');
//Let's check that there are users in the database...
$em=$this->client->getContainer()->get('doctrine')->getManager();
$users=$em->getRepository("MyBundle:User")->findAll();
$this->assertGreaterThan(0, count($users));
//Now let's check that there's a valid user...
$valid_user=$em->getRepository("MyBundle:User")->findBy(
array('login' => self::LOGIN,
'pass' => self::PASS_CRYPT));
$this->assertGreaterThan(0, count($valid_user));
//Let's check that there is no invalid user ;).
$invalid_user=$em->getRepository("MyBundle:User")->findBy(
array('login' => self::LOGIN_ERROR,
'pass' => self::PASS_ERROR));
$this->assertEquals(0, count($invalid_user));
//The view should be the one I expect...
$this->assertEquals('MyBundle\Controller\User\SecurityController::loginAction', $this->client->getRequest()->attributes->get('_controller'));
//Let's try an invalid access...
$this->form_login(self::LOGIN_ERROR, self::PASS_ERROR);
$this->assertEquals('MyBundle\Controller\User\SecurityController::loginAction', $this->client->getRequest()->attributes->get('_controller'));
//And now, let's try the good one!.
self::form_login(self::LOGIN, self::PASS);
$this->assertEquals('MyBundle\Controller\User\HomeController::homeAction', $this->client->getRequest()->attributes->get('_controller'));
}
private function form_login($login, $pass)
{
$form=$this->crawler->selectButton(self::ID_SUBMIT)->form();
$form['_username']=$login;
$form['_password']=$pass;
$this->client->submit($form);
$this->crawler=$this->client->followRedirect();
}
}
The thing is, all tests are passed except the last one (that is, the good login). It does not go to the expected controller, but back to the login.
Here's the phpunit error:
There was 1 failure:
1) MyBundle\Tests\Controller\User\SecurityControllerTest::testIndex
Failed asserting that null matches expected 'MyBundle\Controller\User\HomeController::homeAction'.
Is there something I am missing here?. Everything is working as expected into the dev enviroment. Users do exists (and are asserted, as you can see). I am prepared to give more info as requested.
Thanks!.
You forgot to define the default_target_path in your form_login configuration, try to add your path like this:
form_login:
login_path: /user_tools/login
check_path: /user_tools/login_check
default_target_path: your_target_path
Following are my security.yml settings
form_login:
check_path: /%cluburlidentifier%/backend/login_check
login_path: /%cluburlidentifier%/backend/signin
logout:
path: /%cluburlidentifier%/backend/signout
target: /%cluburlidentifier%/backend/signin
I signin using /myclub/backend/signin. Permission of the user is Superadmin. When i tried to logout using different path, like, /myhome/backend/signout, it shows error "You must activate the logout in your security firewall configuration.".
%cluburlidentifier% is a dynamic parameter taken from url.
How can I change the logout path defined in the security.yml after login..? Is there any session or something like to set...??
Please help me !!
The following is my full security.yml firewall config
firewalls:
frontend:
pattern: /%cluburlidentifier%/backend/.*
provider: fos_userbundle
anonymous: ~
form_login:
check_path: /%cluburlidentifier%/backend/login_check
login_path: /%cluburlidentifier%/backend/signin
username_parameter: _username
password_parameter: _password
csrf_parameter: _csrf_token
intention: authenticate
post_only: true
remember_me: true
#use_referer: true
always_use_default_target_path: true
default_target_path: contact_index
logout:
path: /%cluburlidentifier%/backend/signout
target: /%cluburlidentifier%/backend/signin
invalidate_session: true
delete_cookies:
a: { path: null, domain: null }
b: { path: null, domain: null }
remember_me:
key: "123456"
lifetime: 31536000 # 365 days in seconds
path: fos_user_security_login
domain: ~ # Defaults to the current domain from $_SERVER
always_remember_me: true
In that, first im trying to login with url like "localhost:8080/twilight/backend/signin", where twilight is the cluburl identifier. Afet login he redirects to "localhost:8080/twilight/backend/contact"
The logged user have the superadmin previlage. So he can moved to the next club by changing the cluburl identifier without triggering one more login. That means, just changing the url to "localhost:8080/myclub/backend/contact" in the browser..
That also works...But the problem is, when he tried to logout using the url "**localhost:8080/myclub/backend/logout" after login with "localhost:8080/twilight/backend/signin", couldn't work...
I know it is a negative requirement...But is it possible...??**
Also i didn't get the logged user details if i changed the url path...Is there any way to that also?
Plz help me....