I'm pretty new to the Silex Framework and I was wondering how to make a simple login (using SecurityServiceProvider) ajax request. Everything works well in my code (see below) but how can I change the html page returned for a boolean giving true or false wether the login worked or not.
app.php
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\ExceptionHandler;
// Register global error and exception handlers
ErrorHandler::register();
ExceptionHandler::register();
// Register service providers
$app->register(new Silex\Provider\DoctrineServiceProvider());
$app->register(new Silex\Provider\TwigServiceProvider(), array(
'twig.path' => __DIR__ . '/../views',
));
$app->register(new Silex\Provider\UrlGeneratorServiceProvider());
$app->register(new Silex\Provider\SessionServiceProvider());
$app->register(new Silex\Provider\SecurityServiceProvider(), array(
'security.firewalls' => array(
'secured' => array(
'pattern' => '^/',
'anonymous' => true,
'logout' => array('logout_path' => '/admin/logout', 'invalidate_session' => true),
'form' => array('login_path' => 'login', 'check_path' => '/login_check'),
'users' => $app->share(function () use ($app) {
return new ski\DAO\MemberDAO($app['db']);
}),
),
),
));
// register services
$app['dao.member'] = $app->share(function ($app) {
return new ski\DAO\MemberDAO($app['db']);
});
routes.php
use Symfony\Component\HttpFoundation\Request;
// Home page
$app->get('/', function () use ($app) {
return $app['twig']->render('index.html.twig');
})->bind('home');
// TODO : never called
$app->post('/ajax/login/', function (Request $request) use ($app) {
// HERE : how to return if the login was performed well ?
return $app['security.last_error']($request);
})->bind('ajax_login');
$app->get('/login/', function (Request $request) use ($app) {
return $app['twig']->render('login.html.twig', array(
'error' => $app['security.last_error']($request),
'last_username' => $app['session']->get('_security.last_username'),
));
})->bind('login');
$app->get('/includes/header/', function () use ($app) {
return $app['twig']->render('header.html.twig');
})->bind('header');
and login.js
// Connexion
$(document).on('click', '#connexion_submit_button', function () {
// Connexion Ajax
var username = $('#connexion_pseudo').val();
var password = $('#connexion_password').val();
$.ajax({
type: 'POST',
url: '/ski/web/login_check',
data: '_username=' + username + '&_password=' + password,
beforeSend: function () {
$('#connexion_submit_button').html('Patientez...');
},
success: function (data) {
// TODO : generate custom animations if user is logged or not
console.log(data);
$('#connexion_submit_button').html('Connexion');
}
});
return false;
});
Any suggestions will be greatly appreciated !
And by the way, is there any good manners to do ajax in such frameworks ?
Silex uses Symfony's Security component under the hood and SecurityServiceProvider sets $app['dispatcher'] as event dispatcher to AuthenticationProviderManager. I guess this means that it'll fire events as listed in Security Component documentation.
The one interesting ones for you should be security.interactive_login and security.authentication.failure. Both fire an event where you have full access to the Request object where you can modify matching controller or do whatever you want by subscribing to events using EventSubscriberInterface.
Authentication success and failure handlers create and manage response. So you need custom authentication success and failure handlers if you want to make custom response.
Add handlers
MyAuthenticationFailureHandler
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler as BaseDefaultAuthenticationFailureHandler;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
class MyAuthenticationFailureHandler extends BaseDefaultAuthenticationFailureHandler
{
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
if ($request->isXmlHttpRequest()) return new JsonResponse(['login' => false]);
return parent::onAuthenticationFailure($request, $exception);
}
}
MyAuthenticationSuccessHandler
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler as BaseDefaultAuthenticationSuccessHandler;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class MyAuthenticationSuccessHandler extends BaseDefaultAuthenticationSuccessHandler
{
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
if ($request->isXmlHttpRequest()) return new JsonResponse(['login' => true]);
return parent::onAuthenticationSuccess($request, $token);
}
}
and register them in security service provider
$app['security.authentication.success_handler.secured'] = $app->share(function () use ($app) {
$handler = new MyAuthenticationSuccessHandler(
$app['security.http_utils'],
$app['security.firewalls']['secured']['form'] //$options,
);
$handler->setProviderKey('secured');
return $handler;
});
$app['security.authentication.failure_handler.secured'] = $app->share(function () use ($app) {
return new MyAuthenticationFailureHandler(
$app,
$app['security.http_utils'],
$app['security.firewalls']['secured']['form'],
$app['logger']
);
});
Related
I am building a ReactJS App with a Laravel 8.9.0 backend api. I am using the Laravel Auth functionality that creates a token and passes it to my front end app. I am able to log-in and create a token properly with a hash password etc. What I am not able to do is "Check Login" with the is_login method shown below in the controller. The Auth::check() is always failing, what am I doing wrong? Below are my controllers and routes api.php file. Please help!
Login Controller:
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Models\Users;
class LoginController extends Controller
{
public function login(Request $request) {
$login = $request->validate([
'email' => 'required:string',
'password' => 'required:string'
]);
if(!Auth::attempt($login)) {
return response([
'message' => 'Invalid Credentials'
]);
}
$accessToken = Auth::user()
->createToken('authToken')
->accessToken;
return response([
'user' => Auth::user(),
'access_token' => $accessToken
]);
}
public function is_login()
{
$is_login = false;
if (Auth::check()) {
$is_login = true;
}
return response()->json(['is_login' => $is_login]);
}
}
Routes api.php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::post('/login', 'App\Http\Controllers\LoginController#login');
Route::get('/login-check', 'App\Http\Controllers\LoginController#is_login');
To fix this problem you must pass the token into the header with every request:
In my ReactJS I did this on componentDidMount()
componentDidMount() {
try {
const config = {
headers: {
Authorization: `Bearer ${mytoken}`
}
};
axios.get('/api/login-check',
{ key: "value" },
{ headers: config }
).then(
console.log('succ')
).catch(
console.log('err')
);
} catch (err) {
console.log('Error in submission', err)
}
}
and in my api I added the middleware to the check:
Route::middleware('auth:api')->get('/login-check', 'App\Http\Controllers\LoginController#is_login');
I'm using [Dingo/api][1] laravel package to create an API. I worked with it before and I have not this problem.
this is my routes in the api.php file:
$api = app('Dingo\Api\Routing\Router');
$api->version('v1', ['prefix' => 'v1', 'namespace' => 'App\Http\Controllers'], function ($api) {
$api->group(['prefix' => 'auth'], function ($api) {
$api->post('signIn', 'Auth\LoginController#signIn');
});
$api->group(['middleware' => 'api.auth'], function ($api) {
$api->get('signOut', ['uses' => 'Auth\LoginController#signOut']);
$api->get('test', function () {
return response()->json(['foo' => 'bar']);
});
});
});
/signIn route works fine and respond a token that can used in other protected endpoint like /test and /test directory works fine.
But I want to Sign out a user and call /signOut route it responses this always:
{
"success": false,
"message": "Unauthenticated.",
"status_code": 500
}
This is signOut method in LoginController :
public function signOut()
{
//return 'Hiiiii Alll';
try {
$token = \Tymon\JWTAuth\Facades\JWTAuth::getToken();
\Tymon\JWTAuth\Facades\JWTAuth::invalidate($token);
return [
'success' => true,
'message' => 'You Signed Out Successfully'
];
} catch (\Exception $e) {
return $this->response->error('Something went wrong', 404);
}
}
Even when I return a Simple string from that method it does not run, seems problem is in ['middleware' => 'api.auth'] that I used But if it's right why problem does not occurred for test directory that in in the same route group?
Update(solution) :
I found that should change logout to SignOut in __construct() method in LoginController method :
public function __construct()
{
$this->middleware('guest')->except('logout');
}
I have setup a security firewall and a SuccessHandler for my application. The relevant snippets are:
$app -> register(new SecurityServiceProvider(), array(
'security.firewalls' => array(
'auth' => array(
'pattern' => "^/auth",
'http' => true,
'users' => $app -> share(function() use ($app) {
return new \Model\Manager\Account($app);
})
)
'security.access_rules' => array(
array('^/auth.*$', 'ROLE_USER')
),
));
$app['security.authentication.success_handler.auth'] = $app -> share(function() use ($app) {
return new Handlers\Authentication\Auth\SuccessHandler($app['security.http_utils'], array(), $app);
});
The 'auth' has got the 'http' authentication set to true and indeed when I go to the url 'http://myserver/auth' I get a Basic Authentication challenge.
However when I log in correctly I get the page that I wanted, but I have not gone via the SuccessHandler that I have setup. Is this supported when using HTTP auth or only when using form based authentication?
If it is not supported is there a way I can achieve the same thing? I have been looking at EventSubscriber but I did not know how to wire this up in Silex to listen for the appropriate event.
Thanks, Russell
UPDATE:
My SuccessHandler has the following.
```
<?php
namespace Handlers\Authentication\Auth;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
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 Symfony\Component\HttpKernel\HttpKernelInterface;
use Silex\Application;
class SuccessHandler extends DefaultAuthenticationSuccessHandler {
protected $app = null;
public function __construct(HttpUtils $httpUtils, array $options, Application $app) {
parent::__construct($httpUtils, $options);
$this -> app = $app;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token) {
// get the user from the token
$user = $token -> getUser();
dump($user);
exit;
// redirect user to the page they requested
return $this -> httpUtils -> createRedirectResponse($request, $this -> determineTargetUrl($request));
}
}
```
As you can see all I am trying to do is show the user details and then exit out. I know this is not what I would normally do but I am trying to make sure the onAuthenticationSuccess gets called, which it is not, although authentication is working.
I have a question regarding Slim configuration for the Cloud Foundry.
I'm using the latest build pack version and 'nginx' for my app.
$_ENV['SLIM_MODE'] = 'development';
//Slim App Instance
$app = new \Slim\Slim();
$app->configureMode('development', function () use ($app) {
$app->config(array(
'log.enable' => true,
'log.level' => \Slim\Log::DEBUG,
'debug' => true
));
});
$app->group('/v1', function () use ($app) {
$app->get('/hello/:name', function ($name) use ($app) {
$app->getLog()->info('###Some text###');
$response = $app->response();
$response['Content-Type'] = 'application/json';
$response->status(201);
$response->body(json_encode(['hello' => $name]));
});
});
Unable to see the following text in the Cloud Foundry logs.
Did I miss something?
I have a custom Silex\RouteCollection which I want to register...
class RouteCollectionProvider extends RouteCollection
{
public function __construct() {
$this->add(
'Index',
new Route('/', array(
'method' => 'get',
'controller' => 'index',
'action' => 'index'
)
));
}
}
...during the bootstrapping:
$app = new Silex\Application();
/** here **/
$app->run();
I could use:
$app = new Silex\Application();
$routes = new RouteCollectionProvider();
foreach ($routes->getIterator() as $route) {
$defaults = $route->getDefaults();
$pattern = $route->getPath();
$callback = 'Controller\\'
. ucfirst($defaults['controller'])
. 'Controller::'
. $defaults['action']
. 'Action';
$app->get($pattern, $callback);
}
$app->run();
I don't like having the initialization of those routes right in there.
Do you know any spot in Silex, where this does fit better?
I cannot use $app->register() because it's getting called too late and the routes won't get active in there.
Maybe there is an event I can use with
$app->on('beforeCompileRoutesOrSomething', function() use ($app) {
// initialize routes
}
Or a hook in the Dispatcher?
My aim is to not have a big collection of $app->get() or $app->post() in there. I also know I can ->mount() a controller but then still I have all my get definitions in my bootstrap and not in a Provider.
This post solves the problem: Scaling Silex pt. 2.
$app = new Application;
$app->extend('routes', function (RouteCollection $routes, Application $app) {
$routes->addCollection(new MyCustomRouteCollection);
return $routes;
});
$app->run();