Anonymous and authenticated users for same firewall in Symfony - php

I have two firewalls for auth and api. But i want to combine them and be able to check if there is some user in controller.
firewalls:
user:
pattern: ^/api/auth
stateless: true
anonymous: true
json_login:
check_path: /api/auth/login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
require_previous_session: false
api:
pattern: ^/api
stateless: true
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
access_control:
- { path: ^/api/auth, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
What I want to do is to combine firewalls to one and allow any user everythere, but still be available to identify user if JWT passwed.
Is it possible?

In my opinion,to achieve this you will need only the firewall matching ^/api pattern and continue using guard authenticator. Then, inside your authenticator, check for the exact route requested and chose whether to continue with normal guard authenticator flow or use a custom logic to implement json login. But it is a little bit dirty even if guard permits you to implement your own logic. Keeping separated firewalls sounds better.

I think it is better to keep separate firewalls, as mentioned already, but in case you absolutely need to keep your application open to everybody and check if the user is authenticated in your controller, you can refer to symfony's documentation and use something like:
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');

Related

Symfony 4 Restful API login with JWT and facebook

I have a question regarding authentication with JWT and Facebook for a restful API app.
I am using Symfony 4 and for authentication "lexik/jwt-authentication-bundle": "^2.6", to generate jwt tokens based on username and password.
Here is my configuration security.yaml:
security:
encoders:
App\Entity\User: bcrypt
providers:
database:
entity:
class: App\Entity\User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login:
pattern: ^/api/login
stateless: true
anonymous: true
json_login:
check_path: /api/login
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
register:
pattern: ^/api/register
anonymous: true
methods: [POST]
docs:
pattern: ^/api/docs
anonymous: true
methods: [GET]
api:
pattern: ^/api
stateless: true
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
access_control:
- { path: ^/api/docs, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/register, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/profile/social-account, roles: ROLE_INFLUENCER }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
I am trying to add also login by facebook but I have no idea how to handle that with JWT, as I want to return after login a JWT token.
Is there any way to handle facebook login and JWT login?
You can have a good idea of how to implement it here : https://github.com/lexik/LexikJWTAuthenticationBundle/issues/295.
Basically after the user accept the Facebook login on your frontend :
send a POST request with the user's token to your custom endpoint (for example login/facebook.
Then you can make an extra request with the user's token to Facebook api to get extra information as firstname, lastname or user's email (for example with https://graph.facebook.com/me/?fields=first_name,last_name,email).
The token needs to have correct permissions to get access to those information (https://developers.facebook.com/docs/facebook-login/permissions/overview).
You can now create a user if the email is not in your database.
Finally just return the token with create method of JWTManager class.
$token = $this->jwtManager->create($user);
By using this logic, it is totally transparent for your frontend whether your user has already loged in with your app or not and it gets a token the same way a user would get with your /login endpoint.
I assume you have an frontend application (React/Angular/Vue...) that consumes your API.
I would handle the "login with Facebook" on the Frontend side. After the user accepts the authentication via Facebook, they will be send back to your frontend app. At that point you have access to all the information you need (e.g. e-mail, first name, last name, etc).
POST that information to a new endpoint (e.g. /api/facebook-login), which handles your registration and/or login process, and then return an JWT token with your newly created user. Lexik makes it possible to manually create tokens:
class ApiController extends Controller
{
public function getTokenUser(UserInterface $user, JWTTokenManagerInterface $JWTManager)
{
// ...
return new JsonResponse(['token' => $JWTManager->create($user)]);
}
}

LexikJWTAuthenticationBundle with multiple providers

I 2 part of applications - first for admins (admin panel) and second API.
For API I want to use another model to check credentials and that retrieve a token.
I thought that it could be achieved by specified check_path route where I can verify the provided data and then return manually token.
But It seems that the application doesn't event go to this endpoint because I haven`t seen any debug message from the response - only 401 error code.
Here is my security.yml config:
security:
encoders:
App\Entity\Security\AdminUser:
algorithm: bcrypt
Lexik\Bundle\JWTAuthenticationBundle\Security\User\JWTUser:
algorithm: bcrypt
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
jwt:
lexik_jwt: ~
firewalls:
api:
provider: jwt
pattern: ^/api/
stateless: true
anonymous: true
guard:
authenticators:
- 'jwt.token.authenticator'
json_login:
check_path: api.v1.0.token.get
username_path: passwordName
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
context: 'main'
pattern: ^/
form_login:
provider: fos_userbundle
default_target_path: easyadmin
csrf_token_generator: security.csrf.token_manager
logout: true
anonymous: true
access_control:
- { path: ^/api/doc, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/v1.0/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
And here is my action where I tried to debug:
class TokenController extends AbstractController
{
/**
* #Route("/login", name="api.v1.0.token.get", methods={"POST"})
* #param Request $request
*/
public function obtainToken(Request $request, JWTEncoderInterface $encoder, SiteRepository $siteRepository)
{
dd(123); // I don`t see this message - only 401 error
}
}
First, I'm not sure what you're trying to do with your obtainToken function but if you need to either create a token programatically or manipulate / customize its content before returning it, I highly suggest that you have a look at their documentation first as you'll have all the tools to achieve what you want to do:
here for the customization through events
here for manual creation
Otherwise, the bundle will handle that for you.
Now, assuming that you simply want to protect your api with JWT, you'll need to separate your api firewall into two different ones:
First one to login, like so:
login:
pattern: ^/api/login
stateless: true
anonymous: true
json_login:
check_path: /api/login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
And don't forget to update the access control to make sure your users can access it anonymously:
- { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
This will allow you to hit /api/login_check with the credentials to authenticate and obtain your token.
Then, protect the rest of your public api by defining the JWT guard authenticator, like so:
api:
pattern: ^/api
stateless: true
# /!\ shouldn't be anonymous: true here
provider: jwt
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
And the access control too:
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
If you need to create an account, you can define your own route and create the token programmatically upon success.
Some other things to mention:
check_path: api.v1.0.token.get : I actually never tried but I don't think you can define a path by its route name like so, you better specify the path directly.
username_path: passwordName : here you're telling the bundle to use 'passwordName' as the username, which sounds weird.
If you want to specify custom identifiers for both username and password, you better use something like this:
username_path: email # (or whatever field you use for the authentication)
password_path: password

Symfony2 allow anonymous for certain routes with certain methods

I am developing a backend service for my own project with Symfony2. What i would like to do is simple user registration. Whenever a user needs to be created there will be a POST call to
/v1.0/users (with POST method)
I would like to create a new user. For all of the other url should be authenticated except this one. So I created UserProvider and UserAuthenticator as described in here : http://symfony.com/doc/current/cookbook/security/api_key_authentication.html
I created a secured area and it works fine, but i want to disable this firewal for the url above with post method. I couldnt figure it out how. Here is my security.yml file
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
api_user_secured_area:
pattern: ^/v1.0/users
stateless: true
simple_preauth:
authenticator: user_token_authenticator
provider: user_token_provider
access_control:
...
user_register:
path: /v1.0/users
roles: IS_AUTHENTICATED_ANONYMOUSLY ?? FOR POST ONLY ??
PS: I dont want to use annotation for security (like #Security in the controller)
Access control can be filtered to a given METHOD using the Methods property, please see here for more filters/options regarding access control:
http://symfony.com/doc/current/cookbook/security/access_control.html
Here is the option integrated into your code:
access_control:
user_register:
path: /v1.0/users
methods: [ POST ]
roles: IS_AUTHENTICATED_ANONYMOUSLY

Symfony2 auto-login after user registration

I have seen some posts in this regard, specifically Auto-Login via URL with symfony2 (which I can't use because I do not know what needs to go into $request) and Automatic post-registration user authentication. I tried the latter, but it is not logging in.
Code from security.yml
firewalls:
admin_login_firewall:
pattern: ^/admin/login$
anonymous: ~
admin:
pattern: ^/admin
form_login:
login_path: admin_login
check_path: admin_login_check
default_target_path: admin_dashboard
logout:
path: admin_logout
target: admin_login
http_basic:
realm: "Licensing Admin Portal"
member_login_firewall:
pattern: ^/members/login$
anonymous: ~
members:
pattern: ^/members
form_login:
login_path: member_login
check_path: member_login_check
default_target_path: member_dashboard
logout:
path: member_logout
target: home
http_basic:
realm: "Licensing Member Portal"
encoders:
Pmb\LicensingBundle\Entity\User: plaintext
access_control:
- { path: ^/admin/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/members/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/members/, roles: ROLE_USER }
Code snippet from Controller function saveUserAction():
... // Code preceding this point creates user entity and sets all fields
$em = $this->getDoctrine()->getManager();
if (empty($data['user_id'])) $em->persist($user);
$em->flush();
if (!empty($organization)) $this->linkOrganizationUserAction($organization,$user, true);
if (isset($data['registering']))
{
$token = new UsernamePasswordToken($user, null, 'members', $user->getRoles());
$this->get('security.context')->setToken($token);
$this->get('session')->set('_security_main',serialize($token));
}
return $this->createJsonResponse($user);
I am trying to log in to the members firewall. I do not know enough about this to troubleshoot. Most of this is just copy/paste/edit. Any help / explanation would be greatly appreciated. I also read the article under Symfony2 auto-login after registration, but I do not see the significance of this, as I do not need have users logged in accross different firewalls, and just need the user logged in under the members firewall.
One thing that I DID notice, is that the user entity is having its salt field populated when persiting to the database, even though I did not set a salt and I cannot see anything auto-setting the salt. I am not yet using the salt as I am not yet encrypting my passwords (just trying to get it working with plain text passwords first), and when trying to log in with a created user (which does get created, just not getting logged in) I cannot log in unless I clear the salt on the user. I don't know if this has anything to do with the fact that the auto-login is not working.
The issue here was with the line $this->get('session')->set('_security_main',serialize($token));. The "_main" of "security_main" is also the firewall that you are authenticating against. So it should have been "_security_members". After changing it the code worked as is.

Secure area in Symfony2

I have a question to the security features of Symfony2. I want to protect a special area of my application under the /my prefix.
My configuration looks like follows:
security.config:
providers:
my:
entity: { class: MyUserBundle:User, property: username }
firewalls:
public:
pattern: /my/login.*
security: false
my:
pattern: /my.*
form-login:
check_path: /my/login_check
login_path: /my/login
logout: true
access_control:
- { path: /my/login.*, roles: IS_AUTHENTICATED_ANONYMOUSLY }
When I try to access the login area, everything works fine, submitting the form leads to an error page, because there is no registered controller for the _security_check route, like its described in the guide:
_security_login:
pattern: /my/login
defaults: { _controller: MyUserBundle:Auth:login }
_security_check:
pattern: /my/login_check
I think normally the SecurityBundle hacks into this process so that no controller is needed. The configuration of Symfony2 is allways very complex.
I think I missed something, hope you can help.
Thanks in advance!
I solve the problem with the help of the symfony users mailing group.
You have to define one firewall (that describes all routes) and determine secure zone using access_control part of settings.

Categories