Symfony2, normal + rest authentication - symfony

I am having trouble figuring out how to structure my application.
It is currently a web application built using normal controllers, twig views etc. and using FOS user bundle for authentication. In this application it is possible to create entities that should be seen as a "mobile user"
Now I need an API for a mobile app where the "mobile users" should log in, but I cannot figure out how this authentication should be constructed.
Should I create a user in the user table along with the web app users? Is it possible to require a user to have a specific role to log in on the normal login page?
Or should I add a username and password column to the "mobile user" entity, and make a custom login for the api. But how is this accomplish? I am thinking of using angularjs in the mobile app if this has any impact on how to solve this issue.

One of possible solutions would be using FOSOAuthServerBundle
In this scenario you can have the same place you keep your users for both web app and mobile app. Users can authenticate using the same credentials in web and mobile app - but authentication for mobile app can be done through ajax call.
Thanks to oAuth you don't keep login/password stored at your mobile app.
Bundle itself is written in a way that integrates with Symfony in perfect way.
To access different resources using different security you just configure different firewalls:
security.yml
security:
firewalls:
oauth_token:
pattern: ^/oauth/v2/token
security: false
api:
pattern: ^/api
fos_oauth: true
stateless: true
web_secured:
pattern: ^/
stateless: true
your_security_factory: true
Check these resources for more info:
FOSOAuthServerBundle documentation
step-by-step tutorial

You dont have to use a different user provider but you will need to configure a different firewall in security.yml:
firewalls:
api_firewall:
pattern: "^/api/"
form_login:
check_path: /api/login_check
login_path: /api/login
Then you can still show your users a login form. Using angular, have it post to the check_path. Symfony uses cookies to store authentication information so you may have to configure angular to accept and pass those on subsequent request.
If you dont want to do that you could use an API key and write a custom authenticator implementing SimplePreAuthenticatorInterface

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)

How to connect a client to a Symfony API to make OAuth2 authentication

I am setting up an app made in React + php (symfony/API Platform), where react should use OAuth2 to authenticate the user on Facebook, but I am struggling to connect the frontend to the backend.
For now I am trying to make FB work. I installed react-facebook-login, and configured it. I can see the authentication popup from FB, then I need to call my backend API to authenticate the user and save some info on my local db.
so, php side, I made a controller
class FacebookController extends AbstractController
{
/**
* #Route("/connect/facebook/check", name="connect_facebook_check")
*/
public function connectCheckAction(Request $request, ClientRegistry $clientRegistry)
{
}
}
and an authenticator that extends KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator
and configured it in security.yml:
guard:
authenticators:
- App\Security\Authenticator\FacebookAuthenticator
I can target the controller but I don't understand how to target this authenticator.
Also I am not sure if I am missing something... any help please?
I am following a tutorial here but I want to make a pure stateless API.
I've implemented JWT authentication in API platform, and I know that I indicate the authenticator like so, and it works:
//api/config/packages/security.yaml
security:
providers:
main:
anonymous: true
stateless: true
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
First guess: do you have the 'guard' key at the root level? It should be nested within security:providers:main as in the tutorial.

symfony4 UserProviderInterface implementation with api calls authentification

I'm developing a symfony4 webapp. Users accounts are stored on a DB accessible via an API.
I want to implement this UserProviderInterface as it is advised by the symfony4 documentation to use the symfony4 security module features.
If I understand, implementing this interface requires the API (or service, or db...) to return data (for example hashed password / salt) that will be checked by symfony security.
Is there a way to use the symfony security module without getting such data from the user provider ?
For example to send username and password entered in login form by the user to the api, which will check if it is correct and return a bool ?
You can implement your own Guard Authenticator to perform the authentication checks manually.
There is a good example implementation described in the documentation chapter:
How to Create a Custom Authentication System with Guard
An example configuration would look like this:
security:
firewalls:
firewall_api:
pattern: '^/(.*)+$'
host: '^api\.mypage\.com$'
stateless: true
anonymous: false
guard:
# list of authenticators to try
authenticators:
- 'My\Bridge\Symfony\Security\Authenticator\Guard\JWTTokenAuthenticator'
- 'My\Bridge\Symfony\Security\Authenticator\Guard\FacebookAuthenticator'
# This authenticator's start() method is called
entry_point: 'My\Bridge\Symfony\Security\Authenticator\Guard\JWTTokenAuthenticator'

Symfony FOSUser Bundle - Setting failure_path for password change

I know we can set the failure path for form_login;
form_login:
provider: fos_userbundle
failure_path: /register
default_target_path: /home
how can i set the same structure for profile edit and password change. (profile/edit)
AFAIK, No its not possible.
form_login is handled by symfony2 not by fos_userbundle
By specifying form_login, you have told the Symfony2 framework that
any time a request is made to this firewall that leads to the user
needing to authenticate himself, the user will be redirected to a form
where he will be able to enter his credentials. It should come as no
surprise then that you have specified the user provider service we
declared earlier as the provider for the firewall to use as part of
the authentication process.
Since symfony2 take care of security context token population, if has given way to others to set failure path.
But for your case, its completely in your control, then why you need
that setting?
I suggest you to read more about security

Symfony 2: Accessing a firewalled route with two separate providers

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...

Resources