symfony 5 custom authentication for API REST - rest

I'm working on a project with Symfony 5.
I created the User entity, created the authentication flow on security.yaml and all works well: if user wants to access to protected area, login page was shown and authentication process works! So good!
Now, I want to build an API REST with FOSRest Bundle.
I've created a specific controller for expose some routes:
/**
* #Rest\Route("/api")
*
*/
class APICustomController extends AbstractController
{
...
/**
* #Rest\Get("/shoes")
* #param Request $request
* #Method({"GET"})
*
* #return JsonResponse
*/
public function getShoes(Request $request){
....
return JsonResponse::fromJsonString(array('msg' => 'OK'));
}
}
Here my security.yaml
security:
enable_authenticator_manager: true
encoders:
App\Entity\User:
algorithm: auto
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_VIEWER: ROLE_USER
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: username
api_user_provider:
entity:
class: App\Entity\User
property: api_token
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
guard:
authenticators:
- App\Security\DealmapLoginAuthenticator
logout:
path: app_logout
api:
stateless: true
lazy: true
guard:
authenticators:
- App\Security\TokenAuthenticator
pattern: ^/api/
provider: api_user_provider
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, role: IS_AUTHENTICATED_FULLY }
I followed the steps given here: https://symfony.com/doc/current/security/guard_authentication.html
The problem is the call below
curl -X GET \
http://www.example.com/api/shoes \
-H 'cache-control: no-cache' \
-H 'content-type: application/json' \
-H 'x-auth-token: test'
it is protected by the main firewall (so it returns me the login page), and not by api as expected. I expect to receive an error message in json format.
What's wrong??
Thanks in advance

OK, I found the solution!
I'm posting it here in case someone might need it in the future.
The configuration was all correct, but the reason why the path /api/shoes was managed by the main firewall was due to the order of execution of the rules.
The firewall main handles all the rules, while the api one handles only the ones with the ^/api pattern, so the most stringent rules should go first in the firewall definition in the security.yaml, as below:
security:
enable_authenticator_manager: true
encoders:
App\Entity\User:
algorithm: auto
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_VIEWER: ROLE_USER
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: username
api_user_provider:
entity:
class: App\Entity\User
property: api_token
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
api:
stateless: true
lazy: true
guard:
authenticators:
- App\Security\TokenAuthenticator
pattern: ^/api/
provider: api_user_provider
main:
lazy: true
provider: app_user_provider
guard:
authenticators:
- App\Security\DealmapLoginAuthenticator
logout:
path: app_logout
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, role: IS_AUTHENTICATED_FULLY }

Related

Symfony 4 - authentification 1form 2tables

I would like to know if from the same login form we could connect from two tables: User and Player
Here is my attempt.
security:
encoders:
App\Entity\User:
algorithm: auto
App\Entity\Player:
algorithm: auto
providers:
chain_provider:
chain:
providers: [mon_provider_player, mon_provider_user]
mon_provider_player :
entity:
class: App\Entity\Player
property: email
mon_provider_user :
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
player:
anonymous: lazy
provider: mon_provider_player
form_login:
login_path: login
check_path: login
success_handler: redirect.after.login
logout:
path: logout
target: login
main:
anonymous: lazy
provider: mon_provider_user
form_login:
login_path: login
check_path: login
success_handler: redirect.after.login
logout:
path: logout
target: login
remember_me:
secret: '%kernel.secret%'
lifetime: 604800 # 1 an par défaut
path: /
domain: ~ # Defaults to the current domain from $_SERVER
access_control:
- { path: ^/pol, roles: ["ROLE_POLISTE"] }
- { path: ^/user, roles: ["ROLE_USER"] }
I found the solution. Here to help if needed
Everything happens in the file: loginFormAuthenticator.php which extends AbstractFormLoginAuthenticator in symfony.
There are 5 methods, one of which allows the $user in several tables.
public function getUser($credentials, UserProviderInterface $userProvider)
{
$userFound = $this->userRepository->findOneBy(['email' => $credentials['email']]);
if ($userFound !== NULL) {
return $userFound;
} else {
return $this->playerRepository->findOneBy(['emailJoueur' =>
$credentials['email']]);
}
}

How to handle session user and user with acceess_token at the same time? [duplicate]

I have developed a REST API, there are two ways to connect to it: session and oauth.
Basically, my website will use the session mode and third-party softwares will use the oauth mode.
I managed to make make both session and oauth modes to work in symfony, but I can't make them work at the same time.
Here is my firewalls security config:
firewalls:
auth_oauth_token:
pattern: ^/auth/oauth/v2/token
security: false
api:
pattern: ^/api
anonymous: false
fos_oauth: true
stateless: true
auth:
pattern: ^/
anonymous: ~
form_login:
login_path: /auth/session/check
check_path: /auth/session/login
always_use_default_target_path: true
default_target_path: /auth/session/check
failure_path: /auth/session/check
failure_forward: false
use_forward: false
failure_forward: false
username_parameter: username
password_parameter: password
post_only: true
remember_me: false
require_previous_session: false
logout:
path: /auth/session/logout
target: /auth/session/logged_out
invalidate_session: false
Session handling: /auth/session.
OAuth handling: /auth/oauth.
Api: /api.
So, with this config, with "api" firewall first, I can log in with a token.
But even logged in with a session, if I don't specify the token, I won't have access.
With "auth" firewall first, I can log in with the session form.
But even if I specify a token, I won't have access.
I'm getting crazy with this. I found on stack overflow something about chain providers, I would probably need something like "chain firewall"... if forbidden, check another firewall.
Thank you
I solved by duplicating the routes of the api controllers, so that I have a route /api/method which relies on OAuth2, and a /webapi/method route which relies on the standard (main) firewall:
In security.yml:
firewalls:
api:
pattern: ^/api
fos_oauth: true
stateless: true
oauth_token:
pattern: ^/oauth/v2/token
security: false
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
login_path: /login
check_path: /login_check
logout: true
anonymous: true
access_control:
- { path: ^/api, roles: [ IS_AUTHENTICATED_FULLY ] }
- { path: ^/web-api, roles: [ IS_AUTHENTICATED_FULLY ] }
In routing.yml:
acme_api:
type: rest
prefix: /
resource: "#AcmeBundle/Resources/config/routing_api.yml"
In routing_api.yml:
# REST API - OAUTH Access
acme_api_users:
resource: AcmeBundle\Controller\UsersController
type: rest
defaults: {_format: json}
prefix: /api
name_prefix: api_
# REST API - Frontend Client Access
acme_webapi_users:
resource: AcmeBundle\Controller\UsersController
type: rest
defaults: {_format: json}
prefix: /web-api
name_prefix: webapi_

HWIOAuthBundle : No resource owner with name 'check-google'

I installed HWIOAuthBundle.
But I have this error when I try to login with Google Account :
No ressource owner with name 'check-google'.
And I have the same kind of errror with the others API (Facebook, twitter...)
This is my security.yml :
firewalls:
main:
pattern: ^/login$
security: true
anonymous: true
provider: user_provider
form_login:
login_path: fos_user_security_login
check_path: fos_user_security_check
logout:
path: fos_user_security_logout
target: /
oauth:
resource_owners:
facebook: "/login/check-facebook"
google: "/login/check-google"
twitter: "/login/check-twitter"
linkedin: "/login/check-linkedin"
login_path: /login
check_path: /login
failure_path: /login
oauth_user_provider:
#this is my custom user provider, created from FOSUBUserProvider - will manage the
#automatic user registration on your site, with data from the provider (facebook. google, etc.)
service: my_user_provider
My routing.yml :
#HWIOAuthBundle routes
hwi_oauth_security:
resource: "#HWIOAuthBundle/Resources/config/routing/login.xml"
prefix: /connect/by
hwi_oauth_connect:
resource: "#HWIOAuthBundle/Resources/config/routing/connect.xml"
prefix: /connect/by
hwi_oauth_redirect:
resource: "#HWIOAuthBundle/Resources/config/routing/redirect.xml"
prefix: /login
facebook_login:
pattern: /login/check-facebook
options: { i18n: false }
google_login:
pattern: /login/check-google
options: { i18n: false }
twitter_login:
pattern: /login/check-twitter
linkedin_login:
pattern: /login/check-linkedin
and my config.yml :
# HWIOAuthBundle
hwi_oauth:
connect:
account_connector: my_user_provider
firewall_name: main
fosub:
username_iterations: 30
properties:
# these properties will be used/redefined later in the custom FOSUBUserProvider service.
facebook: facebook_id
google: google_id
twitter: twitter_id
linkedin: linkedin_id
resource_owners:
facebook:
type: facebook
client_id: xxxxx
client_secret: xxxxx
scope: ""
options:
display: popup
google:
type: google
client_id: xxxx
client_secret: xxxx
scope: "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile"
twitter:
type: twitter
client_id: xxxx
client_secret: xxxx
scope: ""
linkedin:
type: linkedin
client_id: xxxx
client_secret: xxxx
scope: "r_basicprofile"
services:
hwi_oauth.user.provider.entity:
class: HWI\Bundle\OAuthBundle\Security\Core\User\OAuthUserProvider
cmf_create.persistence.orm.object_mapper:
class: Midgard\CreatePHP\Mapper\DoctrineOrmMapper
arguments:
- "%cmf_create.map%"
- "#doctrine"
My problem is same than No resource owner with name 'google' (HWIOAuthBundle & FOSUserBundle integration). How can i fix this ?
my best bet is that your firewall is not active on "login with *" URLs
try change:
pattern: ^/login$
I personaly use firewall to all URLs:
pattern: ^/
and explicitly set public urls:
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/, role: ROLE_ADMIN }
- { path: ^/add, role: ROLE_USER }
I have been running into the same issue:
No ressource owner with name 'check-google'.
For me it was solved by changing the routing.yml to this:
google_login:
pattern: /api/login/check/google
I resolved this issue. I found this link helpfull :
http://m2mdas.github.io/blog/2013/11/21/integrate-hwioauthbundle-with-fosuserbundle/
In the above link, After I added cacert.pem to the path, it resolved the issue.
HWIOAuthBundle uses Buzz curl client to communicate with web services. Buzz by default enables SSL certificate check. On some server CA certificate information may not exist. To add CA certificate info download cacert.pem from this page and set curl.cainfo php ini variable to the location of cacert.pem e.g
curl.cainfo = /path/to/cacert.pem
and I missed the above step.
Regards,
Mk6ix

Symfony 2.2 - bad redirect after login in dev environment

After setting up Symfony2.2, I have the following code in my login form:
<input type="hidden" name="_target_path" value="{{path('rok_admin_default_index')}}"/>
Where rok_admin_default_index is pointing to "/" so it's basically the homepage of the application. During the development I'm using app_dev.php (which I obviously), but after successful login in dev environment Symfony redirects me to domain.com/app_dev.php/app_dev.php/ instead of domain.com/app_dev.php/
My security.yml:
jms_security_extra:
secure_all_services: false
expressions: true
security:
encoders:
ROK\AdminBundle\Entity\User: sha512
role_hierarchy:
ROLE_MOD: ROLE_USER
ROLE_ADMIN: [ROLE_MOD, ROLE_USER]
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
main:
entity: { class: ROK\AdminBundle\Entity\User, property: email }
in_memory:
memory:
users:
user: { password: userpass, roles: [ 'ROLE_USER' ] }
mod: {password: modpass, roles: ['ROLE_MOD']}
admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login:
pattern: ^/login$
security: false
secured_area:
pattern: ^/admin/
form_login:
check_path: login_check
login_path: login
logout: ~
#anonymous: ~
#http_basic:
# realm: "Secured Demo Area"
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin, roles: ROLE_ADMIN}
The symfony2 documentation says
The value attribute can be a relative path, absolute URL, or a route
name.
So we can try the second and third options
Use absolute path
<input type="hidden" name="_target_path" value="{{url('rok_admin_default_index')}}"/>
Or Use route name
<input type="hidden" name="_target_path" value="rok_admin_default_index"/>

FOSFacebookBundle does not call custom provider

I'm facing big issue while implementing FOSFacebookBundle.
I followed the docs and have following situation:
* when user clicks login a popup appears
* after user grants permission to the app, FB button is being changed (to Logout)
However, my custom provider is not called (only a constructor is called) - yes, I use a noobish debug method (creating empty files with the name of the class method :-)).
Anybody has any suggestion why? Any tips?
Edit
After some time of trying to solve that issue, I feel I'm lost.
Once again, here's my configuration:
app/config/config.yml:
fos_facebook:
file: %kernel.root_dir%/../vendor/facebook/src/base_facebook.php
alias: facebook
app_id: xxx
secret: xxx
cookie: true
permissions: [email, user_location]
app/config/routing.yml:
_security_login:
pattern: /login
defaults: { _controller: TestBundle:Main:login }
_security_check:
pattern: /login_check
defaults: { _controller: TestBundle:Main:loginCheck }
_security_logout:
pattern: /logout
defaults: { _controller: TestBundle:Main:logout }
app/config/security.yml
security:
factories:
-"%kernel.root_dir%/../vendor/bundles/FOS/FacebookBundle/Resources/config/security_factories.xml"
providers:
my_fos_facebook_provider:
id: my.facebook.user
fos_userbundle:
id: fos_user.user_manager
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
form_login:
provider: fos_userbundle
login_path: /login
check_path: /login_check
logout: true
anonymous: true
public:
pattern: ^/.*
fos_facebook:
app_url: "http://www.facebook.com/apps/application.php?id=xxx"
server_url: "http://symfonytest.com.dev/app_dev.php/"
login_path: /login
check_path: /login_check
provider: my_fos_facebook_provider
default_target_path: /
anonymous: true
logout: true
I'm also implementing code into twig template as shown in docs (also implemented snippet from #Matt).
I have the same workflow as you and my custom user provider is called correctly and everything is working fine.
The first thing that you need to check is: do you have a JavaScript script that redirects the user to the login_check route after it has successfully login into Facebook via the popup? This is important because calling the login_check route after a valid authentication will trigger the security mechanism of Symfony2 that will call the FOSFacebookBundle special security code that will then call your own custom user provider. I think you may be just missing this small piece.
Here the pieces of JavaScript code required to make it work (using jQuery):
$(document).ready(function() {
Core.facebookInitialize();
});
var Core = {
/**
* Initialize facebook related things. This function will subscribe to the auth.login
* facebook event. When the event is raised, the function will redirect the user to
* the login check path.
*/
facebookInitialize = function() {
FB.Event.subscribe('auth.login', function(response) {
Core.performLoginCheck();
});
};
/**
* Redirect user to the login check path.
*/
performLoginCheck = function() {
window.location = "http://localhost/app_dev.php/login_check";
}
}
I put here my security.yml just to help you check for differences with your own file:
security:
factories:
- "%kernel.root_dir%/../vendor/bundles/FOS/FacebookBundle/Resources/config/security_factories.xml"
providers:
acme.facebook_provider:
# This is our custom user provider service id. It is defined in config.yml under services
id: acme.user_provider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
public:
pattern: ^/
fos_facebook:
app_url: "http://www.facebook.com/apps/application.php?id=FACEBOOK_APP_ID"
server_url: "http://localhost/app_dev.php/"
default_target_path: /
login_path: /login
check_path: /login_check
provider: acme.facebook_provider
anonymous: true
logout: true
And my service definition for the custom user provider we use:
services:
acme.user_provider:
class: Application\AcmeBundle\Security\User\Provider\UserProvider
arguments:
facebook: "#fos_facebook.api"
entityManager: "#doctrine.orm.entity_manager"
validator: "#validator"
You also need to create a new route for the /login_check, /login and /logout paths. Those route will be hooked by Symfony2 for the security process. Here an example of the implementation of the actions in a controller called MainController in my cases:
<?php
namespace Application\AcmeBundle\Controller;
use ...;
class MainController extends Controller
{
/**
* This action is responsible of displaying the necessary informations for
* a user to perform login. In our case, this will be a button to connect
* to the facebook API.
*
* Important notice: This will be called ONLY when there is a problem with
* the login_check or by providing the link directly to the user.
*
* #Route("/{_locale}/login", name = "_security_login", defaults = {"_locale" = "en"})
*/
public function loginAction()
{
if ($this->request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
$error = $this->request->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
} else {
$error = $this->request->getSession()->get(SecurityContext::AUTHENTICATION_ERROR);
}
return $this->render('AcmeBundle:Main:login.html.twig', array(
'error' => $error
));
}
/**
* This action is responsible of checking if the credentials of the user
* are valid. This will not be called because this will be intercepted by the
* security component of Symfony.
*
* #Route("/{_locale}/login_check", name = "_security_check", defaults = {"_locale" = "en"})
*/
public function loginCheckAction()
{
// Call intercepted by the Security Component of Symfony
}
/**
* This action is responsible of login out a user from the site. This will
* not be called because this will be intercepted by the security component
* of Symfony.
*
* #Route("/{_locale}/logout", name = "_security_logout", defaults = {"_locale" = "en"})
*/
public function logoutAction()
{
return $this->redirect('index');
}
}
Hope this help, if you have more questions or I misunderstand something from your problem, don't hesitate to leave a comment.
Regards,
Matt