Symfony 2: Accessing a firewalled route with two separate providers - symfony

We have a system where the administrators and standard users are handled by their own separate security providers. This has caused a problem in the administrative pages because the administrators can't access any files or images that are behind the main site's firewall unless they are also signed in to the main site.
The images and files need to be accessible to all authenticated users and administrators, regardless of provider. They are served through a controller that provides more fine grained access control.
Is it possible to define more than one provider to allow access to a route?
Here's a stripped down version of our current security.yml:
security:
providers:
admin_user_db:
entity: { class: OurAdminUserBundle:AdminUser, property: username }
site_user_db:
entity: { class: OurSiteUserBundle:SiteUser, property: username }
firewalls:
admin_login:
pattern: ^/admin/login$
security: false
site_user_login:
pattern: ^/login
security: false
file_route:
pattern: ^/file
anonymous: ~
### We need to allow this route only for authorized users from
### either admin_user_db or site_user_db providers
admin_secured_area:
pattern: ^/admin
http_basic: ~
provider: admin_user_db
form_login:
check_path: /admin/login_check
login_path: /admin/login
logout:
path: /admin/logout
target: /
site_secured_area:
pattern: .*
http_basic: ~
provider: site_user_db
form_login:
check_path: /check_login
login_path: /login
failure_path: /login
failure_forward: false
logout:
path: /logout
target: /

I guess what you looking for is ChainProvider. This provider will be configured to use your two current providers in a sequential way.
The chain provider will first try to authenticate the user with the first provider it was configured with. If it's a success, the user is authenticated. If it's a failure, the chain provider will try the next one and so on until no more providers can be tried.
I used this technique to let users authenticate with either form login or facebook login. I had two providers, one for facebook, one for form. Then, in my firewall, the provider was the chain provider and then, users were able to login with their credentials or with their facebook account.
Since chain provider is sequential, what I suggest is to put the provider that will probably by used most often first.
Here a configuration sample taken from the Symfony documentation site:
security:
providers:
chain_provider:
chain:
providers: [in_memory, user_db]
in_memory:
users:
foo: { password: test }
user_db:
entity: { class: Acme\UserBundle\Entity\User, property: username
You can take a look at the documenation here:
Using Multiple User Providers
Regards,
Matt

I had some experience with this issue and the solution was the one that Matt wrote.
However, since out application had Administration, Student and Teacher firewalls I couldn't achieve successful impersonation when needed. That is, users from Administration could impersonate users from their firewall and those only.
Now, it is possible that I did something wrong but because this was time sensitive task we decided to put everything behind single firewall and let the different roles do the job. This turned out to be much simpler solution but ultimately it's up to you...

Related

Symfony 3.4 firewall configuration with multiple firewalls and multiple shared guard authenticators

This is a Symfony 3.4 application with a website frontend and a mobile app accessing the same backend. Users can log in by
submitting a username and password (form login)
authentication with a google account
authentication with a facebook account
Previously, there was only one way to log in (username+password). Authentication and the firewall configuration worked. Adding the social network authentication required changes in the firewall configuration and now the guard authentication is partly broken.
What I'm confident about so far is that we need separate firewalls for the web users (main) and mobile app users (api). Web users are authenticated once and then the logged in user info is stored in the session cookie but the mobile app users are authenticated with every incoming request. When mobile users log in successfully, they get a jwt token as a response which they will send with every subsequent request.
What seems to be the biggest problem is the rest of the firewall configuration. In the current configuration, the "main" firewall works as intended. But something about the "api" firewall for mobile users is broken. Logging in works with the same guard authenticators so that they return the jwt token as expected. But the subsequent requests that are sent with the token all result in a 403 access denied response. I suspect that lexic authenticator never gets the jwt token from the request so it looks like the user never logged in.
The authenticators have been tested and they seem to work correctly for both web users and mobile users. The configuration of lexik jwt authenticator is also correct - or it hasn't changed since the time mobile users still had a single authenticator. This means the keys, pass phrase, token ttl.
An idea that might work would be to have a separate firewall for the mobile login urls and the rest of the mobile routes because they're handled by different authenticators. I tried it without any improvement in the situation: logging in works but jwt authentication doesn't. The relevant parts of security.yml below:
security:
...
providers:
db_users:
entity: { ... }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js|api)/(password/reset)
security: false
api:
pattern: ^/api/
stateless: true
lexik_jwt: ~
anonymous: false
guard:
provider: db_users
authenticators:
- main.form_login_authenticator
- main.google_login_authenticator
- main.fb_login_authenticator
- lexik_jwt_authentication.jwt_token_authenticator
entry_point: main.form_login_authenticator
main:
pattern: ^/
guard:
provider: db_users
authenticators:
- main.form_login_authenticator
- main.google_login_authenticator
- main.fb_login_authenticator
entry_point: main.form_login_authenticator
form_login:
remember_me: true
login_path: login
check_path: login
always_use_default_target_path: true
default_target_path: /redirect
target_path_parameter: _target_path
use_referer: false
require_previous_session: false
anonymous: true
...
What's broken here? How should I debug this issue? (other than using postman to emulate json requests from the mobile app)
Additional info: All three custom authenticators create a jwt token, for example:
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// mobile users:
if ( $request->getRequestFormat() == 'json') {
return new JsonResponse(['token' => $this->jwtManager->create($token->getUser())]);
// web users handled here
}
Then about the access patterns (or how the request is handled and by which authenticator), here's an example from main.form_login_authenticator
public function supports(Request $request)
{
return ('login' === $request->attributes->get('_route') || 'api_login_check' === $request->attributes->get('_route'))
&& $request->isMethod('POST');
}
The authenticators seem to work as intended, though. Logging in works. What doesn't work is staying logged in with the jwt token.
Finally it works. The main idea of the solution was to separate the firewalls: one for mobile login routes and another for the rest of the mobile (api) routes. Here's the configuration that works, omissions marked with three dots. main firewall configured as shown in the question.
security:
...
firewalls:
...
api_login:
pattern: ^/api/(login|google-login|fb-login)
stateless: true
anonymous: true
guard:
provider: db_users
authenticators:
- main.form_login_authenticator
- main.google_login_authenticator
- main.fb_login_authenticator
entry_point: main.form_login_authenticator
api:
pattern: ^/api
stateless: true
guard:
provider: db_users
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
main:
...
The prefix of the authenticator service names originally came from the name of the firewall main which doesn't make sense anymore as they're shared between multiple firewalls now.
First, for the api firewall you should only have the jwt authenticator because even if the auth. Is done with social network you must generate your own jwt token.
Secondly, can you send us your access pattern ? This can give us more clues.
Third, iam not sure that the entrypoint for the api firewall should be the same that the form one (https://symfony.com/doc/current/components/security/firewall.html#entrypoint)

Symfony how to have multiple signed in users

I am developing and application in symfony, with a pulic access where users logs in with a login form and an admin section with another login form and another user provider.
I've created a LoginFormAuthenticator for each area, and two firewalls to select the correct authenticator in each area. This is my security.yaml:
security:
providers:
admin_provider:
entity:
class: App\Entity\AdminUser
property: email
web_provider:
entity:
class: App\Entity\User
property: email
firewalls:
admin:
pattern: '^/admin'
anonymous: true
provider: admin_provider
guard:
authenticators:
- App\Security\AdminLoginFormAuthenticator
logout:
path: /admin/logout
target: /
main:
anonymous: true
provider: web_provider
guard:
authenticators:
- App\Security\LoginFormAuthenticator
logout:
path: /logout
Now I am adding an /api to the project, and both users should be able to access, managing the access rights diferently if the user is a public user or is an admin user.
When developing a controller in the /api area, I am unable to get the user when logged via the admin.
The question is, How in /api I can get the AdminUser if it is logged in or the User (in this order) when accessing $this->getuser() or $this->denyAccessUnlessGranted() ?
I've tried to add the App\Security\AdminLoginFormAuthenticator in main firewall and add a chain_provider in main.provider. But it is not working.
Thank you.
The firewalls should share a "common context", for being able to access the same connected users. I think that wording comes from Symfony 2, where the SecurityContext was the service storing the user & authorization.
You need to modify slightly your configuration, and then $this->getUser() and $this->denyAccessUnlessGranted() will return/use the same User object for both firewalls.
firewalls:
admin:
pattern: '^/admin'
context: my_app_context
anonymous: true
# ...
main:
anonymous: true
context: my_app_context
# ...
No need for a common provider or a custom guard. Though it may be easier to have the same User class everywhere, or at least common role for clarity.

2nd Guard Authenticator overwriting Authentication in place

For my app I have 2 different Guard Authenticators in place.
1st for traditional website login. (For employees)
2nd login via URL-getParameter (for clients -> for previewing things in the system)
Both of them are working fine individually.
But when I'm logged in as employee and then open a page with the get-Param iin place, I get logged in as client (understandable so far)
Can I prevent reauthentication in case I was already loggin in by another method? Before moving to guard this was working.
security.yml
security:
providers:
fos_userbundle:
id: fos_user.user_provider.username
firewalls:
main:
pattern: ^/
guard:
authenticators:
- AppBundle\Security\LoginFormAuthenticator
- AppBundle\Security\PreviewAuthenticator
entry_point: AppBundle\Security\LoginFormAuthenticator
Thanks for your advice in advance!

Symfony Chain Multiple Authentication Listeners

I have implemented custom authentication, with the whole shebang (custom Token, Listener, Factory, Provider) which I will call as a whole WebserviceAuthentication.
This, as the name implies, authenticates with a custom webservice, receives a few API tokens, etc. etc. and is working fine by itself.
I now want use the FOSUserBundle to authenticate. I have enabled it in my security.yml file, and the cleaned up version is below. The entire point is to authenticate locally, and if that fails, then authenticate with the webservice.
Both of my providers use different tokens. My token for WebserviceAuthentication has a couple of custom attributes, and is not the same as the UsernamePasswordToken that FOSUserBundle uses.
My question is, how do I use one authentication mechanism first, and then go onto the second one? When I enable it as it is now, I can authenticate with FOSUserBundle Users, but I can't authenticate with my webservice users.
In the current state, if the local authentication fails, it tries to invoke the second WebserviceAuthentication provider. However, it supports() a different a token, which means that it's skipped and never called.
Any advice on how to do this? Is there any way to chain authentication listeners to try the FOSUser listener first, and then try the Webservice? The reason I am thinking Listeners is because that's where the Token is initially created. Any advice is appreciated.
security.yml
providers:
#chain_provider:
# chain:
# providers: [ws_provider, fos_userbundle]
fos_userbundle:
id: fos_user.user_provider.username
ws_provider:
id: ws.user_provider
firewalls:
ws_secured:
pattern: ^/
logout:
path: /logout
target: /
success_handler: bo.logout.success.handler
anonymous: true
wslogin:
check_path: /login_check
login_path: /login
provider: ws_provider
default_target_path: /
success_handler: some.success.handler
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
failure_handler: some.failure.handler
success_handler: some.success.handler

Why doesn't Symfony2 require me to login even after I've configured the security?

This is my security setup for my Symfony2 project:
security:
providers:
main:
users:
asa: { pasword: test, roles: ROLE_USER }
firewalls:
application:
pattern: /.*
http_basic: true
security: true
logout: true
Even though I've followed the documentation, setup a user, require authentication for the whole site, it still allows me to access it as an anonymous user. The logs say "Populated SecurityContext with an anonymous Token"
I'm using the latest version of the sandbox where '.config' was removed from 'security.config'
This wasn't working because I was missing
access_control:
- { path: /.*, role: ROLE_USER }
Looking at the logs and some of the vendor code, the firewalls section attempted to find my user, but without access_control it didn't have any need to force me from being an anonymous user.

Resources