Does onAuthenticationSuccess get fired on HTTP auth in Silex? - php

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.

Related

Verify third party JWT with Laravel

I'm using external identity provider to authenticate users, created a SPA client (got client_id & client_secret), configured API with audience & scope, so once users authenticated they will get access_token (will be authorized) to access multiple custom micro-services (APIs).
When my custom API receives a request with a bearer Access Token (JWT) the first thing to do is to validate the token. In order to validate JWT I need to follow these steps:
Check that the JWT is well formed (Parse the JWT)
Check the signature. My external identity provider only supports RS256 via the JWKS (JSON Web Key Set) URL (https://{domain}/.well-known/jwks.json), so I can get my public key following this URL.
Validate the standard claims
Check the Application permissions (scopes)
There are a lot of packages/libraries (i.e. https://github.com/tymondesigns/jwt-auth) to create JWT tokens but I can't find any to validate it using those steps above. Could anyone please help to find suitable Laravel/PHP package/library or move me to the right direction in order to achieve my goals (especially point #2).
I did something similar in the past, I don't know if this may help but I'll give it a try. To use a public key, you should download it, put it somewhere on the disk (storage/jwt/public.pem for example) and then link it in the jwt config config/jwt.php with the ALGO (you can see supported algorithms here
'keys' => [
// ...
'public' => 'file://'.storage_path('jwt/public.pem'),
// ...
],
'algo' => 'RS256',
Then, you should have a custom Guard, let's call it JWTGuard:
<?php
namespace App\Guard;use App\Models\User;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Http\Request;
use Tymon\JWTAuth\JWT;class JWTGuard implements Guard
{
use GuardHelpers;
/**
* #var JWT $jwt
*/
protected JWT $jwt;
/**
* #var Request $request
*/
protected Request $request;
/**
* JWTGuard constructor.
* #param JWT $jwt
* #param Request $request
*/
public function __construct(JWT $jwt, Request $request) {
$this->jwt = $jwt;
$this->request = $request;
}
public function user() {
if (! is_null($this->user)) {
return $this->user;
}
if ($this->jwt->setRequest($this->request)->getToken() && $this->jwt->check()) {
$id = $this->jwt->payload()->get('sub');
$this->user = new User();
$this->user->id = $id;
// Set data from custom claims
return $this->user;
}
return null;
}
public function validate(array $credentials = []) { }
}
This should do all your logic of validation, I used a custom user implementation, the class signature was like:
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Database\Eloquent\Model;
class User extends Model implements AuthenticatableContract {
// custom implementation
}
Finally, you should register the guard in the AuthServiceProvider and in the auth config
public function boot()
{
$this->registerPolicies();
$this->app['auth']->extend(
'jwt-auth',
function ($app, $name, array $config) {
$guard = new JWTGuard(
$app['tymon.jwt'],
$app['request']
);
$app->refresh('request', $guard, 'setRequest');
return $guard;
}
);
}
then allow it in the config
<?php
return [
'defaults' => [
'guard' => 'jwt',
'passwords' => 'users',
],
'guards' => [
// ...
'jwt' => [
'driver' => 'jwt-auth',
'provider' => 'users'
],
],
// ...
];
You can then use it as a middleware like this:
Route::middleware('auth:jwt')->get('/user', function() {
return Auth::user();
}
Does this sound good to you?
In the end I've used the Auth0 SDK for Laravel - https://auth0.com/docs/quickstart/backend/laravel/01-authorization. Nice and clean solution.

How to use Lumen/Laravel custom service

Im new to Lumen, I have managed to create and register the following ArangoDB service provider along with the service below and got it to work, but Im confused how I actually use them in another service or helper.
registered the provider in bootstrap/app.php
$app->register(App\Providers\ArangoServiceProvider::class);
ArangoServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class ArangoServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton('ArangoService', function ($app) {
return new ArangoService($app);
});
}
public function boot()
{
//
}
}
ArangoService.php
<?
namespace App\Services;
use ArangoDBClient\Collection as ArangoCollection;
use ArangoDBClient\CollectionHandler as ArangoCollectionHandler;
use ArangoDBClient\Connection as ArangoConnection;
use ArangoDBClient\ConnectionOptions as ArangoConnectionOptions;
use ArangoDBClient\DocumentHandler as ArangoDocumentHandler;
use ArangoDBClient\Document as ArangoDocument;
use ArangoDBClient\Exception as ArangoException;
use ArangoDBClient\Export as ArangoExport;
use ArangoDBClient\ConnectException as ArangoConnectException;
use ArangoDBClient\ClientException as ArangoClientException;
use ArangoDBClient\ServerException as ArangoServerException;
use ArangoDBClient\Statement as ArangoStatement;
use ArangoDBClient\UpdatePolicy as ArangoUpdatePolicy;
class ArangoService{
public function __construct() {
$connectionOptions = [
// database name
ArangoConnectionOptions::OPTION_DATABASE => 'dbname',
// server endpoint to connect to
ArangoConnectionOptions::OPTION_ENDPOINT => 'tcp://123456789',
// authorization type to use (currently supported: 'Basic')
ArangoConnectionOptions::OPTION_AUTH_TYPE => 'Basic',
// user for basic authorization
ArangoConnectionOptions::OPTION_AUTH_USER => 'root',
// password for basic authorization
ArangoConnectionOptions::OPTION_AUTH_PASSWD => 'passwd',
// connection persistence on server. can use either 'Close' (one-time connections) or 'Keep-Alive' (re-used connections)
ArangoConnectionOptions::OPTION_CONNECTION => 'Keep-Alive',
// connect timeout in seconds
ArangoConnectionOptions::OPTION_TIMEOUT => 3,
// whether or not to reconnect when a keep-alive connection has timed out on server
ArangoConnectionOptions::OPTION_RECONNECT => true,
// optionally create new collections when inserting documents
ArangoConnectionOptions::OPTION_CREATE => true,
// optionally create new collections when inserting documents
ArangoConnectionOptions::OPTION_UPDATE_POLICY => ArangoUpdatePolicy::LAST,
];
// turn on exception logging (logs to whatever PHP is configured)
ArangoException::enableLogging();
$connection = new ArangoConnection($connectionOptions);
$collectionHandler = new ArangoCollectionHandler($connection);
$handler = new ArangoDocumentHandler($connection);
}
public function main(){
print "hello world!<br/>";
}
}
This all above works, but how do I in a controller, call a service, lets call it "UsersService" that has functions like GetUser() , how does this "UsersService" use the $connection, $collectionHandler and $handler , that I have created in my ArangoDB service.
route pointing to a controller (got that one)
controller calling UsersService
UsersService using the arango service and passing something back to the controller
grateful for any help!

How can I create a token for a Password Grant Client using Laravel Passport?

I am building an API and I am using Laravel Passport for authentication.
The API is being used for our mobile app so we're using the Password Grant Client.
Everything works great, and a user can login to get an access token. We have created a register endpoint which allows a user to sign up. We need the API to return an access token at this point too.
Looking through the docs there is no way to create an access token programmatically.
How can I create an access token for a Password Grant Client in my controller? I obviously don't want to do a HTTP request to my own API to get it.
I know I can use a Personal Access Grant Client and call createToken on the user model, but that means the access token is associated with a different Client. This doesn't seem right to me.
Try something like this
class UserController extends Controller
{
protected function login(Request $request)
{
$request->request->add([
'grant_type' => 'password',
'client_id' => '3',
'client_secret' => '6BHCRpB4tpXnQvC1DmpT7CXCSz7ukdw7IeZofiKn',
'scope' => '*'
]);
// forward the request to the oauth token request endpoint
$tokenRequest = Request::create('/oauth/token','post');
return Route::dispatch($tokenRequest);
}
}
I've been toying with Passport for a couple of weeks now and from what I've seen in the documentation it doesn't expose many of the methods it uses for creating tokens. While you may not easily be able to "create an access token for a Password Grant Client in my controller" - what you can do is use Route::dispatch to forward the request for a token to your Passport Password Grant route.
To do this in the controller you are using to issue tokens, use the AuthenticatesUsers trait so you have access to the Password Grant route, create a request, and dispatch that request to the Password Grant route:
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class IssueTokensController extends Controller
{
use AuthenticatesUsers;
protected function issueApiToken(Request $request)
{
// forward the request to the oauth token request endpoint
$tokenRequest = Request::create(
'/oauth/token',
'post'
);
return Route::dispatch($tokenRequest);
}
}
This method of course requires you to have set up Passport and a Password Grant Client.
This answer is based off of another answer to a similar question by Raymond Lagonda - see https://stackoverflow.com/a/40433000/4991377
Patrick has got the right idea, and this is what I ended up doing:
(I don't think Sanju's answer is right because you need to make a http request)
<?php
namespace MyApp\Http\Controllers\API;
use Illuminate\Http\Request;
use Laravel\Passport\Http\Controllers\ConvertsPsrResponses;
use League\OAuth2\Server\AuthorizationServer;
use MyApp\Http\Controllers\APIController;
use Illuminate\Auth\AuthenticationException;
use Zend\Diactoros\ServerRequest;
use Zend\Diactoros\Response as Psr7Response;
class LoginController extends APIController
{
use ConvertsPsrResponses;
/**
*
* #param Request $request
* #param AuthorizationServer $authServer
* #return \Illuminate\Http\JsonResponse
* #throws AuthenticationException
* #throws \League\OAuth2\Server\Exception\OAuthServerException
*/
public function login(Request $request, AuthorizationServer $authServer)
{
$token = $this->getPasswordToken($request, $authServer);
$data = [
"token_details" => $token,
];
return $this->successResponse(
'Successful Login',
200,
$data
);
}
/**
* #param $request
* #param AuthorizationServer $authServer
* #return mixed
* #throws \League\OAuth2\Server\Exception\OAuthServerException
*/
private function getPasswordToken($request, AuthorizationServer $authServer)
{
$parsedBody = [
'grant_type' => 'password',
'client_id' => config('app.client_id'),
'client_secret' => config('app.client_secret'),
'username' => $request->username,
'password' => $request->password,
'scope' => '',
];
$serverRequest = new ServerRequest(
$request->server(),
[],
null,
$request->method(),
'php://input',
$request->header(),
[],
[],
$parsedBody
);
$response = $this->convertResponse(
$authServer->respondToAccessTokenRequest($serverRequest, new Psr7Response)
);
return json_decode($response->getContent());
}
}
I know I can use a Personal Access Grant Client and call createToken on the user model, but that means the access token is associated with a different Client
not sure what you mean by that, could you explain more?
Now this is not ideal but you might be able to inject \League\OAuth2\Server\Grant\PasswordGrant and use
respondToAccessTokenRequest(ServerRequestInterface $request
ResponseTypeInterface $responseType,
\DateInterval $accessTokenTTL)
you would have to build all those objects, but this is the only public method for password that returns any token information.

Symfony Custom UserProvider Auth + Parse SDK

Heyo!
I know it's a common problem people having problems with custom providers and web service authentication. I'm spending hours trying to figure out how to do that but I'm almost freaking out.
So, the thing is: I'm using the Symfony Firewalls with a custom UserProvider and a AbstractGuardAuthenticator as well. The problem is in the loadUserByUsername($username) function inside the UserProvider implementation.
For security reasons I can't retrieve the user password from Parse (my web service), and the loadUserByUsername($username) function asks for that. Even in the documentation about how to create a custom user provider using web services they are retrieving the user password from the database.
So what's the solution in that case? What can I do when I don't have access to the user password?
My current code is something like that:
$app['app.authenticator'] = function () {
return new Authenticator($app);
};
$app['security.firewalls'] = array(
'login' => array(
'pattern' => '^/login/$',
),
'secured' => array(
'pattern' => '^.*$',
'form' => array('login_path' => '/login/', 'check_path' => '/login/auth/'),
'logout' => array('logout_path' => '/logout/', 'invalidate_session' => true),
'guard' => array(
'authenticators' => array(
'app.authenticator'
),
),
'users' => function () use ($app) {
return new UserProvider($app);
},
)
);
The Authenticator.php is quite big code because extends the AbstractGuardAuthenticator class. But I'm basically using this one from Symfony docs. The only thing Is that I'm sending to the UserProvider class the username AND the password as well, because that way I can check if the user and password are right. Like this:
public function getUser($credentials, UserProviderInterface $userProvider) {
return $userProvider->loadUserByUsername($credentials);
}
And my UserProvider class is the default one, I'm just checking inside the loadUserByUsername function if the credentials comming from my Authenticator are right. Something like this:
public function loadUserByUsername($credentials) {
$encoder = new BCryptPasswordEncoder(13);
try {
$user = ParseUser::logIn($credentials['username'], $credentials['password']);
} catch (ParseException $error) {
throw new UsernameNotFoundException(sprintf('Invalid Credentials.'));
}
return new User($credentials['username'], $encoder->encodePassword($credentials['password'], ''), explode(',', 'ROLE_USER'), true, true, true, true);
}
The problem is: after the login (everything with the login is working fine), Silex calls the loadUserByUsername function in every page which needs to be secured, but just sending the username parameter.
So basically, I don't know what to do guys. I'm really trying to figure out how to get this thing working.
Thanks for your help!
I have a similar implementation and this issue is well known. In my user provider I have the methods loadUserByUsername($username) and refreshUser(UserInterface $user). Since I have the same issue like you, I don't check the user in loadUserByUsername but simple return a new Object with only the username in it to not disturb the flow. loadUserByUsername doesn't make sense for external APIs, so I simply jump over it. The method refreshUser is either beeing called on every request, this is usefull.
In your AbstractGuardAuthenticator you have the method createAuthenticatedToken, which returns an token. There you should have the full authentificated user:
abstract class AbstractGuardAuthenticator implements GuardAuthenticatorInterface
{
/**
* Shortcut to create a PostAuthenticationGuardToken for you, if you don't really
* care about which authenticated token you're using.
*
* #param UserInterface $user
* #param string $providerKey
*
* #return PostAuthenticationGuardToken
*/
public function createAuthenticatedToken(UserInterface $user, $providerKey)
{
//do login stuff
//save password in user
return new PostAuthenticationGuardToken(
$user,
$providerKey,
$user->getRoles()
);
}
}
Then, I would't use loadUserByUsername but refreshUser instead. Both are called on every request:
/**
* Don't use
* #codeCoverageIgnore
* #param string $username
* #return User
*/
public function loadUserByUsername($username)
{
return new User($username, null, '', ['ROLE_USER'], '');
}
/**
* Refresh user on every subrequest after login
* #param UserInterface $user
* #return User
*/
public function refreshUser(UserInterface $user)
{
$password = $user->getPassword();
$username = $user->getUsername();
//login check
}

Use of ajax in Silex Framework

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']
);
});

Categories