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.
Related
I'm banging my head on the keyboard for a while now trying to solve something that should be simple but for some reason it has not been.
Here is the scenario. A user will use a form on my website to login (username/pwd). And the following workflow should work:
Try to authenticate the user on the LDAP server:
1.1. If it passes:
1.1.1. Check if there is an user with the same username on our database;
1.1.1.1. If it has, load some data and log in the user;
1.1.1.2. If it has not, throws a failed login.
1.2. If it fails:
1.2.1. Check if there is an user with the same username on our database;
1.2.1.1. If it exists
1.2.1.1.1 Check if the pwd match the save one, if it matches, log the user
1.2.1.2. If it has not, throws a failed login.
So basically, I'm trying to authenticate the user on the LDAP server, and if that fails, I want to try to authenticate locally.
I tried configuring multiple User Providers, using a chain provider, but it does not work. If the first UserProvider fails, it never hits the second one.
# config/security.yaml
security:
providers:
# base local provider
app_user_provider:
entity:
class: App\Entity\User\User
property: username
# LDAP provider
my_ldap:
ldap:
service: Symfony\Component\Ldap\Ldap
base_dn: '%env(resolve:LDAP_BASEDN)%'
search_dn: '%env(resolve:LDAP_SEARCHDN)%'
search_password: '%env(resolve:LDAP_SEARCHPWD)%'
default_roles: ROLE_USER
extra_fields: '%env(resolve:LDAP_EXTRAFIELDS)%'
uid_key: uid
filter: null
# chain
all_users:
chain:
providers: ['my_ldap', 'app_user_provider']
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: all_users
form_login_ldap:
enable_csrf: true
default_target_path: app_homepage
login_path: app_login
check_path: app_login
service: Symfony\Component\Ldap\Ldap
username_parameter: username
password_parameter: password
dn_string: '%env(resolve:LDAP_BASEDN)%'
query_string: '%env(resolve:LDAP_QUERYSTR)%'
search_dn: '%env(resolve:LDAP_SEARCHDN)%'
search_password: '%env(resolve:LDAP_SEARCHPWD)%'
What am I missing? I found a couple of answers here (like Symfony 5: ldap authentication with custom user entity) but it uses the old Authenticator Guard that does not exists.
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
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
I can't get my firewall rule working correctly. I have a user that has the role D-COMPLIANCEDIALOG, and a firewall rule, that grants access to that rule: - { path: ^/ , roles: D-COMPLIANCEDIALOG }. I still get an access denied (Access denied, the user is neither anonymous, nor remember-me.).
#security.yml
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext
providers:
reddot:
id: reddot_user_provider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
secured_area:
pattern: ^/
anonymous: ~
http_basic: ~
simple_form:
authenticator: reddot_authenticator
check_path: login_check
login_path: login
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/ , roles: D-COMPLIANCEDIALOG }
User data from symfony profiler:
Username admin
Authenticated? yes
Roles [D-COMPLIANCEDIALOG]
Inherited Roles { }
Token class Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken
What I checked:
The controller has no own security settings
The role name does not seem to have a typo
It is really the last line in the firewall rule, if I remove it, I do have access.
The role name is incorrect. Please check the documentation Security - Roles
All roles you assign to a user must begin with the ROLE_ prefix. Otherwise, they won't be handled by Symfony's security system in the normal way (i.e. unless you're doing something advanced, assigning a role like FOO to a user and then checking for FOO as described below will not work).
I have faced the same issue when entered 'incorrect' role name and was confused by the error message too.
Although Symfony suggest prefixing the roles with ROLE_.. You can still use your custom roles via Securing by an Expression like:
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/ , allow_if: "has_role('D-COMPLIANCEDIALOG')"}
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.