Symfony 2 : Security Configuration : Login and logout handlers - symfony

Using Symfony 2, I am looking for more information about the handlers that you can define in the security configuration file app/config/security.yml (official documentation). The documentation doesn't give any informations about handlers. Here is an extract of the security file :
# app/config/security.yml
security:
...
firewalls:
somename:
form_login:
...
# login failure redirecting options (read further below)
failure_path: /foo
failure_forward: false
failure_path_parameter: _failure_path
failure_handler: some.service.id
success_handler: some.service.id
logout:
path: /logout
target: /
invalidate_session: false
delete_cookies:
a: { path: null, domain: null }
b: { path: null, domain: null }
handlers: [some.service.id, another.service.id]
success_handler: some.service.id
anonymous: ~
In both form_login ang logout part there is a success_handler field. Moreover, for logout part you can define several handlers using handlers field.
I have two questions :
If I define a succes_handler service (using for example AuthenticationSuccessHandlerInterface or LogoutHandlerInterface), will it overide the default success handler provided in the framework ?
For the logout part of the configuration, how work the handlersfield ?

For information, in logout part of app/config/security.yml :
handlers: [some.service.id, another.service.id] => Here you have to define services implementing Symfony\Component\Security\Http\Logout\LogoutHandlerInterface. Theses handles do not need to return a response. In my case I created a simple handler that creates a flash message on logout.
success_handler: some.service.id => Here you have to define a service implementing => Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface. This handler have to return a response. This handler is called by the constructor of Symfony\Component\Security\Http\Firewall\LogoutListener (firewall listener).

I tried with success the next solution
https://gist.github.com/marydn/8061424
Seems to be what you are trying to do.

Related

Symfony 5.2 Validate json_login Request

I am developing a RESTFUL API in Symfony 5.2 that allows users to authenticate with username and password in order to access it.
My firewall config for this looks like:
login:
pattern: '^/v1/login'
methods: [POST]
anonymous: true
stateless: true
provider: user_provider
user_checker: App\Security\UserChecker
json_login:
check_path: '/v1/login'
success_handler: App\Security\Handler\AuthenticationSuccessHandler
failure_handler: lexik_jwt_authentication.handler.authentication_failure
I have created a validator and a matching route so that I can ensure users submit valid data, like not submitting empty passwords:
v1_login:
path: /v1/login
methods: POST
controller: App\Http\Token\Action\LoginAction
defaults:
_format: json
All my other routes are setup the same way with validators, but this one is not being called. I think it's Symfony is ignoring this and using the user_provider, user_checker and success handler to manage it all. But it means I can't validate the request first.
Is there something else I need to do?

The contents of the service container differ for no obvious reason

I work with Symfony 5.1.2 and my tests worked until I introduced a chain of user providers.
I derive all of my test classes from a class I created in order to put common methods and properties.
Among these methods there is a method that I use to connect a user.
public function connectUser(string $username)
{
$userProvider = static::$container->get(UserProviderInterface::class);
$user = $userProvider->loadUserByUsername($username);
$this->assertNotNull($user);
$this->kernelBrowser->loginUser($user);
}
With the following settings there were no problems
providers:
backend_users:
memory:
users:
admin#localhost.dev: { password: '$argon2id$v=19$m=65536,t=4,p=1$c0F2RmVYa21RclE4ZXJkTA$mvXd/skXaV9w1rqmWb6B5MTtgkP86inWSkj0E8hjtTA', roles: ['ROLE_ADMIN'] }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
lazy: true
logout:
path: security.authentication.logout
provider: backend_users
guard:
authenticators:
- App\Security\LoginFormAuthenticator
I then introduced a new user source ; a database.
With the following settings there is a problem
providers:
# used to reload user from session & other features (e.g. switch_user)
backend_users:
memory:
users:
admin#localhost.dev: { password: '$argon2id$v=19$m=65536,t=4,p=1$c0F2RmVYa21RclE4ZXJkTA$mvXd/skXaV9w1rqmWb6B5MTtgkP86inWSkj0E8hjtTA', roles: ['ROLE_ADMIN'] }
frontend_users:
entity:
class: App\Entity\User
property: email
all_users:
chain:
providers: ['backend_users','frontend_users']
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
lazy: true
logout:
path: security.authentication.logout
guard:
authenticators:
- App\Security\LoginFormAuthenticator
provider: all_users
With this configuration, the service container no longer seems to contain the UserProviderInterface class. Because I receive this message :
You have requested a non-existent service "Symfony\Component\Security\Core\User\UserProviderInterface".
I figured I might need to implement a custom user provider, but after doing some research in the source code, I realized that there is a class that seems specific to user provider chains that is :Symfony\Component\Security\Core\User\ChainUserProvider
As the name suggests, the UserProviderInterface class is an interface, so I'm not supposed to worry about the internal implementation.
Why is this interface no longer in the service container ? How to reintroduce it properly ?
Thank you !
Suppose you have multiple providers all implementing UserProviderInterface. When you type hint against the interface, which service do you want injected and how would the container know? The container does not know anything about your firewalls so it can't guess that you want the chain provider. So things worked when you only had one provider but will fail when you have multiple providers.
The same question arises anytime you have multiple implementations of the same interface. You either need to typehint against a specific implementation or inject the desired service manually or create an alias which will tie the interface to one specific implementation.
In your case:
bin/console debug:container | grep UserProvider
doctrine.orm.security.user.provider Symfony\Bridge\Doctrine\Security\User\EntityUserProvider
security.user.provider.chain Symfony\Component\Security\Core\User\ChainUserProvider
security.user.provider.concrete.all_users Symfony\Component\Security\Core\User\ChainUserProvider
security.user.provider.concrete.backend_users Symfony\Component\Security\Core\User\InMemoryUserProvider
security.user.provider.concrete.frontend_users Symfony\Bridge\Doctrine\Security\User\EntityUserProvider
security.user.provider.in_memory Symfony\Component\Security\Core\User\InMemoryUserProvider
security.user.provider.ldap Symfony\Component\Ldap\Security\LdapUserProvider
security.user.provider.missing Symfony\Component\Security\Core\User\MissingUserProvider
security.user_providers Symfony\Component\Security\Core\User\ChainUserProvider
If you always want the all_users chain provider to be injected then add an alias:
config/services.yaml
Symfony\Component\Security\Core\User\UserProviderInterface : '#security.user.provider.concrete.all_users'
And you should be good to go.

Symfony4 Two authentification methods in security

I want two authentications methods in my application.
One for the entity User, and other (admin) with a plaintext.
Very simple.
Thus, when I configure security.yaml, I specify the providers:
security:
providers:
user:
entity:
class: App\Entity\User
property: username
in_memory:
memory:
users:
admin:
password: admin
roles: 'ROLE_ADMIN'
encoders:
App\Entity\User: bcrypt
Symfony\Component\Security\Core\User\User: plaintext
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
admin:
provider: in_memory
pattern: ^/admin/
guard:
provider: in_memory
form_login:
login_path: admin_login
check_path: admin_login
logout:
path: /admin/logout
target: /
default:
provider: user
anonymous: ~
guard:
provider: user
form_login:
login_path: login
check_path: login
default_target_path: login_redirect
use_referer: true
logout:
path: /logout
target: /
access_control:
- { path: ^/admin/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/dashboard, roles: ROLE_USER }
And return the error:
In GuardAuthenticationFactory.php line 121:
Because you have multiple guard configurators, you need to set the "guard.e
ntry_point" key to one of your configurators ()
Then, if I have to set the guard.entry_point, I need do something like this:
admin:
entry_point: app.form_admin_authenticator
main:
entry_point: app.form_user_authenticator
And therefore, if I undestard, I need to configure a Authentication Listener like this: https://symfony.com/doc/current/components/security/authentication.html
(btw, this particular help page is very ambiguous and incomplete)
Is it necessary? It seems too complex for my purpose
I ran into this particular error. My situation might be a little different, but I had a similar need to authenticate using different authentication strategies depending on the entry point to the application.
One thing your config doesn't include is a reference to any Guard Authenticator objects. See this documentation for an intro to what role those objects play, and how to define them. Symfony's Security package is pretty complicated, and I found that using Guard Authenticators made the process a lot simpler for my use case.
Here is an example of a security.yaml config referencing two different authenticators. The entry_point configuration tells Symfony which one to try first, because in my particular case, Symfony otherwise wouldn't know which authentication strategy to apply first to an incoming request.
security:
providers:
user:
id: App\My\UserProviderClass
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
logout:
path: app_logout
guard:
entry_point: App\My\MainAuthenticator
authenticators:
- App\My\MainAuthenticator
- App\My\OtherAuthenticator
Custom Guard Authenticators contain a method called supports. This method takes the incoming request as its only argument, and returns true or false based on whether the given authenticator should be applied to the incoming request. A common practice might be to check the request's Symfony route name (as defined by the controller) or perhaps something like the full URI for the request. For example:
/**
* Does the authenticator support the given Request?
*
* If this returns false, the authenticator will be skipped.
*
* #param Request $request
*
* #return bool
*/
public function supports(Request $request): bool
{
$matchesMyRoute = 'some_route_name' ===
$request->attributes->get('_route');
$matchesMyUri = '/path/to/secured/resource' ===
$request->getUri();
return $matchesMyRoute || $matchesMyUri;
}
You can imagine that if multiple Guard Authenticators exist in the same application, it's likely the case that one would only want them to apply to a request of a certain type, whether the differentiation is based on the kind of auth applied (eg. a header with an API key vs. a stateful session cookie), whether the difference is more about the specific route being hit, or perhaps a combination of factors.
In this case, telling Symfony which Guard Authenticator to try first may be important for your security strategy, or may have performance ramifications. For example, if you had two authenticators, and one had to hit the database to verify a stateful session, but another could verify the request's authentication statelessly, eg. by verifying a JWT's signature, you'd probably want to make the JWT authenticator run first, because it might not need to make a round trip to the database to authenticate the request.
See this article for a deeper explanation: https://symfonycasts.com/screencast/symfony-security/entry-point

User authentication flow with FOSUserBundle + FOSOAuthServerBundle

I have been working on setting up the FOSUserBundle/RestBundle/OAuthServerBundle trio to create a headless back end that I can then place a separate front end on top of, and eventually expand to mobile, and possible third party API access. I have the general configuration in place based on the various resources and instructions available and can generate an access token using client credentials.
The application this is being added to is an existing one that uses standard Symfony/Twig for front end/back end interaction and uses FOSUserBundle for authentication.
I have two problems related to the flow of authentication.
I want the user to be able to access some parts of the API without authenticating past the client level, and some parts will require user-level authentication to verify they own the requested resources. I am not finding a way to do this. I've found posts talking about the possibility but nothing giving any direction on how it might be achieved. I believe I'll need to check at the controller level for appropriate access, maybe using custom voters, as checking for 'IS_AUTHENTICATED_FULLY' is coming back as true after just authenticating with the client. I want to be able to programmatically authenticate the user, bypassing the UI login form - that might just be overriding the FOSUserBundle login controller, but I'm not sure.
I either need to create a client without an access token expiration or find a way to implement the refresh token. I don't really see why my own app should need to refresh a token, but if that is the standard way to do it I'm ok with following specs on that.
Below is some relevant code, though by and large the code is pretty box standard stuff copied over from the FOSOAuthServer setup guide.
security.yml
security:
encoders:
FOS\UserBundle\Model\UserInterface: sha512
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
firewalls:
oauth_token:
pattern: ^/oauth/v2/token
security: false
rest:
pattern: ^/rest(?!/doc)
fos_oauth: true
stateless: true
anonymous: false
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_token_generator: security.csrf.token_manager
success_handler: security.authentication.success_handler
use_referer: true
logout: true
anonymous: true
access_control:
- { path: ^/rest, roles: [ IS_AUTHENTICATED_FULLY ] }
config.yml snippet
fos_user:
db_driver: orm
firewall_name: main
user_class: AppBundle\Entity\User
registration:
form:
type: AppBundle\Form\Type\RegistrationFormType
profile:
form:
type: user_profile
fos_oauth_server:
db_driver: orm
client_class: AppBundle\Entity\Client
access_token_class: AppBundle\Entity\AccessToken
refresh_token_class: AppBundle\Entity\RefreshToken
auth_code_class: AppBundle\Entity\AuthCode
service:
user_provider: fos_user.user_provider.username_email
options:
supported_scopes: user
fos_rest:
view:
view_response_listener: force
formats:
json: true
templating_formats:
html: true
mime_types:
json: ['application/json', 'application/json;version=1.0', 'application/json;version=1.1']
jpg: ['image/jpeg']
png: ['image/png']
body_listener: true
param_fetcher_listener: true
allowed_methods_listener: true
format_listener:
rules:
- { path: ^/, priorities: [html, json], fallback_format: json, prefer_extension: false }
AD 1)
I solved your problem with two firewalls in security.yml.
Since Symfony is looking first match in security.yml I put first firewall to let anonymous users in:
api_anonym_area:
pattern: (^/api/forgotten-password/.*)
stateless: true
fos_oauth: true
anonymous: true
I catch URL with regex and give anonymous: true
As second firewall I have regex that catches all
api_auth_area:
pattern: ^/
fos_oauth: true
stateless: true
anonymous: false
So in your case, if you want anonymous users to get to /rest/doc, put in front of your firewall:rest something like this:
rest_doc:
pattern: ^/rest/doc
fos_oauth: true
stateless: true
anonymous: true
AD 2)
Its not good practice to have unlimited access token lifetime, but you can do it in config.yml by setting big integer to access_token_lifetime:
fos_oauth_server:
service:
options:
access_token_lifetime: #number in seconds#
To sign in with refresh token just
/oauth/v2/token?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&grant_type=refresh_token&refresh_token=REFRESH_TOKEN
its in FOSOAuthServerBundle out of the box

Interactive login in Symfony 2.6 looses credentials after redirect

I am new to Symfony so it may well be that my question is the result of a simple error but I have been searching for an answer for quite a while with no success.
I am trying to log in a user that requests for a new password with a temporary token.
The relevant part of my controller is:
// auto log in the user
$token = new UsernamePasswordToken($user, $user->getPassword(), 'main', $user->getRoles());
$this->get("security.context")->setToken($token);
// do the the login event
$event = new InteractiveLoginEvent($request, $token);
$this->get("event_dispatcher")->dispatch("security.interactive_login", $event);
return $this->redirect($this->generateUrl('nononsense_home_homepage'));
If I get the user info with getUser before the redirect I can see that the credentials of the logged user are there but they are lost as soon as the redirect takes place.
Curiously enough if I purposedly include an error in the target page whenever symfony throws the errror I can check theat I am logged with the right credentials in the symfony dev bar!!! But if I correct the error and reload the page again I can see in the dev bar that the user is not authenticated any longer.
Firewall
main:
pattern: ^/
provider: main
form_login:
check_path: /users/check_user
login_path: /users/login
remember_me: true
default_target_path: /users/login_init
logout:
path: /users/logout
target: /
remember_me:
key: Qn65f7wtgGZKCCzWQZps04AskmNSiZrvl3qVZRCCIp
lifetime: 31536000
path: /
domain: ~
anonymous: false
switch_user: true
If you need any other chunk of code just let me know...
I found the correct answer in Automatic post-registration user authentication.
It has taken a while but I moved to different things and I retook the issue again to find that it was answered in this post.

Resources