Laravel passport custom error message and status code when user unauthorized - laravel

my project is running on Laravel 5.4 and I use passport to make authentication via api with bearer token. everything works fine, but when unauthorized user tries to reach resource that require authentication, the user gets error message 405 method not allowed
but I want response to be 401 unauthorized .
how can change this, and send only response with message, instead of exception? I did research, but couldn't find anything. I use standard laravel middleware for authorization auth:api. my routes grouped in middleware
Route::group(['middleware' => 'auth:api'], function () {
// these routes should return 401 if user not authenticated
}

Well method not allowed exception happens because you are hitting the wrong endpoint. You are posting to a get or vice verca.
However you can modify your exceptions if you go to App\Exception open up handler.php in render() method there you can adjust exceptions as you want example:
public function render($request, Exception $exception)
{
if ($exception instanceof \Illuminate\Auth\AuthenticationException) {
return response('unauthorized', 401);
}
return parent::render($request, $exception);
}
On handler() method just check if $exception is instance of any exception object, if so you can modify the response as you want. For laravel exceptions follow link

Related

Pass request to exception

Since I started logging exceptions on a production site I'm noticing a lot of them, especially 404s, more than I would expect for a site with barely any traffic, and I'd like to get to the bottom of whether they're genuine users or just bots. To help with this, I want to capture the URL that the user was trying to visit before being redirected to the 404 route, so I can keep track of which non-existent routes are being mistakenly hit. I think I'm correct in assuming this URL should be available in the request, and that I just need to store the request and pass it through to the exception.
What's the best way to do this in Laravel 8 onwards?
Catch 404 exceptions in Handler(App\Exceptions\Handler).
If you see Rendering Exceptions
By default, the Laravel exception handler will convert exceptions into
an HTTP response for you. However, you are free to register a custom
rendering closure for exceptions of a given type. You may accomplish
this via the renderable method of your exception handler.
The closure passed to the renderable method should return an instance
of Illuminate\Http\Response, which may be generated via the response
helper. Laravel will deduce what type of exception the closure renders
by examining the type-hint of the closure:
so in the register method,call renderable
public function register()
{
$this->renderable(function (NotFoundHttpException $e, $request) {
Log::alert("404",[
"fullUrl"=>$request->fullUrl(),
"path"=>$request->path(),
"message" =>$e->getMessage()
]);
return response()->view('errors.404', [], $e->getStatusCode());
});
}
Also, don't forget to import and use
Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
EDIT: just a few hours of using this solution in production, with the benefit of the newly-added request URLs, has confirmed for me just how many of these HTTP 4xx errors are junk - mostly automated bots and maybe a few script kiddies trying common routes. For this reason I've added some logic to ignore 404 and 405 errors, and may still add others that mostly contribute noise to the logfile.
This was harder than it should have been, but this is the solution I'm currently using to log the request with all exceptions. It's probably not the cleanest way to do it, but it works perfectly for my needs. Thanks to John Lobo's answer for pointing me in the right direction.
It works by inspecting each instance of the Exception class and using PHP's instanceof to check whether it's a HTTP exception or not. If it is, it gets logged with the request URL and returns a view with a status code. If it's a generic non-HTTP exception, it gets logged with the request URL and returns another view with no status code (or you can keep the default exception behaviour by removing the return block, which renders a blank screen in production).
public function register()
{
$this->renderable(function (Exception $exception, $request) {
$url = $request->fullUrl();
if ($exception instanceof HttpException) {
$status = $exception->getStatusCode();
// Do not log HTTP 404 and 405s errors for reasons that will
// become apparent after a few hours of logging 404s and 405s
if(($status !== 404) && ($status !== 405)) {
Log::warning("Error $status occurred when trying to visit $url. Received the following message: " . $exception->getMessage());
}
return response()->view("errors.error", [
"exception" => $exception,
"status" => $status
],
$status
);
} else {
$status = $exception->getCode();
Log::warning("Exception $status occurred when trying to visit $url. Received the following message: " . $exception->getMessage());
return response()->view("errors.exception", [
"exception" => $exception,
"status" => $status
]);
}
});
// Optionally suppress all Laravel's default logging for exceptions, so only your own logs go to the logfile
$this->reportable(function (Exception $e) {
})->stop();
}

Route type delete does not work in Laravel

I have following route and works
Route::post("delete-role", [RoleApiController::class, "Remove"]);
I tested it through postman like this
http://localhost/delete-role?api_token=hcvhjbhjb12khjbjhc876
Now, if I change above route and convert to type delete..
Route::delete("delete-role/{role_id}", [RoleApiController::class, "Remove"]);
it does not work. I get below error. It seems to be the reason that the api_token is missing.
I get same error when trying to update route like below
Route::delete("delete-role/{role_id}/{api_token}", [RoleApiController::class, "Remove"]);
You have to set header of your request as:
"Accept": "application/json"
in postman.
If you don't set the required header for api, Laravel Passport can't understand request as an API client and so it will redirect to a /login page for the web.
Or you can set a middleware to check it in code:
public function handle($request, Closure $next)
{
if(!in_array($request->headers->get('accept'), ['application/json', 'Application/Json']))
return response()->json(['message' => 'Unauthenticated.'], 401);
return $next($request);
}
You have an incomplete details. but I see few issues here.
You seem to be using web routes for your API requests which is a bad set-up
You do not have a route with login name.
based on the error you posted, your request seems to successfully destroyed the token and logged you out, then called the middleware App\Http\Middleware\Authenticate which supposed to redirect your request to login route which does not exist and the reason you are getting that error.
You can see from that Authenticate middleware it will supposed to redirect you to login route for unauthenticated request. thats why you need to use the api routes so you can handle the response manually
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* #param \Illuminate\Http\Request $request
* #return string|null
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
}
Also, I'm not so sure about this, but the reason you are not getting the same issue with your POST request is probably because your POST request does not call the Authenticate middleware or whatever in your routes file or class function that calls the authenticate middleware.
But again, just use api routes if you don't want un-authenticated request to automatically redirect your request to login routes which does not exist in your application
The problem is that he doesn't define route ('login'),
add in Exceptions/Handler.php
$this->renderable(function (AuthenticationException $e, $request) {
if ($request->is('api/*')) {
return response()->json(['meassge' => 'Unauthenticated']);
}
});
Then you should use Passport Or Sanctum for auth with Api,
Continue from here https://laravel.com/docs/9.x/passport
Probably, this thread could help you. Route [login] not defined
(OR)
You need to setup auth scaffolding to get login route defined.
Important: your project will be overridden if you setup auth scaffold.
So, I would only recommend doing this if it is a new project or a testing app.
See this for detail doc but install Laravel Breeze would be suffice.
It Appears you have called route('login') without having defined it in your routes, Please remove route('login') from your code or define it in your routes. eg::
Route::get('login', [YourController::class, 'methodName'])->name('login');

Laravel Spatie MultiTenanct noCurrentTenant Exception Handle

I'm making a multitenancy system and I'm facing problem when trying to handle the NoCurrentTenant Exception..
I'm using spatie multitenancy
I'm expecting that when there is NoCurrentTenant exception is thrown , it should redirect to login route. But that is not happening.
Below is my Exception Hander register method.
public function register() {
$this->reportable(function (NoCurrentTenant $e) {
return redirect('/saml2/laraveltestidp/login');
});
}
But even after multiple tries it still shows below error only
Spatie\Multitenancy\Exceptions\NoCurrentTenant
The request expected a current tenant but none was set.

Laravel Policy with Spatie Permission check gives 403 for client credentials API request

I'm using Laravel Policy and checking for permissions created using Spatie's Laravel-Permissions package.
For an API call with client credentials, the authorizeResource() in the Controller constructor returns 403. If this is removed, it returns the expected results.
NpoPolicy.php
public function view(User $user, Npo $npo)
{
return $user->can('npo.view');
}
NpoController.php
public function __construct()
{
$this->authorizeResource(Npo::class);
}
api.php
Route::middleware('client')->resource('/npo', 'NpoController');
API Request
URL: https://my-app.dev/api/npo/1
Method: GET
When I comment out the authorizeResource method in the controller constructor, I get the result as expected:
{
"npos": {
"id":1,
"name":"Bailey and Sons",
"contact_person_name":"Mr. Davion Mayert",
"created_at":"2019-06-13 17:39:25",
"updated_at":"2019-06-13 17:39:25"
}
}
I'm aware that a Laravel policy requires a User model object and that is why the policy is returning 403 response in my case. Is there a general practice to handle API requests (with client credentials) in these cases?
You have missed the second parameter at authorizeResource function so, at the NpoController.php change the authorizeResource to:
$this->authorizeResource(Npo::class, 'npo');

prevent Lumen from showing default 500 Internal Server Error on API responses

I have a Route defined as:
$app->post('api/v1/Subject','SubjectController#createSubject');
And in the Controller I have the following code:
public function createSubject(Request $request){
$Subject = Subject::create($request->all());
return response()->json($Subject);
}
Now, when someone sends incorrect data, it triggers a Query Exception - "SQLSTATE[23000]: Integrity constraint violation:" which is understood.
However, what I want is: I want do not want Lumen to send its own default Error Page in API Response. I want to capture this error event and send my own customized response. How can I do that?
Since I could not find a solution, I tried to add my own view at: /resources/views/errors/500.blade.php but Lumen is even ignoring this template. Please help. Ideally, I would want to capture this error event and send my own customized response.
EDIT:
Lumen was reporting two exceptions - PDOException and QueryException at the 500 error response. So, to get a custom error message, I put the following code in side function render() in app\Exceptions\Handler:
public function render($request, Exception $e)
{
if($e instanceof PDOException){
return response('It is my Custom response for PDOException that caused 500 error response.');
}
if($e instanceof QueryException){
return response('It is my Custom response for QueryException that cuased 500 error response.');
}
return parent::render($request, $e);
}

Resources