Understanding Auth::check method - how it works under the hood - php

I have created the following method in my admin controller:
public function index() {
// $recentBlogPost = DB::table('Admin')->get();
// Auth::logout();
if (!(Auth::check())) {
return Redirect::to('login');
}
$tags = DB::table('Tags')->get();
/* convert Object to array */
$tagsArray = array();
foreach($tags as $tag) {
$tagsArray[$tag->tag] = $tag->tag;
}
$tagsArray = json_decode(json_encode($tagsArray) , TRUE);
return view('admin.index')->with('tags' , $tagsArray);
}
Now i see the following line of code
(Auth::check())
I got this code from the laravel docs here ->https://laravel.com/docs/5.0/authentication
I would reallly like to know that that class or method is really doing under the hood , which file do i pull up for me to have a sneak peak to the code under the hood.

Auth::check()
// Illuminate\Auth\GuardHelpers
/**
* Determine if the current user is authenticated.
*
* #return bool
*/
public function check()
{
return ! is_null($this->user());
}
If you want to know $this->user():
// Illuminate\Auth\SessionGuard
/**
* Get the currently authenticated user.
*
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
if ($this->loggedOut) {
return;
}
// If we've already retrieved the user for the current request we can just
// return it back immediately. We do not want to fetch the user data on
// every call to this method because that would be tremendously slow.
if (! is_null($this->user)) {
return $this->user;
}
$id = $this->session->get($this->getName());
// First we will try to load the user using the identifier in the session if
// one exists. Otherwise we will check for a "remember me" cookie in this
// request, and if one exists, attempt to retrieve the user using that.
$user = null;
if (! is_null($id)) {
if ($user = $this->provider->retrieveById($id)) {
$this->fireAuthenticatedEvent($user);
}
}
// If the user is null, but we decrypt a "recaller" cookie we can attempt to
// pull the user data on that cookie which serves as a remember cookie on
// the application. Once we have a user we can return it to the caller.
$recaller = $this->getRecaller();
if (is_null($user) && ! is_null($recaller)) {
$user = $this->getUserByRecaller($recaller);
if ($user) {
$this->updateSession($user->getAuthIdentifier());
$this->fireLoginEvent($user, true);
}
}
return $this->user = $user;
}
How it happen?
You're using facade/helper - Illuminate\Support\Facades\Auth, but there are no check method.
Illuminate\Support\Facades\Auth extends Illuminate\Support\Facades\Facade
Illuminate\Support\Facades\Facade has a magic __callStatic() method
__callStatic execs check method on 'auth' instance
'auth' instance/singleton is registered in Illuminate\Auth\AuthServiceProvider::registerAuthenticator()
Ok, it means 'auth' instance is a 'Illuminate\Auth\AuthManager'
There is no 'check' method, but 'Illuminate\Auth\AuthManager' has a magic __call method
Illuminate\Auth\AuthManager::__call() execs 'check' method on 'guard' instance
Illuminate\Auth\AuthManager::guard() inits a guard according to your configs/auth.php
By default guard - session (Illuminate\Auth\SessionGuard)
Illuminate\Auth\SessionGuard does not have check method, but it uses Illuminate\Auth\GuardHelpers trait
Illuminate\Auth\GuardHelpers has check method
In other word Auth facade:
Execs facade methods
If facade method not found, execs Illuminate\Auth\AuthManager methods
If AuthManager method not found, execs guard(and guard trait) methods

To understand how it works it's better to look at the check() method source code:
public function check()
{
return ! is_null($this->user());
}
So it's just checking if user instance exists or not and returns true or false.

Related

Laravel resource policy always false

I'm trying to allow user to view their own profile in Laravel 5.4.
UserPolicy.php
public function view(User $authUser, $user)
{
return true;
}
registered policy in AuthServiceProvider.php
protected $policies = [
App\Task::class => App\Policies\TaskPolicy::class,
App\User::class => App\Policies\UserPolicy::class
];
Routes
Route::group(['middleware' => 'auth'], function() {
Route::resource('user', 'UserController');
} );
Blade template
#can ( 'view', $user )
// yes
#else
// no
#endcan
UserController.php
public function profile()
{
return $this->show(Auth::user()->id);
}
public function show($id)
{
$user = User::find($id);
return view('user.show', array( 'user'=>$user,'data'=>$this->data ) );
}
The return is always 'false'. Same for calling policy form the controller. Where do I go wrong?
Answering my own question feels weird, but I hate it when I come across questions without followups.
So after double checking It turned out that if I remove authorizeResource from the constructor:
public function __construct()
{
$this->authorizeResource(User::class);
}
and check for authorization in the controller function:
$this->authorize('view',$user);
everything works.
I must've missed this part when I added $user as a parameter in the policy function. So the user to be viewed is never passed in the authorizeResource method.
Thanks everyone for taking your time to help me.
When you add
public function __construct()
{
$this->authorizeResource(User::class);
}
to your Controller, you have to edit all your function signatures to match it to the class e.g. your show signature has to change from public function show($id)
to public function show(User $user)
After that it should work
Just a different approach here to users viewing their own profile.
First, I will create a route for that
Route::group(['middleware' => 'auth'], function() {
Route::get('profile', 'UserController#profile');
});
Then in the profile function I do
public function profile()
{
$user = Auth::user();
return view('profile', compact('user'));
}
This way, user automatically only views their own profile.
Now, if you want to allow some users to view others' profiles, then you can use Policy. Why? Because I think user should ALWAYS be able to view their own profile. But not all users should view other users profiles.
Solution:
Change the second parameter from #can( 'view', $user ) to #can( 'view', $subject ) and it will work find.
Why:
Because you're doing it the wrong way.
public function view(User $user, $subject){
return true;
}
Just look carefully the policy view method, first parameter is authenticated user or current user and second parameter is $subject, Since policies organize authorization logic around models.
Policies are classes that organize authorization logic around a
particular model or resource. For example, if your application is a
blog, you may have a Post model and a corresponding PostPolicy to
authorize user actions such as creating or updating posts.
if you want to go further deep inside it.
https://github.com/laravel/framework/blob/5.3/src/Illuminate/Auth/Access/Gate.php#L353
/**
* Resolve the callback for a policy check.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param string $ability
* #param array $arguments
* #return callable
*/
protected function resolvePolicyCallback($user, $ability, array $arguments)
{
return function () use ($user, $ability, $arguments) {
$instance = $this->getPolicyFor($arguments[0]);
// If we receive a non-null result from the before method, we will return it
// as the final result. This will allow developers to override the checks
// in the policy to return a result for all rules defined in the class.
if (method_exists($instance, 'before')) {
if (! is_null($result = $instance->before($user, $ability, ...$arguments))) {
return $result;
}
}
if (strpos($ability, '-') !== false) {
$ability = Str::camel($ability);
}
// If the first argument is a string, that means they are passing a class name
// to the policy. We will remove the first argument from this argument list
// because the policy already knows what type of models it can authorize.
if (isset($arguments[0]) && is_string($arguments[0])) {
array_shift($arguments);
}
if (! is_callable([$instance, $ability])) {
return false;
}
return $instance->{$ability}($user, ...$arguments);
};
}
See the last line where it is calling the method with $user and $argument( in our case Model ) is passed.
Laravel Docs for Authorization/Policies
It's possible to escape one or more policies methods using options parameter at authorizeResource with except:
public function __construct()
{
$this->authorizeResource(User::class, 'user', ['except' => ['view']]);
}
This should be on Laravel's documentation, but it isn't. I discovered this just guessing. I think this way it is a better approach thus, by removing authorizeResource method in the construct, it would be necessary to implement the authorization method for each resource action in order to protect the controller.

How to bind user object to request in a middleware

i'm writing an application in Laravel Spark 1.0 (Laravel 5.2). I wrote a custom middleware for agent (api) authentication. This is the code:
<?php
namespace App\Http\Middleware;
use App\Agent;
use Closure;
use Illuminate\Http\Request;
class AgentAuth
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if( isset($request->token) && !empty($request->token) )
{
$agent = Agent::where('token', '=', $request->token)->first();
if( $agent != NULL )
{
$team = $agent->Team()->first();
$user = $team->User()->first();
$request->merge(['team' => $team ]);
$request->merge(['user' => $user ]);
return $next($request);
}
else {
return response('Unauthorized 2.', 401);
}
}
else {
return response('Unauthorized 1.', 401);
}
}
}
In the default laravel authentication the user object is injected in the request (see laravel docs): https://laravel.com/docs/5.2/authentication#retrieving-the-authenticated-user
So you can retrieve the user using:
$request->user();
Spark obviously use this method to check if user subscription is valid (laravel\spark\src\Http\Middleware\VerifyUserIsSubscribed):
if ($this->subscribed($request->user(), $subscription, $plan, func_num_args() === 2)) {
return $next($request);
}
And it's not working because, with my middleware, you can retrieve the user using: $request->user; but not with the laravel defaults $request->user();
How should i inject the user object into the request?
Thank you in advance
EDIT:
Laravel in the service provider (Illuminate\Auth\AuthServiceProvider#registerRequestRebindHandler)
Use this code to bind object user to the request:
/**
* Register a resolver for the authenticated user.
*
* #return void
*/
protected function registerRequestRebindHandler()
{
$this->app->rebinding('request', function ($app, $request) {
$request->setUserResolver(function ($guard = null) use ($app) {
return call_user_func($app['auth']->userResolver(), $guard);
});
});
}
I tried to insert this code, with the appropriate correction, in the middleware but i can't figure out how to make it work.
I don't have a copy of Spark to try this & ensure what I'm doing is correct for you, but I think this will help:
1) An assumption - I believe you are saying that yes, this line will get you the user you want:
$user = $team->User()->first();
and you merely want to bind it to the request so that you can access this user later in your app via:
$request->user()
2) If this is true, then all I did was simplify the code you provided to add:
$request->merge(['user' => $user ]);
//add this
$request->setUserResolver(function () use ($user) {
return $user;
});
// if you dump() you can now see the $request has it
dump($request->user());
return $next($request);
I also $request->user() in the route closure, and it is there.
The app rebinding was a little strange to me, and didn't seem necessary. I'm not sure that anything would really need this for what you are doing.
You could use the auth system if that model implements the right interface, to log them in for the request.
Auth uses a rebinder to assign the userResolver on request. (So you get $request->user() from it). Check Illuminate\Auth\AuthServiceProvider#registerRequestRebindHandler to see how its setting that resolver.
$request->setUserResolver(....)
This is a very useful question. I was having trouble with the selected solution though. In my middleware I could successfully see $request->user(), however it was failing when using gates, namely in the Access/Gate class:
protected function raw($ability, $arguments = [])
{
if (! $user = $this->resolveUser()) {
return false;
}
// ...
This function is always returning false :/
So I did it as suggested here (http://laravel-recipes.com/recipes/230/setting-the-currently-authenticated-user), namely:
$usr = new User();
$usr->setAttribute('id', $request->user_id);
Auth::setUser($usr);
And it appears to be working without using setUserResolver().
Thanks
If you have the user ID you can easily authenticate the user with \Illuminate\Support\Facades\Auth::onceUsingId($user_id)
This updates the $request object. For example:
public function test(Request $request)
{
Auth::onceUsingId(19);
$next = new \App\Http\Controllers\OtherController();
return $next->otherMethod($request);
}

Laravel user capabilities

Within Laravel you can easily define abilities and then hook into them later on a user request regarding to do different actions:
$gate->define('update-post', function ($user, $post) {
return $user->id === $post->user_id;
});
But almost all my defined abilities has this part $user->id === $model->user_id in it. I don't like it as it's a kind of repeating a condition over and over which I think could be more abstract.
Most of my defined abilities are according to updating/deleting records, so it would be better if I could make a global condition applied to all of them or if there could be a group ability defining which is like to what we do in routing.
Is there any workaround for it? I really like it DRY.
Everything in Laravel is extendable, that's the power of its service providers.
You can extend the Gate object to a MyCustomGate object and do whatever you want in that object. Here's an example:
MyCustomGate.php
class MyCustomGate extends \Illuminate\Auth\Access\Gate
{
protected $hasOwnershipVerification = [];
/**
* Define a new ability.
*
* #param string $ability
* #param callable|string $callback
* #return $this
*
* #throws \InvalidArgumentException
*/
public function defineWithOwnership($ability, $callback, $foreignUserIdKey = "user_id")
{
// We will add this
$this->hasOwnershipVerification[$ability] = $foreignUserIdKey;
return $this->define($ability, $callback);
}
/**
* Resolve and call the appropriate authorization callback.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param string $ability
* #param array $arguments
* #return bool
*/
protected function callAuthCallback($user, $ability, array $arguments)
{
$callback = $this->resolveAuthCallback(
$user, $ability, $arguments
);
// We will assume that the model is ALWAYS the first key
$model = is_array($arguments) ? $arguments[0] : $arguments;
return $this->checkDirectOwnership($ability, $user, $model) && call_user_func_array(
$callback, array_merge([$user], $arguments)
);
}
/**
* Check if the user owns a model.
*
* #param string $ability
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param \Illuminate\Database\Eloquent\Model $model
* #return bool
*/
protected function checkDirectOwnership($ability, $user, $model)
{
if(!isset($this->hasOwnershipVerification[$ability])) {
return true
}
$userIdKey = $this->hasOwnershipVerification[$ability];
// getAuthIdentifier() is just ->id, but it's better in case the pk of a user is different that id
return $user->getAuthIdentifier() == $model->{$userIdKey};
}
}
Then, you will have to tell Laravel to use your gate instead of the default one. You ca do that in your AuthServiceProvider (assuming that it's extending Illuminate\Auth\AuthServiceProvider, just add the following method.
AuthServiceProvider
/**
* Register the access gate service.
*
* #return void
*/
protected function registerAccessGate()
{
$this->app->singleton(\Illuminate\Contracts\Auth\Access\Gate::class, function ($app) {
return new MyCustomGate($app, function () use ($app) {
return $app['auth']->user();
});
});
}
And this way, you can define abilities using defineWithOwnership() method instead of define(). You can still use define() for abilities that don't require ownership verification. There's a third parameter defineWithOwnership() accepts which is $foreignUserIdKey; that's used for the case when a model has a different field for the user id.
Note: I wrote the code on the fly and did not try it, it may have errors, but you get the idea.
I checked your question quite a bit, but I've found no "easy" way to do it.
Instead, what I would probably do is this:
<?php
namespace App\Policies;
use App\User;
use App\Post;
trait CheckOwnership {
protected function checkOwnership($user, $model) {
$owned = $user->id === $model->user_id;
if ($owned === false)
throw new NotOwnedException;
}
}
class PostPolicy
{
use CheckOwnership;
public function update(User $user, Post $post)
{
try {
$this->checkOwnership($user, $post);
//continue other checks
} catch (NotOwnedException $ex) {
return false;
}
}
}
Add this function to your AuthServiceProvider
public function defineAbilities(array $abilities, $gate)
{
foreach($abilities as $name => $model){
$gate->define($name, function ($user, $model){
return $user->id === ${$model}->user_id;
});
}
}
and then inside boot method
$this->defineAbilities(['ability1' => 'model1', 'ability2' => 'model2'], $gate);
You can define another function and call it within the anonymous function. This will allow you to have commonly-used code in one central location while still allowing any resource-specific logic.
Add this function to your AuthServiceProvider class:
public function userCheck(User $user, $target)
{
// do the user id check
$result = isset($target->user_id) && isset($user) && $user->id === $target->user_id;
return $result;
}
Your code, modified:
$gate->define('update-post', function ($user, $post) {
// call the function
$result = $this->userCheck($user, $post);
// do some kind of 'update-post' specific check
return $result/* && some_bool_statement*/;
});
I think you can use middlewares.
Simply make a admin middleware and use it in your routes and routes group.
And there is no security bug on your project (delete, create & ... actions) because Laravel has csrf token!
You can use before() function, also.
And then an important note:
if you don't define a correspond function on Policy class and call it $this->authorize($post) on a controller an unauthorized Action error will be thrown unless before()methodreturnstrue.
for example call $this->authorize on Dashboard\PostsController:
public function edit($id)
{
$post = Post::find($id)->first();
$this->authorize($post);
return view('dashboard.post')->with(compact('post'));
}
and if we defined a PostPolicy Class:
class PostPolicy
{
use HandlesAuthorization;
public function before($user, $ability)
{
return $user->is_admin;
}
}
If user be admin he/she can edit post because we returned true in before() method despite of have not a method with same name (as edit method in PostsController).
In fact Laravel will check for before method mthod on Policy Class. if before return'snull will check for correspond method with same name on controller method and if this method not found user cannot perform action.
Thank you laravel for DRY us!♥

Redirect if authenticated logic in Laravel's built-in auth?

This question has been asked before, and I believe my code to be correct, but I am getting strange behaviour.
I need to redirect the user to different routes after login depending on some database values. I thought that in order to do this I simply had to place my logic in the handle method of app/Http/Middleware/RedirectIfAuthenticated.php. My method currently looks like so:
public function handle($request, Closure $next)
{
if ($this->auth->check()) {
if($this->auth->user()->sign_up_complete == 1){
return redirect('/');
} else {
if($this->auth->user()->step_one_complete == 0){
return redirect('/register/step-1');
} elseif($this->auth->user()->step_two_complete == 0){
return redirect('/register/step-2');
} else {
return redirect('/');
}
}
}
return $next($request);
}
This does not work, and upon login the user is redirected to /home. I have tried placing dd($this->auth->user()) inside the $this->auth->check() condition, but it never gets run. If I place it outside of that check then it's run on every request. It looks like $this->auth->check() is never run.
My question: If not here, where should this logic go?
I have removed protected $redirectTo = '/account'; from the AuthController.php controller too.
High level answer: the purpose of RedirectIfAuthenticated is to keep an already authenticated user from reaching the login or registration routes/views since they're already logged in.
Test: bookmark the login view. Then login. Close the browser or window. Open the login bookmark. You'll go straight to user's home or where ever specified in RedirectIfAuthenticated.
For purposes of the LoginController, create a redirecTo() method, which is what the redirectPath() method looks for to see if you have customized the redirect.
// example
public function redirectTo()
{
switch (auth()->user()->role) {
case 'foo':
return route('foo.home');
case 'bar':
return route('bar.home');
default:
auth()->logout();
return route('web.welcome');
}
}
You are not using the middleware correctly. This piece of code will fire everytime you send a request when you are logged in.
To change the redirect location after login you can override the redirectPath() method in your AuthController. (You can find the original method in vendor/laravel/framework/src/Illuminate/Foundation/Auth/RedirectsUsers.php)
This would look something like this:
...
public class AuthController extends Controller {
...
public function redirectPath()
{
if(Auth::user()->sign_up_complete == 1) {
return '/';
} else {
if(Auth::user()->step_one_complete == 0) {
return '/register/step-1';
} elseif(Auth::user()->step_two_complete == 0) {
return '/register/step-2';
} else {
return '/';
}
}
}
// The rest of the class implementation.
}
Note: I've replaced the $this->auth() method with the Facade alternative (Auth::). Just because I am not sure if the AuthController has an auth() method.
To understand why your routing logic is never reached, you should look in app/Http/Kernel.php where the RedirectIfAuthenticated middleware is registered:
protected $routeMiddleware = [
...
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
...
];
This means if a user navigates to a route that is not protected by the guest route middleware, the request never passes through the RedirectIfAuthenticated class and so misses your logic completely.
You can add guest middleware to your registration routes in your routes file to force the routing to pass through your code like this:
Route::get('/register/step-1', '<YOUR CONTROLLER METHOD>')->middleware('guest');
But, since you say the user is already logged in (not a guest) you should instead move your code as suggested by the other answers.
Only adding this as an answer, because it couldn't be clarified in the space allowed by a comment.
Solution is in Mark Walet's answer, but need little correction. Return should be a string:
public class AuthController extends Controller {
...
public function redirectPath()
{
if(Auth::user()->sign_up_complete == 1) {
return '/';
} else {
if(Auth::user()->step_one_complete == 0) {
return '/register/step-1';
} elseif(Auth::user()->step_two_complete == 0) {
return '/register/step-2';
} else {
return '/';
}
}
}
// The rest of the class implementation.
}
I think it's so easy as setting up a custom Middleware class to validate your requests based on the database values, I do this for excluding users without the correct role.
The role is defined in my user table and only users with the administrator role are allowed to access the system.
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\MessageBag;
class RolesMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
// If a user is authenticated
if(\Auth::user() != null)
{
// If the user doesn't have the correct role
if(\Auth::user()->role != 'administrator')
{
// logout the user
\Auth::logout();
// create a new MessageBag
$messageBag = new MessageBag;
// add a message
$messageBag->add('not_allowed', 'You are not allowed to login, because you do not have the right role!');
// redirect back to the previous page with errors
return \Redirect::to('login')->withErrors($messageBag);
}
}
return $next($request);
}
}
u cant change the core files of laravel all u need to do is adding this code to
Auth\AuthController
protected $redirectPath = '/dashboard';

Laravel 4 custom Auth

First of all sorry about my english, I'll try to do my best.
Im new to Laravel, im trying to implement custom auth throught a SOAP WS, I declare new class that implement UserProviderInterface. I success on implement retrieveByCredentials and validateCredentials methods but since i dont have access to database or global users information i cant implement retrieveByID method. Is there any way to make custom Auth not based on users id's ?
I need:
- Login and validate user throught SOAP WS
- Store User Info returned by WS.
- Remember me functionality
- Secure routes based on logged user and level of access
- Logout
Implemented class:
<?php
namespace Spt\Common\Providers;
use Illuminate\Auth\UserProviderInterface;
use Illuminate\Auth\GenericUser;
use Illuminate\Auth\UserInterface;
class AuthUserProvider implements UserProviderInterface{
private $user;
public function __construct(){
$this->user = null;
}
public function retrieveByID($identifier){
return $this->user;
}
public function retrieveByCredentials(array $credentials){
$client = new \SoapClient('webserviceurl');
$res = $client->Validar_Cliente($credentials);
$res = $res->Validar_ClienteResult;
if($res->infoError->bError === true){
return;
}
$res->id = $res->id_cliente;
$user = new GenericUser((array) $res);
return $user;
}
public function validateCredentials(UserInterface $user, array $credentials){
//Assumed that if WS returned a User is validated
return true;
}
}
I think that re-implement UserProviderInterface its not the solution but i googled and not found other way
Any Idea?
You're almost done, apart from the fact that private variable $user of AuthUserProvider doesn't survive the current http request. If you cannot "retrieve by id" from your web service, I guess the only way is to store the entire user in the session - Laravel itself stores the user's id in the session and the fact that it stores only the id (not the entire user) is one of the reasons why a retrieveByID method is needed.
The following is only to clarify and is untested.
class AuthUserProvider implements UserProviderInterface {
public function retrieveByCredentials(array $credentials) {
$client = new \SoapClient('webserviceurl');
$res = $client->Validar_Cliente($credentials);
$res = $res->Validar_ClienteResult;
if($res->infoError->bError === true) {
return;
}
$res->id = $res->id_cliente;
Session::put('entireuser', $res);
$user = new GenericUser((array) $res);
return $user;
}
public function retrieveByID($identifier) {
$res = Session::get('entireuser');
return new GenericUser((array) $res);
}
// ...
}
If you can't retrieve by id from your web service, I guess you cannot either retrieve by remember token, so it may be impossible for you to implement the "remember me" functionality, unless you store part of users data in a second database (which at that point could be used in place of the session above).

Categories