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'?
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);
}
When I call server without headers its working and server returns json:
this.http.get(url)
but when I add header:
var headers = new Headers({'x-id': '1'});
this.http.get(url, {'headers': headers})
browser returns error:
XMLHttpRequest cannot load http://domain/api/v1/. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access.
I also tried add Origin header - browser error: Refused to set unsafe header "Origin"
And Access-Control-Allow-Origin header - no effect
On server (Laravel) I created middleware Cors.php:
<?php
namespace App\Http\Middleware;
use Closure;
class Cors {
public function handle($request, Closure $next)
{
return $next($request)
->header('Access-Control-Allow-Origin', 'http://localhost:3000')
->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
->header('Access-Control-Allow-Headers', 'x-id');
}
}
Im new to angular2 and CORS requests, dont know what to do.
Angular: 2.0.0-beta.0
Laravel: 5.0
Browser: Google Chrome
A preflighted request with CORS means that an OPTIONS HTTP request is executed before the actual one. You switch from a simple request to the one since you add a custom header in the case of a GET method. This link could help you to understand what happens: http://restlet.com/blog/2015/12/15/understanding-and-using-cors/.
FYI the Origin header is automatically added by the browser when executing a cross domain request.
I think your problem is within the Access-Control-Allow-Origin header. You must set the host that makes the call and not the address of the server. You should have this instead (if your Angular2 application is running on localhost:8080):
return $next($request)
->header('Access-Control-Allow-Origin', 'http://localhost:8080')
->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
->header('Access-Control-Allow-Headers', 'x-id');
Hope it helps you,
Thierry
I had the same issue in my Angular2 application.
The problem, as already stated, is that before every request made by the client a preflight request is sent to the server.
This kind of request have a type OPTIONS, and it's duty of the server to send back a preflight response with status 200 and headers set for accepting requests from that client.
This is my solution (with express):
// Domain you wish to allow
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'YOUR-CUSTOM-HEADERS-HERE');
// Set to true if you need the website to include cookies in requests
res.setHeader('Access-Control-Allow-Credentials', true);
// Check if Preflight Request
if (req.method === 'OPTIONS') {
res.status(200);
res.end();
}
else {
// Pass to next layer of middleware
next();
}
In this way, the client will be authorized and you will be able to set your custom headers in all the requests. In your case, you'll have to set x-id in the Access-Control-Allow-Headers option.
In your case the server has to respond to the preflight request with following headers:
Access-Control-Allow-Origin: *
X-Custom-HeaderAccess-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: x-id
Note that for the Access-Control-Allow-Origin HTTP header it's best practice to set the domain where the angular2 app is hosted explicitly instead of the * which is only necessary for public API's where you don't control all consumers!
I highly recommend you to read following article about CORS:
http://www.html5rocks.com/en/tutorials/cors/
I have been developing an angular2 app with a c# web api back end and ran into an issue with the cors settings as well.
The real problem turned out not to be the cors settings, rather the data that was being sent to the api (js date object to c# datetime type didn't line up correctly). However in your case, is it absolutely essential to pass the x-id in the header rather than as a parameter on the request? For example you could do somthing like this:
getYourDataType(id: number): Observable<YourDataType> {
return this.http.get(url + '/' + id.toString())
.map((response: Response) => <YourDataType>response.json());
From my research after dealing with this issue it seems like angular would try to find the endpoint you were trying to hit, fail to find it and then assume that the reason must be something wrong with the cors settings, rather than the client side data.
You said that the api returns data when you don't set any headers on your request, so if changing from a header to a parameter doesn't work, you could try inspecting the request with a tool like fiddler or the network tab of chrome's dev tools and see if angular constructed each request as you expect it should have. Or comparing the results by manually constructing a request in fiddler, it could be very illuminating as to what exactly is happening to give you this unexpected response.
At any rate figuring out that there is an issue with your data, rather than the cors settings is quite difficult. Angular has you looking at the totally wrong thing trying to figure out a very simple problem. I hope this helps somebody.
the problem is that you also need to set the allowed headers. That's why it's not working. To make it work with a simple angular2 http request, you need to add the following headers:
header('Access-Control-Allow-Origin: *');
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept"); // In your case also add x-id or what you are also using.
header('Access-Control-Allow-Methods: POST, PUT, DELETE, GET, OPTIONS');
Try following code, This should work
let headers = new Headers();
headers.append('Accept', 'application/json');
headers.append('Content-Type', 'application/json');
headers.append('x-id','1');
this.http.get(url, {'headers': headers}).subscribe(
response => {...},
error => {...}
);