I'm new to Silex, I used this tutorial to set up Doctrine ORM.
But now when I'm trying to log in, i got "Bad credentials" error.
It happens when I use the default "login_check" controller.
If I use a custom one, it works but I don't know how to redirect the user to the page he was looking for. (I tried whith $request->headers->get('referer') in my login controller but it's empty.)
Here's my custom login_check contorller :
$app->post('/login-check-perso', function(Request $request) use ($app){
$route = $request->request->filter('route');
$password = $request->get('_password');
$username = $request->get('_email');
$userProvider = new \Lib\Provider\UserProvider($app);
$user = null;
try {
$user = $userProvider->loadUserByUsername($username);
}
catch (UsernameNotFoundException $e)
{
}
$encoder = $app['security.encoder_factory']->getEncoder($user);
// compute the encoded password
$encodedPassword = $encoder->encodePassword($password, $user->getSalt());
// compare passwords
if ($user->getPassword() == $encodedPassword)
{
// set security token into security
$token = new UsernamePasswordToken($user, $password, 'yourProviderKeyHere', array('ROLE_ADMIN'));
$app['security']->setToken($token);
// redirect or give response here
} else {
// error feedback
echo "wrong password";
die();
}
// replace url by the one the user requested while he wasn't logged in
return $app->redirect('/web/index_dev.php/admin/');
})->bind('login_check_perso');
So if someone can explain how to use the default "login_check", or explain to me how can I redirect user to the page he was trying to visit while not logged, it'll be great.
Thanks
EDIT:
I think the "Bad Credentials" is caused by a wrong encoders setting, I used this
to configure mine :
$app['security.encoder.digest'] = $app->share(function ($app) {
// use the sha1 algorithm
// don't base64 encode the password
// use only 1 iteration
return new MessageDigestPasswordEncoder('sha1', false, 1);
});
$app['security.encoder_factory'] = $app->share(function ($app) {
return new EncoderFactory(
array(
'Symfony\Component\Security\Core\User\UserInterface' => $app['security.encoder.digest'],
'Entity\User' => $app['security.encoder.digest'],
)
);
});
Is that correct ?
You can extend the DefaultAuthenticationSuccessHandler, which will be called after a successfull login, I doing it like this:
// overwrite the default authentication success handler
$app['security.authentication.success_handler.general'] = $app->share(function () use ($app) {
return new CustomAuthenticationSuccessHandler($app['security.http_utils'], array(), $app);
});
Create a CustomAuthenticationSuccessHandler:
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Silex\Application;
class CustomAuthenticationSuccessHandler extends DefaultAuthenticationSuccessHandler
{
protected $app = null;
/**
* Constructor
*
* #param HttpUtils $httpUtils
* #param array $options
* #param unknown $database
*/
public function __construct(HttpUtils $httpUtils, array $options, Application $app)
{
parent::__construct($httpUtils, $options);
$this->app = $app;
}
/**
* (non-PHPdoc)
* #see \Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler::onAuthenticationSuccess()
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
$user = $token->getUser();
$data = array(
'last_login' => date('Y-m-d H:i:s')
);
// save the last login of the user
$this->app['account']->updateUser($user->getUsername(), $data);
return $this->httpUtils->createRedirectResponse($request, $this->determineTargetUrl($request));
}
}
I'm using onAuthenticationSuccess() to save the users last loging datetime. You can use the createRedirectResponse() to redirect the user to the starting point you need.
Related
I have found many tutorials about this issue. But no one of them solved my problem. So, i'm trying on Symfony 4 to follow this tutorial for OAuth2 Facebook
When i click on my button "Connexion with Facebook", i have a blank page with the message :
Error fetching OAuth credentials: "OAuthException: This authorization
code has been used.".
I saw on some tutorials that is a problem about accessToken, longliveAccessToken, etc.
But i have no idea what to do in my code to solve this issue.
Here is my code of my FacebookAuthenticator.php :
<?php
namespace App\Security;
use App\Entity\User; // your user entity
use Doctrine\ORM\EntityManagerInterface;
use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator;
use KnpU\OAuth2ClientBundle\Client\Provider\FacebookClient;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class FacebookAuthenticator extends SocialAuthenticator
{
private $clientRegistry;
private $em;
public function __construct(ClientRegistry $clientRegistry, EntityManagerInterface $em)
{
$this->clientRegistry = $clientRegistry;
$this->em = $em;
}
public function supports(Request $request)
{
// continue ONLY if the current ROUTE matches the check ROUTE
return $request->attributes->get('_route') === 'connect_facebook_check';
}
public function getCredentials(Request $request)
{
// this method is only called if supports() returns true
// For Symfony lower than 3.4 the supports method need to be called manually here:
// if (!$this->supports($request)) {
// return null;
// }
return $this->fetchAccessToken($this->getFacebookClient());
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
/** #var FacebookUser $facebookUser */
$facebookUser = $this->getFacebookClient()
->fetchUserFromToken($credentials);
// 1) have they logged in with Facebook before? Easy!
$existingUser = $this->em->getRepository(User::class)
->findOneBy(['facebookId' => $facebookUser->getId()]);
if ($existingUser) {
return $existingUser;
}
// 2) do we have a matching user by email?
$user = $this->em->getRepository(User::class)
->findOneBy(['email' => $email]);
// 3) Maybe you just want to "register" them by creating
// a User object
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!##$%&*_";
if(!$user){
$user = new User();
}
$user->setFacebookId($facebookUser->getId());
$user->setUsername($facebookUser->getEmail());
$user->setPassword(password_hash(substr( str_shuffle( $chars ), 0, 10), PASSWORD_DEFAULT));
$user->setPrenom($facebookUser->getFirstName());
$user->setNom($facebookUser->getLastName());
$user->setEmail($facebookUser->getEmail());
$user->setEnabled(true);
$user->setSocialAuthentication(true);
$this->em->persist($user);
$this->em->flush();
return $user;
}
/**
* #return FacebookClient
*/
private function getFacebookClient()
{
return $this->clientRegistry
// "facebook_main" is the key used in config/packages/knpu_oauth2_client.yaml
->getClient('facebook_main');
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// on success, let the request continue
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$message = strtr($exception->getMessageKey(), $exception->getMessageData());
return new Response($message, Response::HTTP_FORBIDDEN);
}
/**
* Called when authentication is needed, but it's not sent.
* This redirects to the 'login'.
*/
public function start(Request $request, AuthenticationException $authException = null)
{
return new RedirectResponse(
'/connect/', // might be the site, where users choose their oauth provider
Response::HTTP_TEMPORARY_REDIRECT
);
}
}
My user is created in my database, with correct data, but can't authenticate with it.
Thanks for help me, if you want the code for FacebookController.php , tell me, then i will edit my post.
EDIT :
EDIT 2 :
public function getUser($credentials, UserProviderInterface $userProvider)
{
/** #var FacebookUser $facebookUser */
$client = $this->clientRegistry->getClient('facebook_main');
$accessToken = $client->getAccessToken();
if ($accessToken && !$accessToken->getToken()) {
dump("User is not found!"); die;
}
$provider = $client->getOAuth2Provider();
$longLivedToken = $provider->getLongLivedAccessToken($accessToken);
//I get the user by using long lived token
$facebookUser = $client->fetchUserFromToken($longLivedToken);
$email = $facebookUser->getEmail();
// 1) have they logged in with Facebook before? Easy!
$existingUser = $this->em->getRepository(User::class)
->findOneBy(['facebookId' => $facebookUser->getId()]);
if ($existingUser) {
return $existingUser;
}
// 2) do we have a matching user by email?
$user = $this->em->getRepository(User::class)
->findOneBy(['email' => $email]);
// 3) Maybe you just want to "register" them by creating
// a User object
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!##$%&*_";
if(!$user) {
$user = new User();
$user->setFacebookId($facebookUser->getId());
$user->setUsername($facebookUser->getEmail());
$user->setPassword(password_hash(substr(str_shuffle($chars), 0, 10), PASSWORD_DEFAULT));
$user->setPrenom($facebookUser->getFirstName());
$user->setNom($facebookUser->getLastName());
$user->setEmail($facebookUser->getEmail());
$user->setEnabled(true);
$user->setSocialAuthentication(true);
}
$this->em->persist($user);
$this->em->flush();
return $user;
}
I had same problem and I've refered the long live access token with this way. It might be a solution. Here you are.
$client = $clientRegistry->getClient('facebook_main');
$accessToken = $client->getAccessToken();
if ($accessToken && !$accessToken->getToken()) {
dump("User is not found!"); die;
}
$provider = $client->getOAuth2Provider();
$longLivedToken = $provider->getLongLivedAccessToken($accessToken);
//I get the user by using long lived token
$facebookUser = $client->fetchUserFromToken($longLivedToken);
The first error you have:
Error fetching OAuth credentials: "OAuthException: This authorization code has been used.".
Thats because you reload your Page (after e.g. change Code and refresh). The Error Message said that the Auth Code in Url Params used before.
Instead refresh your Site 1. go Back to the site where your button is placed and 2. get a new Auth Code by Clicking the FB Login Button.
Now this error is gone and you can go forward to debug your code.
We are working in a login form, using simfony and a REST Webservice.
We have been searching in this link (http://symfony.com/doc/current/security/custom_provider.html)
The goal is login in with the form and the REST web service, updating my session data, like, name, doc, email, etc.
And with this data allow or deny the access to some pages or functions.
When we submit the form, we donĀ“t know how to use the data returned by the webservice, also if there are response or not.
This is our code:
SecurityController.php
<?php
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class SecurityController extends Controller {
// public function loginAction(AuthenticationUtils $authenticationUtils) {
public function loginAction(Request $request, AuthenticationUtils $authenticationUtils) {
// $authenticationUtils = $this->get('security.authentication_utils');
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('AppBundle:Security:login.html.twig', array('last_username' => $lastUsername, 'error' => $error));
// return $this->render('AppBundle:Security:login.html.twig');
}
public function loginCheckAction() {
$ca = $this->get('webservice_user_provider');
print_r($ca);
exit;
}
}
Login.html.twig-----
<form class="form-signin" action="{{ path('app_user_login_check') }}" method="POST">
Security.yml-----------------------
webservice:
id: webservice_user_provider
Archivo services.yml----------------------------
webservice_user_provider:
class: AppBundle\Security\User\WebserviceUserProvider
WebserviceUserProvider.php-----------------------------
<?php
// src/AppBundle/Security/User/WebserviceUserProvider.php
namespace AppBundle\Security\User;
use AppBundle\Security\User\WebserviceUser;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Unirest;
class WebserviceUserProvider implements UserProviderInterface {
protected $user;
public function __contsruct(UserInterface $user) {
$this->user = $user;
}
public function loadUserByUsername($username) {
// make a call to your webservice here
print_r("Estoy en el controlador de usuario");
exit;
$headers = array('Accept' => 'application/json');
$password = $this->request->get('password');
$query = array('user' => $username, 'password' => _password);
$userData = Unirest\Request::post('http://127.0.0.10:8888/login', $headers, $query);
// pretend it returns an array on success, false if there is no user
if ($userData) {
$datos = $userData->raw_body;
// print_r($userData);
// print_r($userData->body);
// print_r($userData->raw_body);
$username = $datos['ldap']['document'];
$password = $datos['ldap']['document'];
$salt = $datos['ldap']['document'];
$roles = $datos['ldap']['document'];
$doc = $datos['ldap']['document'];
$full_name = $datos['ldap']['document'];
$userLdap = $datos['ldap']['document'];
$userEpersonal = $datos['ldap']['document'];
$mail = $datos['ldap']['document'];
$position = $datos['ldap']['document'];
return new WebserviceUser($username, $password, $salt, $roles, $documento, $full_name, $userLdap, $userEpersonal, $mail, $position);
}
throw new UsernameNotFoundException(
sprintf('Username "%s" does not exist.', $username)
);
}
public function refreshUser(UserInterface $user) {
if (!$user instanceof WebserviceUser) {
throw new UnsupportedUserException(
sprintf('Instances of "%s" are not supported.', get_class($user))
);
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class) {
return WebserviceUser::class === $class;
}
}
I will give you a general overview, for implementation details you may want to ask another question.
A REST web service should be stateless, i.e. (to simplify a bit) should have no session. To implement ACL you may have different strategies.
The easiest one is to perform authentication on each request. You may use http authentication, or you can use an API key as many webservices do. Your webservice will always authenticate the user as the first step in each request.
A slightly more secure strategy is to have authentication return you a temporary token. I.e. first you request the login action with whatever authentication system you choose (you can even have more than one) and you get back a randomly generated token associated with your user. In the next requests you include this token and the system know who you are.
I setup a middleware on a route so that if anyone browses to it, they should be logged to facebook first, if not they'll be redirected to facebook:
Route::get( '/events/facebook', 'EventsController#facebookEvents' )->middleware('CheckFB');
It works fine, however, now the route keeps redirecting back to Facebook over and over.
This is the middleware:
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next, $provider=null)
{
$provider = 'facebook';
$this->validateProvider($provider);
if ($provider === 'facebook') {
return Socialite::driver($provider)->scopes([
'email',
'public_profile',
'rsvp_event',
'user_birthday',
'user_events',
'user_friends',
])->redirect();
}
return Socialite::driver($provider)->redirect();
}
What I want is that if the user is already logged, he shouldn't be redirected! only the first once.
I tried this:
$user = Socialite::driver('facebook')->user();
But it makes this error:
GuzzleHttp \ Exception \ ClientException (400) Client error: POST
https://graph.facebook.com/v2.10/oauth/access_token resulted in a
400 Bad Request response: {"error":{"message":"This authorization
code has been
used.","type":"OAuthException","code":100,"fbtrace_id":"***
(truncated...)
Im using Auth::check(); to know if an user is logged
use App\User;
use Auth;
use Socialite;
use Redirect;
The method recives as param a $service in this case Facebook
//route to handle the callback
Route::get('/callback/{service}', 'SocialAuthController#handleProviderCallback');
public function handleProviderCallback($service)
{
if(Auth::check())//if user is logged
{
$user_id = Auth::id(); //you get the user ID
$authUser = User::where('id', $user_id)->first(); //you should find the user in the User table
$user_service = $authUser->service; //I saved in the database the service used to log in, so I call it
return view ( 'home' )->withDetails ( $authUser )->withService ( $user_service ); //then I return the view with the details
}else //if user is not login in
{
$user = Socialite::driver( $service )->user();
$authUser = $this->findOrCreateUser($user, $service);//personal method to know if the user is new or it is already saved on DB
Auth::login($authUser, true);//Login if the authUser return is true
return view ( 'home' )->withDetails ( $user )->withService ( $service );
}
Hope it works for you!
I am using Facebook PHP SDK to log my user.
I created a guard called login for this
Here is my config file of auth.php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
'admin'=>[
'driver'=>'session',
'provider'=>'adminusers',
],
'verify'=>[
'driver'=>'session',
'provider'=>'verify',
],
'login'=>[
'driver'=>'session',
'provider'=>'users'
]
],
to access Facebook api i created a class in App\services namespace called it Facebook
App\Services\Facbook.php
<?php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Config;
use App\Extensions\Facebook\FacebookLaravelPersistentDataHandler;
use Facebook\Facebook as FB;
use App;
class Facebook{
protected $fb;
protected $helper;
protected $permission;
protected $log;
protected $canvashelper;
protected $persistentDataHandler;
function __construct()
{
$this->fb = new FB([
'app_id'=>Config::get('facebook.app_id'),
'app_secret'=>Config::get('facebook.app_secret'),
'default_graph_version' => Config::get('facebook.default_graph_version'),
'persistent_data_handler' => new FacebookLaravelPersistentDataHandler(),
]);
$this->helper = $this->fb->getRedirectLoginHelper();
$this->permission = Config::get('facebook.permission');
$this->log = new Logging(Config::get('facebook.logfile'),'Facebook Log');
$this->canvashelper = $this->fb->getCanvasHelper();
$this->persistentDataHandler = new FacebookLaravelPersistentDataHandler();
}
public function FBAuthUrl()
{
if($this->isFBAuth())
{
return $this->helper->getLogoutUrl($this->persistentDataHandler->get('facebook_access_token'),route('facebook.logout'));
}
else
{
return $this->helper->getLoginUrl(route('facebook.callback'),$this->permission);
}
}
public function LoginCallback()
{
$accessToken = $this->helper->getAccessToken();
if(isset($accessToken))
{
$this->persistentDataHandler->set('facebook_access_token',(string) $accessToken);
}
}
public function isFBAuth()
{
return $this->persistentDataHandler->has('facebook_access_token');
}
public function getFBUser()
{
if($this->isFBAuth())
{
$this->fb->setDefaultAccessToken($this->persistentDataHandler->get('facebook_access_token'));
/*,user_birthday,user_tagged_places*/
$response = $this->fb->get("/me?fields=id,name,first_name,last_name,age_range,link,gender,locale,picture,timezone,updated_time,verified,email");
return $response->getGraphUser();
}
else
{
return false;
}
}
public function logout()
{
$this->persistentDataHandler->delete('facebook_access_token');
$this->persistentDataHandler->delete('state');
}
}
And Here is my UserController Where i write my login logic
class UserController extends Controller
{
.....
/*
* Facebook login callback function
* #param Object App\services\Facebook
* return redirect
*/
public function fbLogin(Facebook $facebook)
{
$facebook->LoginCallback();
/*
* get the usergraphnode from facebook
*/
$fbUser = $facebook->getFBUser();
/*
* Convert UserGraphNode User To Eloquent User
*/
$user = $this->getFBLoggedUser($fbUser);
/*
* Here Log the user in laravel System
*/
Auth::guard('login')->login($user);
//dump(Auth::guard($this->guard)->user());
dump(session()->all());
return reidrect('/');
}
public function getFBLoggedUser($fbUser)
{
if(User::where('email','=',$fbUser->getField('email'))->count())
{
$user = User::where('email','=',$fbUser->getField('email'))->first();
if($user->fb_app_id){
$user->fb_app_id = $fbUser->getField('id');
$user->save();
}
}
else
{
$user = $this->FBregister($fbUser);
}
return $user;
}
/**
* Register The user logged in from Facebook
*
* #param \Facebook\GraphNodes\GraphUser;
*
* return \App\Models\User
*/
public function FBregister($fbUser)
{
$user = new User();
$user->fname = $fbUser->getField('first_name');
$user->lname = $fbUser->getField('last_name');
$user->gender = $fbUser->getField('gender');
$user->email = $fbUser->getField('email');
$user->fb_app_id = $fbUser->getField('id');
$picture = $fbUser->getField('picture');
if($picture->isSilhouette()){
$user->profile_image = $picture->getUrl();
}
$user->save();
return $user;
}
.........
}
On Successful Facebook login redirect i am calling UserController#fbLogin
after calling Auth::guard()->login() i dump session it successfully show a login_login_randomstring=>UserId i session . but When i redirect it all session data lost.
But the weird thing is that it only happen when it calling through facebook redirect. If i use this function like normal login routes it works perfactaly like this
in route.php
Route::get('/login','UserController#login');
and in UserController
function login(){
$user = User::find(12);
Auth::guard('login')->login($user);
return redirect('/');
}
Using this method i can easily access Session data after redirecting from here but in facebook case it doesn't happening.
I stuck here for two days please anyone can help me
[Note: Please don't mention in your answer that i should grouped my routes in web middleware. ]
After digging very deep in laravel i finally found what i was doing wrong. And i am posting may be it help some in future.
Important thing :- Laravel save session very last in its request life-cycle. It saves session it sends header response. So if we echo something in controller class then it will send header response without doing saving session and our session will not save. In my case i use dump function in my controller which terminate the laravel default life-cycle and forcefully send header response to browser. that's why all of session data is lost. i remove dump() form my code and everything start working correctly
According to API documentation https://laravel.com/api/5.2/Illuminate/Auth/Guard.html you should call user() method to get the currently authenticated user. So i would suggest that instead of Auth::guard() use Auth::user($user).
try to use plugin socialite for login with facebook socialite
Facebook php sdk use $_SESSION.In laravel you cannot access this variable,laravel use personal class for session.
According to api code and your facebook documentation. Simple session working with request. You can save your data with
For put session in value
Session::put('userid','1');
Retrieve the value
$request->session()->get('userid') //or
{!! Session::get('userid') !!}
Very useful thing in your case.
Using the FOSUserbundle, I want to achieve the following:
1) User submits a POST with "username" and "password" parameters. Password is in plaintext.
2) In a controller, I parse the parameters as usual:
/**
* #Route("/api/token",name="api_token")
*/
public function tokenAction(Request $request) {
$username = $request->get('username');
$password = $request->get('password');
// ...
}
3) Finally, if the credentials match, I return something.
Note that I want to do this WITHOUT modifying the session, i.e. without actually authenticating the user and setting a token. I only want to check if the credentials match.
UPDATE
The accepted answer works, but only under the assumption that you have an active session. If you want to solve the case where you simply expose a REST layer or the like (which was my usecase), you can do the following, assuming your usernames are unique:
/**
* #Route("/api/token",name="api_token", options={"expose"=true})
*/
public function getTokenAction(Request $request) {
$username = $request->get('username');
$password = $request->get('password');
$user = $this->getDoctrine()
->getRepository('YourUserClass')
->findOneBy(["username" => $username]);
$encoder = $this->get('security.encoder_factory')->getEncoder($user);
$isValidPassword = $encoder->isPasswordValid($user->getPassword(),
$password,
$user->getSalt());
if ($isValidPassword) {
// Handle success
} else {
// Handle error
}
}
You should use an authentication service to do this, writing all code in controller is not a best practice. Anyway, to your answer, you can use this:
/**
* #Route("/api/token",name="api_token")
*/
public function tokenAction(Request $request) {
$username = $request->get('username');
$password = $request->get('password');
// fetch your user using user name
$user = ...
//If your controller is extended from Symfony\Bundle\FrameworkBundle\Controller\Controller
$encoder = $this->get('security.encoder_factory')->getEncoder($user);
//If you are extending from other ContainerAware Controller you may have to do
//$this->container->get('security.encoder_factory')->getEncoder($user)
//Check your user
$isValidPassword = $encoder->isPasswordValid(
$user->getPassword(),
$password,
$user->getSalt()
);
if ($isValidPassword) {
//.... do your valid stuff
}else{
//... Do your in valid stuff
}
}