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
Related
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)]);
}
}
I am using Gitlab authentication in my current symfony project. I am using following two packages.
omines/oauth2-gitlab
knpuniversity/oauth2-client-bundle
Inside my security.yaml file
security:
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
app_user_provider:
id: App\Security\UserProvider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
guard:
authenticators:
- App\Security\GitlabAuthenticator
access_control:
- { path: ^/connect, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_USER }
This is working fine and it is storing cookie PHPSESSID. I need to retrieve the username after successful login.
It is very very simple. Just use $this->getUser().
public function index()
{
/** #var \App\Entity\User $user */
$user = $this->getUser();
return new Response('Well hi there '.$user->getFirstName());
}
UserProvider (that's used in login process) returns a UserInterface, which represent a User. From it you can access the username.
Now, how to retrieve that user?
Depending on Symfony version, and depending where you want to access it, you have several possibilities:
Symfony <3.2
In controller -> you can use $this->getUser()->getUsername();
Symfony >=3.2
In controller -> Inject UserInterface directly as a dependency (read more)
For both cases, if you need the user not in controller, you can inject (based on your symfony version) the TokenStorage or the Security service (can't find which version supports which, for sure Security component is supported in sf5).
If you choose (or are forced) to go with TokenStorage (or its interface), you need to perform the following steps:
getToken
check if the token is not null
retrieve the username from the token (or optionally retrieve the whole user and then retrieve the username from there)
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');
I'm looking for a working solution, to hide pages from authenticated users in symfony. Unfortunately, the default user roles are staggered. The following configuration does not work:
# app/config/security.yml
access_control:
# This rules works not:
- { path: ^/login, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
# This rule works:
- { path: ^/logout$, role: roles: [ROLE_USER] }
For my opinion, this ruleset should end up in a HTTP 404 Unauthorized after visiting /login or /register as a authenticated user (UsernamePasswordToken) - but it does not!
After some reasearch, I found more information. According to Symfony 2 documentation, users are automatically given one of the following roles depending on how they are authenticated:
IS_AUTHENTICATED_REMEMBERED
IS_AUTHENTICATED_ANONYMOUSLY
IS_AUTHENTICATED_FULLY
If you have the IS_AUTHENTICATED_REMEMBERED role, then you also have
the IS_AUTHENTICATED_ANONYMOUSLY role. If you have the
IS_AUTHENTICATED_FULLY role, then you also have the other two roles.
In other words, these roles represent three levels of increasing
"strength" of authentication.
In other words: There seems to be no possible way, to hide pages from a authenticated user...
1. Possible solution - Use a static role (does not work...)
Using the access control role IS_ANONYMOUS could be great option, but it does not work for paths behind a Firewall: Symfony will append a AnonymousToken to your session each time you visit any page behind ^/:
# app/config/security.yml
security:
firewalls:
main:
pattern: ^/
...
2. Possible solution - Edit all controllers (takes way too long...)
I ended up rewriting all controllers (.....) and implemented custom, hard-coded access rules into the controllers.
I hope, somebody has a smiliar problem and a simple stupid solution.
You can easily solve this with the use of the expression-language component ( symfony ~2.4):
access_control:
- { path: ^/whatever, allow_if: "!is_fully_authenticated()" }
For symfony <2.4 you can use JMSSecurityExtraBundle which provides expression-based security rules aswell.
request, token and user are the variables you have access to and
is_anonymous(), is_authenticated(), is_fully_authenticated(),
is_rememberme(), and has_role() are the functions defined in this
context.
Read more about the expression-language:
introduction
documentation
syntax
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.