There are web and api route files. Web Routes are guarded by auth and the unauthenticated users are getting redirected to login page. But my requirement is in api route file. Unauthenticated api routes should get a custom json response instead of redirection to login page.
I am new to passport feature. And I cannot change the Web routes because its made by some other developer.
When a laravel request is not authorized (aka 'unauthenticated api routes') laravel throws the same exception regardless of it being a web or api route.
However, when the exception is handled/rendered by the application the response is determined by $request->expectsJson(), if its true it returns it as json, if not it will act as if it is a web route.
The above function checks multiple function, and returns true if any number of these is correct:
header X-Requested-With === XMLHttpRequest
header Content-Type contains application/json
header Content-Type contains a wildcard (*/*)
header Accept contains application/json
Ensure that your api request matches any of the above described headers, and in that case should return the authentication error as json.
To enforce that api routes always throw json authentication errors you could do any of these:
override the request header in a service provider if the route is determined to be an api route. (do not try this in middleware, it might not work)
override the exception handler in a same manner as described above
and of course other scenarios are also possible
I updated render method of app/Exception/Handler.php method to as follows and it worked.
{
if($exception instanceof \Illuminate\Auth\AuthenticationException && $request->is('api/v*'))
{
return Response(['success'=>-1,'statuscode'=>401,'msg'=>'Sorry, your account has been logged in from another device.'],401);
}
return parent::render($request, $exception);
}
Related
I'm using a SpringBoot 2 (2.7.0) application (including Spring security 5.7.1) to secure REST endpoints with Keycloak for authentication and authorization. Everything works fine but the only thing which bothers me is when I don't set the bearer token I get a HTTP 400 response. The response itself is correct but the body of the response contains HTML (Keycloak login page).
Is there a way to avoid that the body of the response contains the login page? I would like to set a custom response body.
That is an expected default behavior. If you want to instead get relevant 4xx error instead, you can try setting the the "bearer-only" in your "keycloak.json" file so that it would not redirect API calls (i.e. AJAX calls from browser) to the login page:
{
...
"bearer-only": true
}
what i got:
created a new jetstream project (inertia) and Features::api() enabled in jetstream.php config,
web.php:
Route::middleware(['auth:sanctum', 'verified'])->get('/testweb', function () {
return "test web called";
})->name('testweb');
api.php:
Route::get('/testapi', function(){
return 'api called';
})->middleware('auth:sanctum');
also i created a test API token
now when I call /testweb in the browser and I am logged in I get "test web called"
when I am logged out and call it I get redirected to login view
when I make the API request WITH the token
I get the expected result "api called"
BUT
when I don't add a token to the request
I don't get a 401 or so but I get a 200 with an "empty" view (with livewire i see it is the loginview, so i think with inertia it is the loginview too)
what is the cause o that? do i have to handle it myself? if yes, where and how??
additional note:
I made the API request with POSTMAN, does it differ if I do not set the header as Accept: application/json?
When the request is made with that head included
Accept: application/json
then the Authenticate Middleware will know what to do and decide
if it will redirect it or
just send back a 401 response.
I'm new to Laravel and still exploring.
Is there a way to differentiate if calls made to an API is from a REST client like Postman or from GUI?
I earlier did something like this which worked -
if ($request->is('api/*'))
but later I had to remove the "api" prefix from the URL and so now I have no way of differentiating the calls.
The URL to call my api - http://localhost/myprojectname/someAPI
What I'm trying to achieve is return a custom error in Authenticate.php middleware if call is made from API.
My earlier code
protected function redirectTo($request)
{
if ($request->is('api/*')) {
//return custom message
}
if (! $request->expectsJson()) {
return route('login');
}
}
but now that the "api" prefix is removed from URL, I'm not sure how to differentiate.
You could differentiate between them by calling the request()->header() method.
It determines the user-agent that sent the request is it postman or any other browser.
Another useful things that it returns the accept parameter that determine the way the sender want to receive the result, for example when you send this request from an HTTP client like axios it returns application/json which means it a REST client that expect to receive the result as a json response. While the GUI browser returns */* which means it's prepared to receive any kind of response.
Note: by default the postman client returns */*
Context
Using Postman I send a PUT HTTP request to my Laravel API.
Expected behavor
Postman should show "test" as a response.
Actual behavior
Postman shows the error "Parse Error: The response has a duplicate "Content-Length" header" as a response.
The route
I have defined in api.php the following route:
Route::put('/test', function(Request $request) {
return 'test';
})->name('test');
This route exists: indeed, php artisan route:list returns the following...
PUT | api/test | test | Closure | api
The request
In Postman I have defined the following request (the URL is in the shape of: https://<laravel site>/api/test):
What I've tried to do
According to the Laravel docs, _method = PUT should be sent when a HTML form pointing to a PUT route is sent with POST because it's incompatible with PUT. Normally here, since I use JSON, it's not the case. But even though it could be useless, I've put it, in case I was wrong.
Moreover I've explicitly specified application/json as value for Content-Type and Accept. Also I've specified XMLHttpRequest for make the server know it's an XHRequest.
Finally, there is the CSRF token and the Sanctum token (Bearer Token). I don't know if their values are correct but thanks to other requests not shown here, I know I'm authenticated using Fortify so normally the Sanctum token value may not be used (because of the standard authentication cookie session is defined by Fortify) ; for the CSRF token maybe it's the same.
Question
Why doesn't Laravel simply show "test"?
Clues
NGinx configuration could be the root cause of this problem. Digging deeper...
Just because I can't see the url. Do you have '/api/test'?
I need to send an XMLHttpRequest header with all my requests in order to get a json response
Is it possbile to have this as the default behavior for all api routes?
EDIT:
laravel automatically redirects to the home route when a request fails (e.g. request validation error).
However, when I define the X-Requested-With: XMLHttpRequest header I receive a json response telling me what went wrong.
Since all my endpoints under /api are json specific I would like to default to this behavior without having to define the header.
You could do this with a "before" middleware, using the middleware to inject the X-Requested-With header into the request.
Create app/Http/Middleware/ForceXmlHttpRequest.php:
namespace App\Http\Middleware;
use Closure;
class ForceXmlHttpRequest
{
public function handle($request, Closure $next)
{
$request->headers->set('X-Requested-With', 'XMLHttpRequest');
return $next($request);
}
}
Apply the middleware to your api middleware group. Edit app/Http/Kernel.php:
'api' => [
'throttle:60,1',
'bindings',
\App\Http\Middleware\ForceXmlHttpRequest::class,
],
This does, of course, take control away from the requester. As far as the framework is concerned, every request made to the api middleware group will be treated as an ajax request, and there will be no way for the requester to say otherwise. Just something to keep in mind.
NB: untested.