I'm attempting to rebind what $request->user() returns, and having poked through the built in authentication code, I found a service using app->rebinding to request->setUserResolver is how it's done? I tried it myself, with no luck. I created a service (well, coopted AuthServiceProvider, and changed the register to:
public function register()
{
$this->app->rebinding('request', function ($app, $request) {
$request->setUserResolver(function () use ($app) {
$token = $this->request->bearerToken();
dd($token);
// error_log($token);
return array('user' => 1);
});
});
}
Ignoring the dd, which is there to test, how can I find where I'm going wrong? I even found a SO answer that seems to indicate this is the way to go but nothing gets dumped, nothing gets logged (when error log isn't commented out) and dumping $request->user() in my controller just returns null.
I know I can use the built in auth/guard setup, but I figured since I'm not using most of what the auth/guard setup has, why not try to learn and set it up myself? Of course, so far I've gotten nowhere. I'm going to fall back to using the built-in stuff, but I'd like to learn and improve.
As I realized it may make a difference, I'm running Lumen 5.4.
In Lumen, your App\Providers\AuthServiceProvider class comes by default with
public function boot()
{
// Here you may define how you wish users to be authenticated for your Lumen
// application. The callback which receives the incoming request instance
// should return either a User instance or null. You're free to obtain
// the User instance via an API token or any other method necessary.
$this->app['auth']->viaRequest('api', function ($request) {
if ($request->input('api_token')) {
return User::where('api_token', $request->input('api_token'))->first();
}
});
}
This is the place to define the user resolution logic. The rebinding you were registering in the register method was being supeseded by this one.
Just uncomment the $app->register(App\Providers\AuthServiceProvider::class); line in bootstrap/app.php to register your provider; don't modify the code in the vendor folder (if I understood correctly you were doing that).
Update
I now see what you mean, although I'm not sure it is really too much "load" for the auth/guard method.
However, in the interest of creating a minimal implementation, I think the solution would be overriding the prepareRequest method of the Application class.
In bootstrap/app.php replace
$app = new Laravel\Lumen\Application(
realpath(__DIR__.'/../')
);
with
$app = new class (realpath(__DIR__.'/../')) extends Laravel\Lumen\Application {
protected function prepareRequest(\Symfony\Component\HttpFoundation\Request $request)
{
if (! $request instanceof Illuminate\Http\Request) {
$request = Illuminate\Http\Request::createFromBase($request);
}
$request->setUserResolver(function () use ($request) {
return $request->bearerToken();
})->setRouteResolver(function () {
return $this->currentRoute;
});
return $request;
}
};
This way you can have the simple resolution logic for getting the bearer token (don't include the AuthServiceProvider then).
(This requires PHP 7 anonymous classes; alternatively just extend to a regular class).
You do not need to change the register() function.
Just uncomment the following lines in bootstrap/app.php file:
$app->withEloquent();
$app->register(App\Providers\AppServiceProvider::class);
$app->register(App\Providers\AuthServiceProvider::class);
$app->routeMiddleware([
'auth' => App\Http\Middleware\Authenticate::class,
]);
And in app/Providers/AuthServiceProvider.php->boot(), it has default method to retrieve the authenticated user.
$this->app['auth']->viaRequest('api', function ($request) {
if ($request->input('api_token')) {
return User::where('api_token', $request->input('api_token'))->first();
}
});
You may use an API token in the request headers or query string, a bearer token on the request, or using any other approach your application requires.
After that, you may retrieve the authenticated user like this:
use Illuminate\Http\Request;
$app->get('/post/{id}', ['middleware' => 'auth', function (Request $request, $id) {
$user = Auth::user();
$user = $request->user();
//
}]);
The rebinding method will add an additional reboundCallbacks which this callback will be triggered right after the abstract is rebound. As long as your abstract is not rebound, the reboundCallbacks are not called. So, you can simply rebound your abstract, like so:
$this->app->rebinding('request', function ($app, $request) {
$request->setUserResolver(function () use ($app) {
$token = $this->request->bearerToken();
dd($token);
// do the rest
});
});
// REBOUND HERE
$this->app->instance('request', $this->app->make('request'));
// TEST
// $this->app->make('request')->user(); // output is $token
Try uncomment the rebound line above, your dd will not called at all.
Extra
You can use refresh method (to register reboundCallbacks) combined with extend method (to rebound) for cleaner code:
public function register()
{
parent::register();
$this->app->refresh('request', $this, 'overrideUserResolver');
// REBOUND HERE, JUST ANOTHER WAY TO REBOUND
$this->app->extend('request', function ($request) { return $request; });
// TEST
$this->app->make('request')->user();
}
public function overrideUserResolver($request)
{
$request->setUserResolver(function ($guard = null) use ($request) {
$token = $request->bearerToken();
dd($token);
// do the rest
});
}
Related
I’m new to slim and trying to figure out the best way to create a global user variable. My first thought is to add it to the Container, but I can’t figure out how to inject $app into a route mapped to a class method. I saw somewhere that I can add Container $container to the constructor and the DI should do it automatically? Doesn’t seem to be working.
__construct(Container $container)
Plus, I need to access the variable in a middleware class I wrote to parse my Bearer header, and I can’t find any details on how to do that. I know I can pass $app into each ->add, but that’s a lot of repeated code, and I’m hoping I can avoid that. I'm already injecting an extra variable to parse for roles:
->add(new \App\Middleware\AuthMiddleware('admin'));
public function __construct($role = null)
I can always create a PHP global, but I'd like to figure out what the right Slim way of doing this is.
You can do this with a CurrentUser-class which stores the roles:
class CurrentUser {
private $roles;
function getRoles() {return $this->roles; }
function setRoles($newRoles) {$this->roles = $newRoles; }
}
Which then you can add to the container:
$container[CurrentUser::class] = function($c) {
$user = new CurrentUser();
$user->setRoles(['myrole']);
return $user;
};
And use the CurrentUser-object in a helper method which dynamically creates us the wanted middleware. This method could also be on the CurrentUser-class
function hasPermission($role) {
return function($req, $resp, $next) use ($role) {
if(in_array($role, $this[CurrentUser::class]->getRoles())) {
return $next($res, $resp); // proceed to route
} else {
// handle unauthorized
return $resp->withStatus(401)->write('unauthorized');
}
};
}
Now use the helper method to create the middleware which authorize the user.
$app->get('/books', function ($request, $response, $args) {
return $response->write('Some books yay');
})->add(hasPermission('myrole'));
Is there a built-in way to skip authorization completely while testing the controllers?
Sample controller:
public function changePassword(Request $request, LdapInterface $ldap)
{
$this->authorize('change-password');
$this->validate($request, [
'pass' => 'min:8|confirmed|weakpass|required',
]);
$success = $ldap->updatePassword($request->get('pass'));
$message = $success ?
'Your e-mail password has been successfully changed' :
'An error occured while trying to change your alumni e-mail password.';
return response()->json(['message' => $message]);
}
I want to skip change-password rule, which is defined inside the AuthServiceProvider like:
public function boot(GateContract $gate)
{
$gate->define('change-password', function ($user) {
// Some complex logic here
});
}
I don't want to add smt. like if (env('APP_ENV') == 'testing') return; inside the code.
Actually there is a built-in way. You can add a "before" callback to be called before actual authorization check and bypass the check simply by returning true:
\Gate::before(function () {
return true;
});
You should add this snippet to either the setUp() method of your test or every test method that you want to bybass the authorization.
I'm not aware of one, but you could move that check to a dedicated middleware and use the withoutMiddleware trait to disable it in tests.
Or you could mock the application's gate instance using Mockery. Mockery is well documented so I'd suggest reading the docs for more details, but setting it up would look something like this:
$mock = Mockery::mock('Illuminate\Contracts\Auth\Access\Gate');
$mock->shouldReceive('authorize')->with('change-password')->once()->andReturn(true);
$this->app->instance('Illuminate\Contracts\Auth\Access\Gate', $mock);
This sets up a mock of the gate contract, sets up what it expects to receive and how it should respond, and then injects it into the application.
From laravel documentation :
When testing your application, you may find it convenient to disable
middleware for some of your tests. This will allow you to test your
routes and controller in isolation from any middleware concerns.
Laravel includes a simple WithoutMiddleware trait that you can use to
automatically disable all middleware for the test class:
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
use WithoutMiddleware;
//
}
Or you can use withoutMiddleware() method in you test method like this :
public function testBasicExample()
{
$this->withoutMiddleware();
$this->visit('/')
->see('Laravel 5');
}
Ps : since Laravel 5.1
In my tests I add a method I can call at the beginning of a test to disable authorization only for that test.
Add to your base test class
public function withoutAuthorization()
{
\Gate::before(function () {
return true;
});
return $this;
}
And then in a test you can call it:
public function testSomeThing()
{
$this->withoutAuthorization();
// any gates will be bypassed
$this->get('/my-protected-endpoint');
}
It is much simpler.
Just take it out of the authorization required section in your routes file.
In this example I needed the AssignmentsController to work without Authentication, so I just moved it from the jwt.auth group up:
Route::post('v1/auth/login', 'Api\AuthController#authenticate');
Route::post('v1/auth/sendpassword', 'Api\AuthController#sendPassword');
Route::get('v1/assignments', 'Api\AssignmentsController#getAll');
Route::group(['middleware' => 'jwt.auth'], function() {
Route::post('v1/auth/logout', 'Api\AuthController#logout');
Route::post('v1/shipments', 'Api\ShipmentController#getShipments');
Route::post('v1/assignments/{id}/transfer', 'Api\AssignmentsController#saveAssignment');
Route::get('v1/shipments/assignments/{assignmentId}', 'Api\AssignmentsController#getDetail');
Route::post('v1/shipments/assignments/{id}/upload', 'Api\AssignmentsController#uploadFile');
});
I'm doing an existence check within a middleware, by checking a route-parameter.
If the check succeeds, I'm attaching it's model to the request to make it available throughout the rest of the request-cycle, application.
// App\Http\Middleware\CheckForExistence.php:
...
public function handle($request, Closure $next)
{
// some checks...
// success
$request->attributes->add([
'company' => $someModel
]);
}
I now have a controller which 'needs' this information in a couple of methods. So my thought was to add it to the construct of the controller and add it as a protected var in the whole controller:
// App\Http\Controllers\MyController.php
<?php
use Illuminate\Http\Request;
class MyController extends Controller
{
protected $company;
public function __construct(Request $request)
{
$this->company = $request->attributes->get('company');
}
public function index()
{
dd($this->company); // returns null
}
}
This controllers index() returns null instead of the give model.
If I change the index() method to:
public function index(Request $request)
{
return $request->attributes->get('company');
}
This returns the model; as expected.
Why is this happening? It looks like the middleware is not run when the controller is constructed.... Is there a way to circumvent it?
Or am I missing the obvious here.....
I could off course repeat myself in each method; but that is not very DRY ;)
You can't access the session or authenticated user in your controller's constructor because the middleware has not run yet, So you can do it like this :
public function __construct()
{
$this->middleware(function ($request, $next) {
$this->company = $request->attributes->get('company');
return $next($request);
});
}
For reasons currently unclear to me, the controller object is constructed before the request changes are reflected in the request object. In short the request is not considered properly constructed when a controller is constructed. This post seems to imply that.
There's two ways to work around this (if for a second we ignore what you're trying to do).
Use request dependency injection
public function index(Request $request)
{
$compary = $request->attributes->get('company');
}
This is not really WET because you're just swapping $this->company with $request->attributes->get('company') it's just a refactor. You should be injecting the request in the controller action anyway and if you don't want to do that you can use the request() helper.
Use a callback middleware in the constructor (Maraboc's answer explains how)
Now if you want a more case specific solution though you can use case specific dependency injection:
If you need to bind a model to a specific route parameter you can use route model binding and add the following in your RouteServiceProvider (or any provider).
Route::bind("companyAsARouteVarName", function () {
// this is why more details in the question are invaluable. I don't know if this is the right way for you.
//checks
// success
return $someModel;
});
Then you will register your route as:
Route::get("/something/{companyAsARouteVarName}", "SomeController#index");
and your controller will be:
public function index(Company $companyAsARouteVarName) {
//Magic
}
Controller constructor will be initialized before middleware execution.
You can get data from Injected $request object in controller functions.
I've been reading the documentation up and down now, still not sure what I'm doing wrong. In my opinion the documentation is very difficult to understand for a beginner.
Anyway, I'm trying to make something akin to the Auth::user() method, where it returns additional data about a logged in user that I will be needing for this application.
I have this helper class here:
namespace App\Helpers;
use Auth;
use Illuminate\Http\Request;
use App\Models\Grouping\User;
use App\Models\Grouping\Client;
use App\Models\Grouping\Rank;
class ClientUser {
public function __construct($request) {
$this->request = $request;
}
public function client() {
return Client::find($this->request->session()->get('client_id'));
}
public function auth() {
if (Auth::check()) {
// Get the client
$client = $this->client();
// Get the client's user
$user = $client->users()->find(Auth::user()['id']);
// Get the rank of the logged in user
$rank = Rank::find($user->pivot->rank_id);
return [
'user' => $user,
'rank' => $rank,
'client' => $client
];
}
return null;
}
}
This is responsible for doing what I described, returning additional data that I can't get through Auth::user(). Now I'm trying to register this class in the AuthServiceProvider
public function register()
{
// Register client auth
$request = $this->app->request;
$this->app->singleton(ClientUser::class, function ($app) {
return new ClientUser($request);
});
}
Now what I don't understand is how I'm supposed to make this globally accessible throughout my app like Auth::user() is.
The problem with just making "importing" it is that it needs the request object, which is why I'm passing it through the service container.
Now here's where I'm stuck. I'm not able to access app in my controller or anywhere, and I can't define a Facade because a Facade expects you to return a string of the bound service that it should "alias?"
Change your service provider like this :
$this->app->bind('client.user', function ($app) {
return new ClientUser($app->request);
});
Create another class extended from Illuminate\Support\Facades\Facade.
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class ClientUserFacade extends Facade {
public static function getFacadeAccessor(){
return "client.user";
}
}
Add 'ClientUser => ClientUserFacade::class in alias key of app.php
Using dingo/api along with lucadegasperi/oauth2-server-laravel. Authenticating a user is fine and I get an access token back but any time I make another request I get the following error:
call_user_func() expects parameter 1 to be a valid callback, no array or string given
I'm using the Service Provider option listed in the dingo/api docs and it's definitely setting the user resolver (I'd var_dump'd the resolver in the setUserResolver method).
My OauthServiceProvider is below.
<?php namespace App\Providers;
use Dingo\Api\Auth\Auth;
use Dingo\Api\Auth\Provider\OAuth2;
use App\User\User;
use Illuminate\Support\ServiceProvider;
class OAuthServiceProvider extends ServiceProvider
{
public function boot()
{
$this->app[Auth::class]->extend('oauth', function ($app) {
$provider = new OAuth2($app['oauth2-server.authorizer']->getChecker());
$provider->setUserResolver(function ($id) {
return User::first();
// Logic to return a user by their ID.
});
$provider->setClientResolver(function ($id) {
// Logic to return a client by their ID.
});
return $provider;
});
}
public function register()
{
//
}
}
So turns out I was completely off in where I was looking. In config/api.php, in my auth settings I had
'oauth2' => Dingo\Api\Auth\Provider\OAuth2::class,
Should have just been
'oauth' => Dingo\Api\Auth\Provider\OAuth2::class,