I'm using laravel basic policy system to protect unauthorized user from update post. For Example User have Id 1 and In posts Table User_id is also 1.
Now in $this->authorize('update',$post); way I can pass only one variable $post to authenticate. while in can method I can also use $user variable $user->can('update',$post) for authorize.
Here is code:
In PostPolicy.php :
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}
In AuthServiceProvider.php :
protected $policies = [
Post::class => PostPolicy::class
]
In Controller Authorize way :
public function update(Request $request, $id)
{
$post=Post::find(1);
$user=User::find(1);
$this->authorize('update',$post);
return 'Hello Everything Access For You ';
}
Using can method in Controller :
public function update(Request $request, $id)
{
$post=Post::find(1);
$user=User::find(1);
if($user->can('update',$post)){
return 'Your are allowed';
}
else
{
return 'Your are Not allowed';
}
}
Is I'm right for these two functions. Is there any Difference. Which method I have to use. Thanks in Advance.
If you are using any of authorize() or can(), the purpose is to validate if user is authorized to do certain tasks.
But :
In case of authorize(), if it fails(returns false from policy method), the authorize method will throw an Illuminate\Auth\Access\AuthorizationException, which the default Laravel exception handler will convert to an HTTP response with a 403
In case of can(), it like just a basic method to check if user is authorized or not and then you need to handle the rest on your own. Like what to do if its unauthorized.
Considering above factors, I would say $this->authorize('update',$post); is easier to be used in controller.
Check more about the same in documentation
Also you can do :
$request->user()->can() if you want to check authorization of current requesting user.
Update :
authorize() is designed to authorize current user who is logged in, where laravel passes current user automatically to policy.
Whereas you can use can on any user instance.
$request->user()->can() if you want to check authorization of current requesting user.
$user = $user::find($id); $user->can(...) if its any other user
$this->authorize() checks if the current user is authorized. $user->can() checks if the user in $user is authorized. Both rely on the same underlying policy to make the decision.
The main difference between the two is that $this->authorize() throws an exception if the current user isn't authorized (as it's intended for use within a controller), whereas $user->can() just returns true/false.
If you want a controller to act as if it's doing $this->authorize() for a different user than the current one, you can do this:
// where "123" is the user you want to check against
$user = App\User::find(123);
if(!$user->can('update', $post) {
throw new \Illuminate\Auth\Access\AuthorizationException;
}
That said, this is rarely what you want to do - it doesn't (usually) make much sense to determine if the current user can do something based on another user's permissions.
Related
Let's say we have action in the policy for our model that can return false in bunch of different scenarios:
class PostPolicy
{
public function publish(User $user, Post $post)
{
if ($post->user_id !== $user->id) {
return false;
}
return $post->show_at->lessThan(now());
}
}
As you can see, we are denying this user his publishing rights in two cases: if it's not his post or if this post was prepared in advance for some future date that is not yet due.
How can I provide some context as to why authorization failed? Was it because we are not the owner or was it because it's not time yet for this post to be published?
$user->can('publish', $post); // if this returns false we don't know
// what caused authorization to fail.
It looks that Laravel policies by design doesn't have any way of doing that. But I am curious as to what workarounds there can possibly be so that we can have authorization logic (no matter how intricate) in one place (model's policy) and also get some context (i.e., custom error codes) when authorization fails.
Any ideas?
In case any one needed,
apart from above accepted answer, in Laravel 7+, Gate can provide reasons for denial,
Reference: https://laravel.com/docs/7.x/authorization#gate-responses
Gate::authorize() calls will throw reason along with the exception with message provided in the Response::deny(<message>) call, the exception itself will be an Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
$user->can() or Gate::allows() will give boolean
Gate::inspect() will give full response
Given that you will now return a Gate Response object instead of boolean, Laravel will help on returning suitable response stated above.
<?php
use Illuminate\Auth\Access\Response;
Originally, you only return a boolean
<?php
class PostPolicy
{
public function publish(User $user, Post $post)
{
return $post->user_id !== $user->id;
}
}
By using Gate Response, you can now provide a reason
<?php
use Illuminate\Auth\Access\Response;
class PostPolicy
{
public function publish(User $user, Post $post)
{
return $post->user_id === $user->id
? Response::allow()
: Response::deny('You are not the author of the post.');
}
}
If in controller you are using the policy like :
<?php
$this->authorize('publish', Post::class)
Then laravel is going to have 403 HTTPResponse error.
What you should do is, one policy method should just be checking one validation case.
For example update your policy :
<?php
// Check if post he is trying to publish is his own
public function publishOwnPost(){...}
// Check if post is for future purpose
public function publicFuturePost(){...}
Then in controller do :
<?php
if(!$user->can('publishOwnPost', $post)){
// Return custom error view for case 1
return response()->view('errors.publishOwnPostError', $data, 403);
}
if(!$user->can('publishFuturePost', $post)){
// Return custom error view for case 2
return response()->view('errors.publishFuturePostError', $data, 403);
}
// Do further processing
What I ended up doing was splitting some of the responsibilities between model and it's policy.
Policy ended up responsible for making sure user has the right to do a specific action. Nothing more or less:
class PostPolicy
{
public function publish(User $user, Post $post)
{
return $post->user_id !== $user->id;
}
}
Model on the other hand must have logic to check whether or not certain action can be performed with it:
class Post extends Model
{
...
public function isPublishable()
{
return $this->show_at->lessThan(now());
}
...
}
Therefore each post instance now can tell us whether or not it can be published. Lastly my Post::publishBy(User $user) action will include authorizing user for this action first and checking if this post can be published separately so that we can determine specific reason as to why publishing failed.
I feel like this design suits better, leaving Laravel policies to do only what they are supposed to be doing (authorizing user actions) and requiring models to be responsible for things that only concern them.
I know, how to use Laravel policies and everything works, but I am stuck with create(...) method.
My app is a training diary for ski racers. Each racer can manage (view, add, edit..) his own diary. Each trainer can manage his own diary, diary of all racers but not other trainers. I use native Laravel Policies and it works great during *update(...) and delete(...) method, but not create(...).
TrainingRecordPolicy.php:
public function update(User $user, TrainingRecord $record)
{
if ($user->id == $record->user->id) {
return true;
}
if ($user->isTrainer() && $record->user->isRacer()) {
return true;
}
return false;
}
I'm using it in controllers like $this->authorize('update', $record). But if I want to check, if user can create new record into others user diary, I have no idea how to deal with it.
This doesn't work $this->authorize('create', TrainingRecord::class, $diaryOwner) and if I use $this->authorize('createDiaryRecord', $diaryOwner) it calls method inside UserPolicy.
How do I send $diaryOwner as extra parameter to create() method within the policy?
Note: $diaryOwner is retrieved from the route parameter user in route with signature /diary/{user}
You can access the $diaryOwner within the policy using request() helper.
public function create(User $user)
{
$diaryOwner = request()->user; // because route is defined as /diary/{user}
}
There may be a problem because:
When using dynamic properties, Laravel will first look for the parameter's value in the request payload. If it is not present, Laravel will search for the field in the route parameters.
So instead of using request()->user use request()->route()->parameter('user').
Also if you are using \Illuminate\Routing\Middleware\SubstituteBindings::class you will get User instance.
When we login into our gmail account for the first time or after removing the cache and cookie, we get the window to type a code which is sent to our Mobile.
I am trying to implement this but through email instead of SMS. Below is my approach to implement this.
I am following this link : https://laravel.com/docs/5.2/session
and create a Session table in database. I can also see my browser details in Session Table record. I am not sure if this is the correct approach.
Gmail has provision keep track of multiple browsers. This means if I last time logged in from Firefox and this time from Chrome then I will be asked for code again. Going forward, I will not be asked to fill code for Chrome and Firefox if cache/cookie is not removed.
Can somebody give me any link that explains how to make provision for multiple browsers when it is cache/cookie saved ? so that I can send email for security code
You can achieve this by issuing a extra cookie (lets say browser_cookie) to remember the already authenticated browser.
Implementation:
Create a following table (browser_management) :
token (pk)| user_id (fk) | series_identifier
where:
token : hashed form of token issued to the user (using bcrypt or similar algorithm ) (token issued to user, itself, is unguessable randomly generated key from a suitably large space)
series_identifier : unguessable randomly generated key from a suitably large space
Whenever, user logs in check for the browser_cookie.
Case 1: User is logging for the first time.
Considering user is logging in for first time browser_cookie won't be present. So, you would send an email with the authentication code.
Once authenticated, generate two random numbers each for token and series_identifier. Store the hashed token and series_identifier in the browser_management table, for the user identified by user_id.
Also, issue the browser_cookie to the user with token and series_identifier.
Case 2: User is re-logging next time.
Now, when same user logs in next time,take the token and find the entry in the browser_management table with the hashed token.
If found, check if the user_id and series_identifier matches.
Case 2.1: Entries matched:
Allow the user to enter the system without the need to re-authenticate the email code.
Generate another token and replace the token in the cookie as well as table with the new one. (This will lower the risk of session hijacking).
Case 2.2: Entries does not match:
Follow the steps for email authentication and notify the user regarding the possible theft.(Like gmail notifying new browser logins).
References:
Improved Persistent Login Cookie Best Practice
What is the best way to implement “remember me” for a website?
Update:
Sample code:
Migration:
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class browser_management extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('browser_management', function (Blueprint $table) {
$table->string('token');
$table->string('user_id');
$table->string('series_identifier');
$table->timestamps();
$table->primary('token');
$table->foreign('user_id')->references('id')->on('users');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('users');
}
}
Middleware: Create a new middleware
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
use Cookies;
class Email_verification
{
public function handle($request, Closure $next, $guard = null)
{
//retrieve $token from the user's cookie
$token = $request->cookie('browser_cookie');
//check id token is present
if($token == null){
//if token is not present allow the request to the email_verification
return $next($request);
}
else{
//Retrieve the series_identifier issued to the user
$series_identifier = Auth::user()
->series_identifier(Hash::make($token))
->first()
->series_identifier;
//Check if series_identifier matches
if($series_identifier != $request->cookie('series_identifier')){
//if series_identifier does not match allow the request to the email_verification
return $next($request);
}
}
return redirect('/dashboard'); //replace this with your route for home page
}
}
Make the middleware's entry in the kernel.php
protected $routeMiddleware = [
'email_verification' => \App\Http\Middleware\Email_verification::class,
//your middlewares
];
User Model: Add the following method's to your user model
// method to retrieve series_identifier related to token
public function series_identifier($token){
return $this->hasMany(Browser_management::class)->where('token',$token);
}
//method to retriev the tokens related to user
public function tokens (){
return $this->hasMany(Browser_management::class);
}
Browser_management Model: Create a model to represent browser_managements table
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Browser_management extends Model
{
protected $primaryKey = 'token';
protected $fillable = array('token','series_identifier');
public function User(){
return $this->hasOne('App\Models\User');
}
}
Email Verification Methods: Add the following methods to your AuthController for handling the email verification
public function getVerification(Request $request){
//Create a random string to represent the token to be sent to user via email.
//You can use any string as we are going to hash it in our DB
$token = str_random(16);
//Generate random string to represent series_identifier
$series_identifier = str_random(64);
//Issue cookie to user with the generated series_identifier
Cookie::queue('series_identifier', $series_identifier,43200,null,null,true,true);
//Store the hashed token and series_identifier ini DB
Auth::user()->tokens()->create(['token'=>Hash::make($token)]);
//Your code to send an email for authentication
//return the view with form asking for token
return view('auth.email_verification');
}
public function postVerification(Request $request){
//Retrieve the series_identifier issued to the user in above method
$series_identifier = $request->cookie('series_identifier');
//Retrieve the token associated with the series_identifier
$token = Auth::user()
->tokens()
->where('series_identifier',$series_identifier)
->first()
->value('token');
//Check if the user's token's hash matches our token entry
if(Hash::check($request->token,$token)){
// If token matched, issue the cookie with token id in it. Which we can use in future to authenticate the user
Cookie::queue('token', $token,43200,null,null,true,true);
return redirect('dashboard');
}
//If token did not match, redirect user bak to the form with error
return redirect()->back()
->with('msg','Tokens did not match');
}
Routes: Add these routes for handling email verification requests. We will also add the email_verification middleware to it.
Route::get('/auth/email_verification',`AuthController#getVerification')->middleware('email_verification');
Route::post('/auth/email_verification',`AuthController#postVerification')->middleware('email_verification');<br/>
Update 2:
Regarding the flow of gmail..
I followed following steps:
1)Log into gmail followed by 2-step verification.
2)Log out
3)Clear cache link
4)Log in again
When I loged in again, after clearing the cache, it did not ask me for 2-step verification.
Though, If you clear the cookies, it will ask for 2-step verification.
Reason:
All the user data which identifies the user (here token) is stored in the cookies. And if you clear the cookies, server will have no mechanism to identify the user.
Update 3:
Gmail asking for 2-step verification:
First of all Gmail or any other website is not notified about the clearing of cache
As given here:
The cache is nothing more than a place on your hard disk where the
browser keeps things that it downloaded once in case they’re needed
again.
Now, cookies are the small text files issued by server to store user related information. As given here
The main purpose of a cookie is to identify users and possibly prepare
customized Web pages or to save site login information for you.
So, basically when you clear the cookies in your browser, webserver will not get any user data. So, the user will be considered as guest and will be treated accordingly.
Create an additional table ( besides the session one )
Something like
UserId | UserAgent | IP
And when they go to login check that against their current values in the $_SERVER array. If it's in there all is good, if not interrupt the login, and send them a link to confirm the new data. You'll probably want to do some kind of ajax on the original login to check when they are logged in, then once that happens do the redirect to where they were going.
Make sense.
As I said in the comments for maintainability I would handle it myself and not use any third party APIs, the data is easy enough to verify. That part is relatively trivial, continuing the login process not so much.
OP, if I understand you clearly, you simply want to understand how to implement the laravel session table so you can have multiple login from same user in the same browser:
Schema::create('sessions', function ($table) {
$table->string('id')->unique();
$table->integer('user_id')->nullable();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->text('payload');
$table->integer('last_activity');
});
While this question has been answered before here I will add that you can easily achieve this feature in your actual login method without modifying your core files.
To do this, you can follow the following logic
Before login, check manually if request user agent header is same as session user agent in session, i.e.:
public function authenticate()
{
$data = Input::all();
$user = User::where('email', '=', $data['email'])->first();
if($user != null)
{
//check if user agent different from session
if($request->header('User-Agent') != session('user_agent'))
{
//do some extra login/password validation check
}
if(Auth::attempt($data))
{
//here you now may manually update the session ID on db
}
}
}
You will have to do substantially more work than this but I hope you get the concept.
Case: I'm building a forum using Laravel's Authorization as a backbone using policies. Examples of checks I run are stuff like #can('view', $forum), and #can('reply', $topic), Gate::allows('create_topic', $forum) etc. These checks basically checks if the users role has that permission for the specific forum, topic or post. That way I can give roles very specific permissions for each forum in my application.
The issue is that all of these checks go through the Gate class, specifically a method called raw() which in its first line does this:
if (! $user = $this->resolveUser()) {
return false;
}
This presents an issue when dealing with forums. Guests to my application should also be allowed to view my forum, however as you can see from the code above, Laravels Gate class automatically returns false if the user is not logged in.
I need to be able to trigger my policies even if there is no user. Say in my ForumPolicy#view method, I do if(User::guest() && $forum->hasGuestViewAccess()) { return true }
But as you can see, this method will never trigger.
Is there a way for me to still use Laravel's authorization feature with guest users?
I'm not aware of a super natural way to accomplish this, but if it's necessary, you could change the gate's user resolver, which is responsible for finding users (by default it reads from Auth::user()).
Since the resolver is protected and has no setters, you'll need to modify it on creation. The gate is instantiated in Laravel's AuthServiceProvider. You can extend this class and replace the reference to it in the app.providers config with your subclass.
It's going to be up to you what kind of guest object to return (as long as it's truthy), but I'd probably use something like an empty User model:
protected function registerAccessGate()
{
$this->app->singleton(GateContract::class, function ($app) {
return new Gate($app, function () use ($app) {
$user = $app['auth']->user();
if ($user) {
return $user;
}
return new \App\User;
});
});
}
You could go a step further and set a special property on it like $user->isGuest, or even define a special guest class or constant.
Alternatively you could adjust your process at the Auth level so that all logged-out sessions are wrapped in a call to Auth::setUser($guestUserObject).
I just released a package that allows permission logic to be applied to guest users. It slightly modifies Laravel's Authorization to return a Guest object instead of null when no user is resolved. Also every authorization check now makes it to the Gate instead of failing authorization instantly because there isn't an authenticated user.
I do have a UserController and User Model in my Laravel 5 source.
Also there is one AuthController is also Present (shipped prebuilt with laravel source).
I would like to query data from db in my blades making use of Eloquent Models.
However, Neither in my User Model (Eloquent ) nor in any of the controller, the user() method is defined. even then, I could use it in my blade by accessing it from Auth class. why?
For example,
in my blade, {{ Auth::user()->fname }} works. it retrieve the data fnamefrom my users table and echo it.
What is the logic behind it, and can i emulate the same for other db tables such as tasks?
Whenever you do it automatically or manually some like this
if (Auth::attempt(['email' => $email, 'password' => $password]))
{
}
The selected User's Data will be stored in the storage/framework/sessions
It will have data something like
a:4:{s:6:"_token";s:40:"PEKGoLhoXMl1rUDNNq2besE1iSTtSKylFFIhuoZu";s:9:"_previous";a:1:{s:3:"url";s:43:"http://localhost/Learnings/laravel5/laravel";}s:9:"_sf2_meta";a:3:{s:1:"u";i:1432617607;s:1:"c";i:1432617607;s:1:"l";s:1:"0";}s:5:"flash";a:2:{s:3:"old";a:0:{}s:3:"new";a:0:{}}}
The above sessions file doesn't have any data and it will have the data such as user's id, url, token in json format.
Then whenever you call the {{ Auth::user()->fname }} Laravel recognises that you're trying to fetch the logged in user's fname then laravel will fetch the file and get the user's primary key and refer it from the user's table from your database. and you can do it for all coloumns of the users table that you have.
You can learn more about it here
This user function is defined under
vendor/laravel/framework/src/Illuminate/Auth/Guard.php
with following content :
/**
* Get the currently authenticated user.
*
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
if ($this->loggedOut) return;
// If we have already retrieved the user for the current request we can just
// return it back immediately. We do not want to pull the user data every
// request into the method because that would tremendously slow an app.
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))
{
$user = $this->provider->retrieveById($id);
}
// 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;
}
this Guard.php has more functions defined in it which we use every now and then without even knowing where they are coming from
It works because Laravel comes with decent authentication.
Auth is the authentication library and has plenty of features like this, check out the documentation!