I want to implement Impersonate functionality into Laravel-8 without using any package.
Only super-admin can use this functionality.
I used laravel sanctum to authenticate.
to access impersonate functionality user should be super-admin. (is_admin(boolean) flag is set into users table).
Here is my middleware:
<?php
namespace App\Http\Middleware;
use Closure;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class ImpersonateUser
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next)
{
$impersonateId = $request->cookie('x-impersonate-id');
if($request->user()->is_admin && $impersonateId) {
$user = User::findOrFail($impersonateId);
if($user->is_admin) {
return response()->json(["message" => trans("You cannot impersonate an admin account.")], 400);
}
Auth::setUser($user);
}
return $next($request);
}
}
My route file:
// Impersonate routes.
Route::middleware(['auth:sanctum', 'impersonate'])->group(function () {
// checklist routes
Route::get('checklists', [ChecklistController::class, "index"]);
});
Whether use Auth::setUser($user) is safe or I have to use Auth::onceUsingId($userId); ?
Auth::onceUsingId($userId); not working with auth::sanctum middleware. So Auth::setUser($user) is safe or not?
I used laravel to develop backend API only.(SPA)
They should be the same in terms of safety. OnceUsingId() calls setUser() in the background.
From the Illuminate\Auth\SessionGuard class
/**
* Log the given user ID into the application without sessions or cookies.
*
* #param mixed $id
* #return \Illuminate\Contracts\Auth\Authenticatable|false
*/
public function onceUsingId($id)
{
if (! is_null($user = $this->provider->retrieveById($id))) {
$this->setUser($user);
return $user;
}
return false;
}
/**
* Set the current user.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #return $this
*/
public function setUser(AuthenticatableContract $user)
{
$this->user = $user;
$this->loggedOut = false;
$this->fireAuthenticatedEvent($user);
return $this;
}
Both of these methods come from the SessionGuard though. I don't know if Sanctum implements its own version.
I'm trying to create a middleware that redirects users to a verification page if no data is found on the identification table but I keep getting the logic for the if statement wrong
I already tried the $request->user()->identification->()has('user_id) as my if statement
//From my identification middleware, I have this;
<?php
namespace App\Http\Middleware;
use Closure;
class Identification
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (! $request->user()->identification()->verified) {
return redirect('identification');
}
return $next($request);
}
}
I expect that this should return as true and proceed to the next page since i already have data on the Identifications table belonging to this user
but I get this error
ErrorException (E_NOTICE) Undefined property:
Illuminate\Database\Eloquent\Relations\HasOne :: $verified
I'm guessing the problem is your if statement and it should be like this:
if (! $request->user()->identification->verified)
recently i created two middlewares which is one for user called device, and one other for super user which is high level of admin. This is my middleware
Role Device Middleware
<?php
namespace App\Http\Middleware;
use Closure;
class RoleDevice
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if(Auth::check() && Auth::User()->role=='device'){
return $next($request);
}
return redirect()->route('login')->with('danger',"You don't have an access");
}
}
Role Device Super User
<?php
namespace App\Http\Middleware;
use Closure;
use Auth;
use User;
class RoleSuper
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if(Auth::check() && Auth::User()->role=='super'){
return $next($request);
}
return redirect()->route('login')->with('danger',"You don't have an access");
}
}
after i created the middlewares, i put into the routes which is one route could access two middlewares. Here is one of my route.
Route::get('/dashboard','DashboardController#index')->middleware(['rolesuper','roledevice'])->name('dashboard');
and when i try to log in into my website, it returns
You don't have an access
which is don't pass into the middleware.
i hope i get any comments above !
thanks.
Middlewares are executed in the order the are passed. So in case first middleware returns redirect response that's it - second middleware won't be executed.
You could combine both middleware into one and pass available roles as middleware parameter or just create single middleware for this that will verify if user is authorized.
I have a Like button that fires an Ajax Post, this route is protected by auth:api middleware:
myproject/routes/api.php
Route::group(['middleware' => ['auth:api']], function () {
Route::post('/v1/like', 'APIController#set_like');
});
When an authenticated user clicks the like button, no problem at all, everything works smoothly. But when guests click the button, I redirected them to login page via Javascript and after authentication they are redirected to the page specified in RedirectIfAuthenticated middleware, so usually to /home.
I modified that middleware as follows:
myproject/app/Http/Middleware/RedirectIfAuthenticated.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string|null $guard
* #return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
return redirect()->intended('/home');
}
return $next($request);
}
}
My Ajax call is this:
var toggleLike = function(){
var token = USER_TOKEN; //javascript variable
var current_type = $(thisLikeable).attr("data-type");
var current_id = $(thisLikeable).attr("data-id");
var jqxhr = $.post( APP_URL + "/api/v1/like", {api_token: token, type: current_type, id: current_id}, function(data) {
setLikeAppearance(data.message);
})
.fail(function(xhr, status, error){
if (xhr.status == 401) {
window.location = APP_URL + "/login" ;
}
});
};
The problem here's the intended() function, which for Ajax calls is not storing the correct session variable and I am not figuring out how to set it properly.
I am clearly missing something obvious, can anyone help?
Cheers!
EDIT
What I want to achieve is this:
GUEST is in //mysite/blabla
clicks Like button
gets redirected to login
logs in (or register)
gets redirected to //mysite/blabla with the Like already triggered on
What's happening is that in APIs sessions are not managed or in other words it's stateless. So the session middleware is not implemented on Laravel framework for API requests. Though you can manually add, it's not idle to use. So if the API does not use sessions and uses the redirect, fronted does not know about it, as API and frontend work as two separate apps. SO you need to send the frontend the status of the response and let the frontend handle the redirect as you have done with ajax. Just remove the redirect if unauthenticated and let the API throw unauthorized exception. Then, from the handler, handle the unauthorized exception.
Here is how to do it.
Add this to app/Exceptions/Handler.php
/**
* Convert an authentication exception into an unauthenticated response.
*
* #param \Illuminate\Http\Request $request
* #param \Illuminate\Auth\AuthenticationException $exception
* #return \Illuminate\Http\Response
*/
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
return redirect()->guest('login');
}
this will send the user a 401 with message Unauthenticated if the request was json(api request), else(if web request) will redirect to login
check the render method below or check it from source to understand what's happening. when an unauthorized exception is thrown we are telling to check the request type and if it's from an API request, we are sending a json response with 401 status code. So know from frontend we could redirect the user to login page after seeing the 401 status code.
From source
/**
* Render an exception into a response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $e
* #return \Symfony\Component\HttpFoundation\Response
*/
public function render($request, Exception $e)
{
if (method_exists($e, 'render') && $response = $e->render($request)) {
return Router::toResponse($request, $response);
} elseif ($e instanceof Responsable) {
return $e->toResponse($request);
}
$e = $this->prepareException($e);
if ($e instanceof HttpResponseException) {
return $e->getResponse();
} elseif ($e instanceof AuthenticationException) {
return $this->unauthenticated($request, $e);
} elseif ($e instanceof ValidationException) {
return $this->convertValidationExceptionToResponse($e, $request);
}
return $request->expectsJson()
? $this->prepareJsonResponse($request, $e)
: $this->prepareResponse($request, $e);
}
AFTER EDIT
The intended method() is only for web routes as it uses session to extract the intended route or manually passed value. Here is the intended() method.
/**
* Create a new redirect response to the previously intended location.
*
* #param string $default
* #param int $status
* #param array $headers
* #param bool $secure
* #return \Illuminate\Http\RedirectResponse
*/
public function intended($default = '/', $status = 302, $headers = [], $secure = null)
{
$path = $this->session->pull('url.intended', $default);
return $this->to($path, $status, $headers, $secure);
}
To achive the redirect to the page the user is comming from you can
1 - Manually pass some queries with url like
/login?redirect=like(not the url, just a mapping for /comments/like url)&value=true(true is liked, false is unliked)
and handle it manually.
2 - Get and check the query parameters from url
3 - Use to() method to pass the intended url instead of using intended(). here is the to() method. (see 4 to see the recommended way)
/**
* Create a new redirect response to the given path.
*
* #param string $path
* #param int $status
* #param array $headers
* #param bool $secure
* #return \Illuminate\Http\RedirectResponse
*/
public function to($path, $status = 302, $headers = [], $secure = null)
{
return $this->createRedirect($this->generator->to($path, [], $secure), $status, $headers);
}
4 - But, I would recommend sending redirect url (I mean the mapping ex: like) as a response to frontend and let the frontend handle the redirecting. As API redirecting will work if the API is used by websites only. Suppose if you are using this same api for a mobile app, wonder how API redirect will work. It's not a work of API to redirect, unless if it's for things like OAuth Authentication, which would have a redirect url specified.
Remember to sanitize the url params to block XSS like stuff. Better
Send some values and map it to the urls. Like
[
//like is mapping
//comments/like is the actual url
'like' => 'comments/like'
]
Then, get the mapping url from array or use frontend mappings.
You can make changes in your RedirectIfAuthenticated.php to distinguish between Ajax call & normal login like this:
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string|null $guard
* #return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
if ($request->has('api_token')) {
// do whatever you want
return response()->json('some response');
}
return redirect()->intended('/home');
}
return $next($request);
}
}
Update:
Another solution is to remove Auth middleware from your route. In APIController#set_like function manually login user, trigger like & return json response.
If a button is not for a guest, then you shouldn't render it on page.
Instead, you should render a link to login, then if the user logs in you will redirect him back to where he was before. Now, user can see and click the button.
I am using Laravel and sentinel to develop a permission system however it was designed so that the user can select and deselect which permissions the role has from a checkbox form. I have already coded the part where they can assign permissions however I need that the checkboxes that have already been assigned are marked when the user request the page. How do you recommend approaching this? I am using a middleware
<?php
namespace App\Http\Middleware;
use Closure;
use Cartalyst\Sentinel\Laravel\Facades\Sentinel;
class PermissionsMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$user = Sentinel::findById(1);
$permisos = array(array_keys($user['permissions']))
return $next($request);
}
}
However, I don't know how to pass data from the middleware to the view.
I don't think it's recommended using the middleware for this purpose, but if you still want to do it that way you can try using:
View::share ( 'permisos', $permisos );
To share the 'permisos' variable with the view that's coming after the middleware.
So your code is going to look like this:
<?php
namespace App\Http\Middleware;
use Closure;
use Cartalyst\Sentinel\Laravel\Facades\Sentinel;
class PermissionsMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$user = Sentinel::findById(1);
$permisos = array(array_keys($user['permissions']))
View::share ( 'permisos', $permisos );
return $next($request);
}
}