Related
I'm writing a tiny sms gateway to be consumed by a couple of projects,
I implemented laravel passport authentication (client credentials grant token)
Then I've added CheckClientCredentials to api middleware group:
protected $middlewareGroups = [
'web' => [
...
],
'api' => [
'throttle:60,1',
'bindings',
\Laravel\Passport\Http\Middleware\CheckClientCredentials::class
],
];
The logic is working fine, now in my controller I need to get client associated with a valid token.
routes.php
Route::post('/sms', function(Request $request) {
// save the sms along with the client id and send it
$client_id = ''; // get the client id somehow
sendSms($request->text, $request->to, $client_id);
});
For obvious security reasons I can never send the client id with the consumer request e.g. $client_id = $request->client_id;.
I use this, to access the authenticated client app...
$bearerToken = $request->bearerToken();
$tokenId = (new \Lcobucci\JWT\Parser())->parse($bearerToken)->getHeader('jti');
$client = \Laravel\Passport\Token::find($tokenId)->client;
$client_id = $client->id;
$client_secret = $client->secret;
Source
However the answer is quite late, i got some errors extracting the JTI header
in Laravel 6.x because the JTI is no longer in the header, but only in the payload/claim. (Using client grants)
local.ERROR: Requested header is not configured {"exception":"[object] (OutOfBoundsException(code: 0): Requested header is not configured at /..somewhere/vendor/lcobucci/jwt/src/Token.php:112)
Also, adding it in a middleware was not an option for me. As i needed it on several places in my app.
So i extended the original Laravel Passport Client (oauth_clients) model.
And check the header as well as the payload. Allowing to pass a request, or use
the request facade, if no request was passed.
<?php
namespace App\Models;
use Illuminate\Support\Facades\Request as RequestFacade;
use Illuminate\Http\Request;
use Laravel\Passport\Client;
use Laravel\Passport\Token;
use Lcobucci\JWT\Parser;
class OAuthClient extends Client
{
public static function findByRequest(?Request $request = null) : ?OAuthClient
{
$bearerToken = $request !== null ? $request->bearerToken() : RequestFacade::bearerToken();
$parsedJwt = (new Parser())->parse($bearerToken);
if ($parsedJwt->hasHeader('jti')) {
$tokenId = $parsedJwt->getHeader('jti');
} elseif ($parsedJwt->hasClaim('jti')) {
$tokenId = $parsedJwt->getClaim('jti');
} else {
Log::error('Invalid JWT token, Unable to find JTI header');
return null;
}
$clientId = Token::find($tokenId)->client->id;
return (new static)->findOrFail($clientId);
}
}
Now you can use it anywhere inside your laravel app like this:
If you have $request object available, (for example from a controller)
$client = OAuthClient::findByRequest($request);
Or even if the request is not available somehow, you can use it without, like this:
$client = OAuthClient::findByRequest();
Hopefully this useful for anyone, facing this issue today.
There is a tricky method.
You can modify the method of handle in the middleware CheckClientCredentials, just add this line.
$request["oauth_client_id"] = $psr->getAttribute('oauth_client_id');
Then you can get client_id in controller's function:
public function info(\Illuminate\Http\Request $request)
{
var_dump($request->oauth_client_id);
}
The OAuth token and client information are stored as a protected variable in the Laravel\Passport\HasApiTokens trait (which you add to your User model).
So simply add a getter method to your User model to expose the OAuth information:
public function get_oauth_client(){
return $this->accessToken->client;
}
This will return an Eloquent model for the oauth_clients table
In the latest implementation you can use:
use Laravel\Passport\Token;
use Lcobucci\JWT\Configuration;
$bearerToken = request()->bearerToken();
$tokenId = Configuration::forUnsecuredSigner()->parser()->parse($bearerToken)->claims()->get('jti');
$client = Token::find($tokenId)->client;
as suggested here: https://github.com/laravel/passport/issues/124#issuecomment-784731969
So, no answers ...
I was able to resolve the issue by consuming my own API, finally I came up with simpler authentication flow, the client need to send their id & secret with each request, then I consumed my own /oauth/token route with the sent credentials, inspired by Esben Petersen blog post.
Once the access token is generated, I append it to the headers of Symfony\Request instance which is under processing.
My final output like this:
<?php
namespace App\Http\Middleware;
use Request;
use Closure;
class AddAccessTokenHeader
{
/**
* Octipus\ApiConsumer
* #var ApiConsumer
*/
private $apiConsumer;
function __construct() {
$this->apiConsumer = app()->make('apiconsumer');
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$response = $this->apiConsumer->post('/oauth/token', $request->input(), [
'content-type' => 'application/json'
]);
if (!$response->isSuccessful()) {
return response($response->getContent(), 401)
->header('content-type', 'application/json');
}
$response = json_decode($response->getContent(), true);
$request->headers->add([
'Authorization' => 'Bearer ' . $response['access_token'],
'X-Requested-With' => 'XMLHttpRequest'
]);
return $next($request);
}
}
I used the above middleware in conjunction with Passport's CheckClientCredentials.
protected $middlewareGroups = [
'web' => [
...
],
'api' => [
'throttle:60,1',
'bindings',
\App\Http\Middleware\AddAccessTokenHeader::class,
\Laravel\Passport\Http\Middleware\CheckClientCredentials::class
],
];
This way, I was able to insure that $request->input('client_id') is reliable and can't be faked.
I dug into CheckClientCredentials class and extracted what I needed to get the client_id from the token. aud claim is where the client_id is stored.
<?php
Route::middleware('client')->group(function() {
Route::get('/client-id', function (Request $request) {
$jwt = trim(preg_replace('/^(?:\s+)?Bearer\s/', '', $request->header('authorization')));
$token = (new \Lcobucci\JWT\Parser())->parse($jwt);
return ['client_id' => $token->getClaim('aud')];
});
});
Few places to refactor this to in order to easily access but that will be up to your application
As I can see the above answer are old and most importantly it dose not work with laravel 8 and php 8, so I have found a way to get the client id of the access token ( current request )
the answer is basically making a middleware, and add it to all routes you want to get the client id.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Nyholm\Psr7\Factory\Psr17Factory;
use Laravel\Passport\TokenRepository;
use League\OAuth2\Server\ResourceServer;
use Illuminate\Auth\AuthenticationException;
use League\OAuth2\Server\Exception\OAuthServerException;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
class SetPassportClient
{
/**
* The Resource Server instance.
*
* #var \League\OAuth2\Server\ResourceServer
*/
protected $server;
/**
* Token Repository.
*
* #var \Laravel\Passport\TokenRepository
*/
protected $repository;
/**
* Create a new middleware instance.
*
* #param \League\OAuth2\Server\ResourceServer $server
* #param \Laravel\Passport\TokenRepository $repository
* #return void
*/
public function __construct(ResourceServer $server, TokenRepository $repository)
{
$this->server = $server;
$this->repository = $repository;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next)
{
$psr = (new PsrHttpFactory(
new Psr17Factory,
new Psr17Factory,
new Psr17Factory,
new Psr17Factory
))->createRequest($request);
try {
$psr = $this->server->validateAuthenticatedRequest($psr);
} catch (OAuthServerException $e) {
throw new AuthenticationException;
}
$token = $this->repository->find($psr->getAttribute('oauth_access_token_id'));
if (!$token)
abort(401);
$request->merge(['passportClientId' => $token->client_id]);
return $next($request);
}
}
Add the middleware to app\Http\Kernel.php
protected $routeMiddleware = [
.
.
'passport.client.set' => \App\Http\Middleware\SetPassportClient::class
];
Finaly in the routes add the middleware
Route::middleware(['client', 'passport.client.set'])->get('/test-client-id', function (Request $request){
dd($request->passportClientId); // this the client id
});
Sorry for the long answer, but I want it to be very clear to any all.
All of the code was inspired by laravel CheckCredentials.php
public function handle($request, Closure $next, $scope)
{
if (!empty($scope)) {
$psr = (new DiactorosFactory)->createRequest($request);
$psr = $this->server->validateAuthenticatedRequest($psr);
$clientId = $psr->getAttribute('oauth_client_id');
$request['oauth_client_id'] = intval($clientId);
}
return $next($request);
}
put above to your middleware file, then you can access client_id by request()->oauth_client_id
In a method you can easily get by:
$token = $request->user()->token();
$clientId = $token['client_id'];
First, i apologize for my bad english.
I'm here because i'm looking for some way to listen exception guzzle event to redirect to login page if i receive status code 401.
I found an event "PostTransactionEvent" allowing to get data struct of my response. It makes his job but i can't redirect to login page. It seems RedirectResponse method was not executed.
services.yml :
glpi.expire_listener:
class: GlpiBundle\Expire\ExpireListener
arguments: ["#router","#request_stack"]
tags:
- { name: kernel.event_listener, event: guzzle_bundle.post_transaction, method: check }
ExpireListener.php
namespace GlpiBundle\Expire;
use EightPoints\Bundle\GuzzleBundle\Events\GuzzleEventListenerInterface;
use EightPoints\Bundle\GuzzleBundle\Events\PostTransactionEvent;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
class ExpireListener implements GuzzleEventListenerInterface
{
protected $service_name;
protected $request_stack;
public function __construct($router,$request_stack)
{
$this->router = $router;
$this->request_stack = $request_stack;
}
public function check(PostTransactionEvent $event)
{
$response_transaction = $event->getTransaction();
$e = new ExpireApi();
$available = $e->deconnect($response_transaction);
if ($available) {
return new RedirectResponse($this->router->generate('logout'));
}
$event->setTransaction($response_transaction);
}
public function setServiceName($serviceName){
$this->service_name = $serviceName;
}
}
request :
$reponse_categories =$client->get('/apirest.php/itilcategory?searchText[itilcategories_id]='.self::ID_CAMPUS_ID,
[
"headers"=>
[
"App-Token"=>TOKEN,
"Session-Token"=>SESSION
],
'exceptions'=>false
]);
Thanks in advance for your help,
Use this code:
// redirect to login page
$redResponse = new RedirectResponse( $this->router->generate('login') );
$redResponse->send();
Here is the full code:
https://github.com/viher3/yuwik-frontend/blob/develop/src/EventListeners/Guzzle/ExpiredJwt.php
Regards!
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.
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.
I have an example where I am trying to create an AJAX login using Symfony2 and FOSUserBundle. I am setting my own success_handler and failure_handler under form_login in my security.yml file.
Here is the class:
class AjaxAuthenticationListener implements AuthenticationSuccessHandlerInterface, AuthenticationFailureHandlerInterface
{
/**
* This is called when an interactive authentication attempt succeeds. This
* is called by authentication listeners inheriting from
* AbstractAuthenticationListener.
*
* #see \Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener
* #param Request $request
* #param TokenInterface $token
* #return Response the response to return
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
if ($request->isXmlHttpRequest()) {
$result = array('success' => true);
$response = new Response(json_encode($result));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
}
/**
* This is called when an interactive authentication attempt fails. This is
* called by authentication listeners inheriting from
* AbstractAuthenticationListener.
*
* #param Request $request
* #param AuthenticationException $exception
* #return Response the response to return
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
if ($request->isXmlHttpRequest()) {
$result = array('success' => false, 'message' => $exception->getMessage());
$response = new Response(json_encode($result));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
}
}
This works great for handling both successful and failed AJAX login attempts. However, when enabled - I am unable to login via the standard form POST method (non-AJAX). I receive the following error:
Catchable Fatal Error: Argument 1 passed to Symfony\Component\HttpKernel\Event\GetResponseEvent::setResponse() must be an instance of Symfony\Component\HttpFoundation\Response, null given
I'd like for my onAuthenticationSuccess and onAuthenticationFailure overrides to only be executed for XmlHttpRequests (AJAX requests) and to simply hand the execution back to the original handler if not.
Is there a way to do this?
TL;DR I want AJAX requested login attempts to return a JSON response for success and failure but I want it to not affect standard login via form POST.
David's answer is good, but it's lacking a little detail for newbs - so this is to fill in the blanks.
In addition to creating the AuthenticationHandler you'll need to set it up as a service using the service configuration in the bundle where you created the handler. The default bundle generation creates an xml file, but I prefer yml. Here's an example services.yml file:
#src/Vendor/BundleName/Resources/config/services.yml
parameters:
vendor_security.authentication_handler: Vendor\BundleName\Handler\AuthenticationHandler
services:
authentication_handler:
class: %vendor_security.authentication_handler%
arguments: [#router]
tags:
- { name: 'monolog.logger', channel: 'security' }
You'd need to modify the DependencyInjection bundle extension to use yml instead of xml like so:
#src/Vendor/BundleName/DependencyInjection/BundleExtension.php
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
Then in your app's security configuration you set up the references to the authentication_handler service you just defined:
# app/config/security.yml
security:
firewalls:
secured_area:
pattern: ^/
anonymous: ~
form_login:
login_path: /login
check_path: /login_check
success_handler: authentication_handler
failure_handler: authentication_handler
namespace YourVendor\UserBundle\Handler;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
class AuthenticationHandler
implements AuthenticationSuccessHandlerInterface,
AuthenticationFailureHandlerInterface
{
private $router;
public function __construct(Router $router)
{
$this->router = $router;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
if ($request->isXmlHttpRequest()) {
// Handle XHR here
} else {
// If the user tried to access a protected resource and was forces to login
// redirect him back to that resource
if ($targetPath = $request->getSession()->get('_security.target_path')) {
$url = $targetPath;
} else {
// Otherwise, redirect him to wherever you want
$url = $this->router->generate('user_view', array(
'nickname' => $token->getUser()->getNickname()
));
}
return new RedirectResponse($url);
}
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
if ($request->isXmlHttpRequest()) {
// Handle XHR here
} else {
// Create a flash message with the authentication error message
$request->getSession()->setFlash('error', $exception->getMessage());
$url = $this->router->generate('user_login');
return new RedirectResponse($url);
}
}
}
If you want the FOS UserBundle form error support, you must use:
$request->getSession()->set(SecurityContext::AUTHENTICATION_ERROR, $exception);
instead of:
$request->getSession()->setFlash('error', $exception->getMessage());
In the first answer.
(of course remember about the header: use Symfony\Component\Security\Core\SecurityContext;)
I handled this entirely with javascript:
if($('a.login').length > 0) { // if login button shows up (only if logged out)
var formDialog = new MyAppLib.AjaxFormDialog({ // create a new ajax dialog, which loads the loginpage
title: 'Login',
url: $('a.login').attr('href'),
formId: '#login-form',
successCallback: function(nullvalue, dialog) { // when the ajax request is finished, look for a login error. if no error shows up -> reload the current page
if(dialog.find('.error').length == 0) {
$('.ui-dialog-content').slideUp();
window.location.reload();
}
}
});
$('a.login').click(function(){
formDialog.show();
return false;
});
}
Here is the AjaxFormDialog class. Unfortunately I have not ported it to a jQuery plugin by now... https://gist.github.com/1601803
You must return a Response object in both case (Ajax or not). Add an `else' and you're good to go.
The default implementation is:
$response = $this->httpUtils->createRedirectResponse($request, $this->determineTargetUrl($request));
in AbstractAuthenticationListener::onSuccess
I made a little bundle for new users to provide an AJAX login form : https://github.com/Divi/AjaxLoginBundle
You just have to replace to form_login authentication by ajax_form_login in the security.yml.
Feel free to suggest new feature in the Github issue tracker !
This may not be what the OP asked, but I came across this question, and thought others might have the same problem that I did.
For those who are implementing an AJAX login using the method that is described in the accepted answer and who are ALSO using AngularJS to perform the AJAX request, this won't work by default. Angular's $http does not set the headers that Symfony is using when calling the $request->isXmlHttpRequest() method. In order to use this method, you need to set the appropriate header in the Angular request. This is what I did to get around the problem:
$http({
method : 'POST',
url : {{ path('login_check') }},
data : data,
headers: {'X-Requested-With': 'XMLHttpRequest'}
})
Before you use this method, be aware that this header does not work well with CORS. See this question