I use Dingo with Laravel 5.1 to create simple API.
So at route.php I have:
$api = app('Dingo\Api\Routing\Router');
$api->version('v1', function($api) {
$api->get('getvoucher', 'App\Http\Controllers\BitemsController#index');
$api->get('update/{key}', 'App\Http\Controllers\BitemsController#update');
$api->post('store', 'App\Http\Controllers\BitemsController#store');
$api->post('authenticate', 'App\Http\Controllers\AuthenticateController#authenticate');
$api->post('logout', 'App\Http\Controllers\AuthenticateController#logout');
$api->get('token', 'App\Http\Controllers\AuthenticateController#getToken');
});
and my BitemsController is:
public function index(Request $request)
{
$bitem = Bitem::where('key',$request->key)->where('id',$request->pin)->first();
return $bitem;
}
public function store(Request $request)
{
$bitem = new Bitem($request->all());
$bitem->save;
return $bitem;
}
Now I use POSTMAN application to test the API, and when I send GET to localhost:8888/api/getvoucher everything is fine, but when I make POST request to store some data then I got error:
"message": "500 Internal Server Error",
"status_code": 500,
"debug": {
"line": 53,
"file": "C:\\wamp\\www\\dine\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken.php",
"class": "Illuminate\\Session\\TokenMismatchException",
"trace": [
POSTMAN:
To fix the problem I try to add:
protected $except = [
'api/*',
];
inside middleware VerifyCsrfToken.php but wnt work.
Please tell me how to solve my problem...
For Postman to work, you need to either send the correct CSRF header, or remove the need for it on your routes.
I'm assuming based on your screenshot your Dingo API routes are using API_PREFIX=api in your .env file.
Check the Laravel documentation on CSRF tokens for more information about those. The gist that #BM2ilabs suggested has some basics on how to find out what CSRF token you're using for local testing in your session to put into Postman.
If you don't want to use CSRF protection, you are correct in using the $except property on the VerifyCsrfToken middleware as per the Laravel documentation - this has also come up on Stack Overflow before. Tricky to troubleshoot that without seeing your Kernel and the full middleware file you're using. If the $except property really isn't working for you, you can always override the VerifyCsrfToken::handle() method as per this post and add whatever route checks you like:
public function handle($request, Closure $next)
{
if ( ! $request->is('api/*'))
{
return parent::handle($request, $next);
}
return $next($request);
}
If you are only creating an API that is going to be stateless and not need CSRF protection, you could just comment out the usage of the VerifyCsrfToken middleware in your Kernel entirely (and possibly some of the other session middleware), although I would recommend using some kind of authentication/validation that your visitor should be allowed to access the API endpoint.
You just need to add csrf_token to the post , but that might be tricky with postman
in laravel add the header with what you use
For example Axios :
it already has that integrated
jQuery Ajax
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
for more info's
Update
After some searching i found this article that show how to make csrf work with POSTMANas well
Gists of #ethanstenis
Related
I see quite a few people having a similar issue with this, but no final resolved solutions. I have been trying to get this working for about 24 hours now and still no luck!
Goals
Build and API using Laravel 6 and Dingo API
Be able to consume the API externally, authenticating with Passport oAuth.
Be able to consume the API internally, via ajax, using passports self-authenticating feature.
Be able to consume the API internally, with PHP, using dingo's self-consuming methods.
What I have found out so far
Auth provider order
Most solutions I have seen suggest setting up both the passport auth and dingo alongside one another. This is auth:api (passport) and api.auth (dingo).
// API route middleware
$api->group(['middleware' => 'auth:api', 'api.auth'], function (Router $api) {
...
The api.auth here is actually a custom auth provider setup in laravel and configured to dingo, which bridges the passport logic into dingo.
// Auth provider
class DingoPassportAuthProvider extends Authorization
{
protected $guard;
public function __construct(AuthManager $auth)
{
dump('DingoPassportAuthProvider Instantiated');
$this->guard = $auth->guard('api');
}
public function authenticate(Request $request, Route $route)
{
if ($this->guard->check()) {
return $this->guard->user();
}
throw new UnauthorizedHttpException('Not authenticated via Passport.');
}
public function getAuthorizationMethod()
{
return 'Bearer';
}
}
// Configured in dingo (Api.php)
'auth' => [
'passport' => \App\Providers\DingoPassportAuthProvider::class,
],
If we put the dingo API provider first in the middleware stack we get:
Internal API requests work IF you specify the user for the call with the be() method: $this->api->be($request->user())->get('/api/profile')
External API requests and internal AJAX requests authenticate correctly and the user is returned from the custom dingo auth provider, however, for some reason you cannot then access this user from within the API controllers: $user = $request->user(); // null
If we put the Passport API provider first in the middleware stack we get:
Internal API requests do not work at all (401 always returned)
External API requests and internal AJAX requests work as intended.
The authenticate method on the dingo passport provider is no longer called. I think this may have something to do with the 401 returned on internal calls.
I believe the correct way around, is to put the passport authentication first. This way, we authenticate the user before calling the dingo authentication, resulting in 2 things:
Passport works natively as expected.
Dingo internal API calls should now just be able to be called with $this->api->get('/api/profile') (omit defining the user with be()), however this does not work.
At the moment I have the previous configuration. Passport works as intended for external and ajax calls, but the internal dingo calls always return 401.
There are a few boilerplate templates I have checked out and they do not seem to do anything different. I wonder if something changed in L6 to explain why the internal requests do not work.
I have found one work around for now, which gets most of the way there...
Within the custom dingo auth provider:
class DingoPassportAuthProvider extends Authorization
{
public function authenticate(Request $request, Route $route)
{
if (Auth::guard('web')->check()) {
return Auth::guard('web')->user();
}
if (Auth::guard('api')->check()) {
$user = Auth::guard('api')->user();
Passport::actingAs($user);
return $user;
}
throw new UnauthorizedHttpException('Not authenticated via Passport.');
}
public function getAuthorizationMethod()
{
return 'Bearer';
}
}
This now checks to see if the request is coming from either the web guard (internal request) or the api guard (external or ajax request) and returns the correct user.
For the api guard, there seems to be an issue that the user is authenticated but not actually available within the controllers. To get around this I added the Passport::actingAs($user). It is probably not best practice, but the guards are now acting as they should and as are all my different scenarios.
Then in the API route middleware, we only specify the custom dingo provider.
// API route middleware
$api->group(['middleware' => 'api.auth'], function (Router $api) {
...
One thing to note with this, is dingos be() method does not work quite as expected. Instead you need to switch the user as you would in a general laravel app.
\Auth::loginUsingId(2);
$user = $this->api->get('/api/profile');
I'm working on a personal project using Laravel 5.6 and Axios library (standard Laravel 5.6 package).
I need to send first GET then POST request using Laravel's API and axios' http requests, but I'm not using Passport or any library like that since it's an internal API serving only VueJS to obtain stuff from the database.
If I set my API routes using auth:api middleware, I always get unauthorized response, whichever is the request type, this is the error output :
Error: Request failed with status code 401
The message :
message: "Unauthenticated."
But, as I read in the documentation, axios headers are set inside laravel's bootstrap.js to read the authorization token from meta tags (and the code is there) :
window.axios = require('axios');
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
// further down the file...
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
While, if needed, here's the http request code :
axios.post('/api/latest', formData)
So, why am I unauthenticated if those are set?
I've tried removing the auth:api middleware and, of course, it's working:
- Route::middleware('auth:api')->get('/latest', 'InternalApiController#latestEp');
+ Route::get('/latest', 'InternalApiController#latestEp');
What am I doing wrong?
I'm not using Passort or any library like that since it's an internal API serving only VueJS to obtain stuff from the database.
If the API is not stateless, meaning that the user is known to be logged in with a standard session cookie, then you can just use the default 'web' middleware for the API routes.
In the default RouteServiceProvider, change the mapApiRoutes function to use the web middleware instead:
protected function mapApiRoutes()
{
Route::prefix('api')
// ->middleware('api')
->middleware('web')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
That being said, you should really put the API routes behind the default 'auth' middleware since they're not throttled by default.
In the routes/api.php file:
Route::group(['middleware' => 'auth'], function() {
Route::get('/latest', 'InternalApiController#latest');
});
And if you want to ensure it's an AJAX request, you can create a simple middleware that checks that the request has the X-Requested-With header set to XMLHttpRequest.
class RequestIsAjax
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (!$request->ajax()) {
return redirect()->route('login.index');
}
return $next($request);
}
}
And register it within the $routeMiddleware array inside the \App\Http\Kernel class.
protected $routeMiddleware = [
'ajax' => \App\Http\Middleware\RequestIsAjax::class,
With Laravel 8, I was getting a 401 error when trying to request something from the backend api. Following the accepted answer got me close, but it still didn't work. I believe this was because I have the dashboard on a subdomain as users.mydomain.tld and where I was using Axios was on the primary domain as mydomain.tld/some/path. The issue is the path for the session cookies. When re-configuring this, you need to clear all the cookies (dev toolbar). Then re-login like normal after you have made your changes. Else, the cookies will be messed up and you won't see it fixed or you wont be able to login at all until you clear them.
I reverted all the changes I made (following the accepted answer) and pinned down the SESSION_DOMAIN environment variable being the key ingredient, at least when it comes to the main user's area being on a sub-domain.
In your .env file:
SESSION_DOMAIN=".yourdomain.tld"
In app/Providers/RouteServiceProvider.php change api to web in the boot() method:
Route::prefix('api')
//->middleware('api')
->middleware('web')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
In routes/api.php:
Route::middleware('auth')->get('/user', function (Request $request) {
return $request->user();
});
In your resources/js/bootstrap.js:
window.axios.defaults.withCredentials = true;
After modifying the bootstrap.js file, you will need to run npm run dev again to recompile.
Now, you should be able to use Axios in your frontend JavaScript. Whether it be jQuery or Vue, whatever you are using.
axios.get('/api/user')
.then(response => console.dir(response.data))
.catch(error => console.log(error));
CSRF-token is not the same as an authorization token. Most likely you will manually need to add the authorization token, which is probably like this, depending on your method of authorization.
I am building a REST user-microservice using Laravel 5.5 + Passport.
I am using the standard Passport::routes(), but I have had to modify the Auth::routes in order to make them return JSON responses, and to make them work with Passport.
I have added the following lines to my routes/web.php file:
Route::group(['middleware' => 'auth:api'], function () {
$this->post('logout', 'Auth\LoginController#logout')->name('logout');
});
This allows me to POST https://myapi/logout
If I make the call with the header "Authorization => Bearer TOKEN", I get a successful logout response.
If I provide no header at all, I get a "not authenticated" message (which is good)
However, if I provide the header with a revoked token, I get a recursive deadloop of the function: Illuminate\Auth\RequestGuard->user() (it keeps calling itself recursively until stack-overflow)
This is all done in the auth:api middleware, my logout code is not reached, but my LoginController constructor is called. Constructor code:
public function __construct(Application $app)
{
$this->apiConsumer = $app->make('apiconsumer');
$this->middleware('guest')
->except('logout');
}
I'm struggling to understand if it's my code causing this issue, or some combination of Laravel + passport + auth.
My first thought was that the auth:api middleware fails to authenticate the user, and as a result redirects the user to /home, where for some reason it's triggered again, recursively. But if that was the case, why would it work correctly with no header?
My current thinking is that the token in question does exist in the database, but Laravel is failing to figure out that it's revoked.
Any suggestions appreciated,
I found an answer (if not the answer) after a lot of research. It appears this is a Laravel bug (https://github.com/laravel/passport/issues/440). The solution is to add OAuthServerException to the $dontReport array in app/Exceptions/Handler.php:
class Handler extends ExceptionHandler
{
protected $dontReport = [
...
\League\OAuth2\Server\Exception\OAuthServerException::class,
];
}
This will avoid trying to log user information, thereby avoid the deadloop.
I have faced this in localhost. in my case, I have used xampp server and facing this issue
after creating a virtual host like "testlarave.test" then solve the error
I am trying to create a RESTful API by using Laravel. I have created my controller using php artisan make:controller RestController and this is my controller code:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class RestController extends Controller
{
private $arr = array(
array("name"=>"jon", "family"=>"doe"),
array("name"=>"jhon", "family" => "doue")
);
public function index(){
return json_encode($this->arr);
}
public function store(Request $request){
return "oops!!";
}
public function update (Request $request, $id){
return "test";
}
}
I have added this line of code to create this route in my routes/web.php file:
Route::resource('person', 'RestController');
When I try to test this api on GET /person it works fine but on POST and PUT I am getting a 419 status code from Laravel.
If you are developing REST APIs, you better not add tokens. If you are using 5.4 or 5.5 you can use api.php instead of web.php. In api.php you don't need token verification on post requests.
If you are using web.php, then you can exclude routes that you don't want to validate with CSRF Tokens.
Here is the official documentation:
Excluding URIs From CSRF Protection
Sometimes you may wish to exclude a set of URIs from CSRF protection. For example, if you are using Stripe to process payments and are utilizing their webhook system, you will need to exclude your Stripe webhook handler route from CSRF protection since Stripe will not know what CSRF token to send to your routes.
Typically, you should place these kinds of routes outside of the web middleware group that the RouteServiceProvider applies to all routes in the routes/web.php file. However, you may also exclude the routes by adding their URIs to the $except property of the VerifyCsrfToken middleware:
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier
{
/**
* The URIs that should be excluded from CSRF verification.
*
* #var array
*/
protected $except = [
'stripe/*',
'http://example.com/foo/bar',
'http://example.com/foo/*',
];
}
For reference https://laravel.com/docs/5.5/csrf
As per my Knowledge there are two methods to solve this
Method 1: Add CsrF Token
Method 2: Exclude URIs from CSRF protection
How to use
Method 1: Add one more variable to your POST request
_token: "{{ csrf_token() }}"
Example for Ajax
req = $.ajax({
type: "POST",
url: "/search",
data: {
"key": "value",
_token: "{{ csrf_token() }}",
},
dataType: "text",
success: function(msg) {
// ...
}
});
Example if you using forms
<input type="hidden" name="_token" id="token" value="{{ csrf_token() }}">
Method 2: There is a file named VerifyCsrfToken in following location
yourProjectDirectory/app/Http/Middleware
Add your URL in following method
protected $except = [
'url1/',
'url2/',
];
When To use
If you are the owner(full control) of API, use Method 1, as CSRF Token adds security to your application.
If you are unable to add CSRF Token like in case if you are using any third party API's, webhooks etc., then go for Method 2.
This can solve by excluding csrf protection of specific route you want to.
Inside your middleware folder, edit the file called VerifyCsrfToken.php
protected $except = [
'http://127.0.0.1:8000/person/'
];
I solved this problem by changing my server cache setting.
You can disable all of your caching systems (Nginx, Cloudflare, ...) to check it and then
turn it on by applying QueryString + Cookie to prevent caching a page with old csrf token in it.
I had the same issue when did POST requests to a Laravel API.
I solved the issue sending Accept: application/json in the headers request.
I've tried to exclude requests from another localhost server (http://localhost:8080/order/placeorder) to another one localhost server (http://localhost:8000)
I don't want to disable all csrf protection by removing
\App\Http\Middleware\VerifyCsrfToken::class in Illuminate\Foundation\Http\Kernel.php
I've tried to modify app/Http/Middleware/VerifyCsrfToken.php
protected $except = [
'http://localhost:8080/*',
'http://localhost:8080',
'/order/placeorder/*',
'http://localhost:8080/order/placeorder'
];
and I also tried this way
private $openRoutes = [
'http://localhost:8080/*',
'http://localhost:8080',
'/order/placeorder/*',
'http://localhost:8080/order/placeorder'
];
public function handle($request, Closure $next)
{
//add this condition
foreach($this->openRoutes as $route) {
if ($request->is($route)) {
return $next($request);
}
}
return parent::handle($request, $next);
}
But I still got this error
TokenMismatchException in VerifyCsrfToken.php
Can anyone suggest me what should I do and what I've done wrong?
The exceptions are routes within your own application that are excluded, not the URLs of servers that are requesting it. You will never put localhost, http, or any domain in these exceptions in normal circumstances. If you wish for a request by an external server to be accepted, I would disable CSRF protection for the routes it is accessing (because you want a cross-site request, that's what CSRF prevents).
For example, if you want any external server to be able to send a POST request to /order/placeorder, you would simply add that route to the exclusion. You also need to add any other route you want it to be able to access. If there are a lot, there are other more manageable ways to do this with middleware as well.
To authenticate the server making the request, it should send a token to verify itself. You can create a static token for this purpose (like an API key), or possibly use an OAuth implementation of some sort with access/refresh tokens - there is a package for Laravel for this that makes it easy.