I am starting work on an API in Laravel, using Postman. However, no matter what I send via Postman, the $request received is empty.
Route in Laravel:
Route::group(['namespace' => 'Api', 'as' => 'api.'], function() {
Route::post('/stock-list', 'DataController#stock_list');
});
Controller method:
public function stock_list(Request $request)
{
logger()->info($request->all());
return response()->json([
'request' => $request
]);
}
The log shows that $request->all() is an empty array. Since I am also returning $request to Postman as a test, this is what Postman gets as a response:
{
"request": {
"attributes": {},
"request": {},
"query": {},
"server": {},
"files": {},
"cookies": {},
"headers": {}
}
}
What am I sending? In Postman, I have this content in the body tab:
{
'foo' : 'bar'
}
The body type is selected as raw and Postman reads it as JSON.
In terms of authentication, I have chosen basic auth with username and password for now, however I have not done anything on the Laravel side about this (as you can see from the route and controller). As far as I can tell, this shouldn't matter at this point - the request should still be received by Laravel correctly, then either an "unauthenticated" response should be returned, or no authentication should happen at all since there is no auth middleware on the route.
Can someone explain the cause of the problem and suggest a solution?
EDIT
Here are the headers in Postman:
{
'foo' : 'bar'
}
is not a valid JSON. By standard you have to use double quotes.
{
"foo" : "bar"
}
You already set content-type to json which is good. I believe it should be working now.
I did not understand why you used logger()->info($request->all()); in your code but I tried your code like bellow and it worked
route:
Route::group(['prefix' => 'Api'], function() {
Route::post('stock-list', 'Api\UsersControllers#stock_list');
});
controller:
public function stock_list(Request $request)
{
$request=$request->all();
return response()->json([
'request' => $request
]);
}
postmans:
Related
Really sorry if this has been covered before but I can't find a working solution and have even asked numerous work colleagues.
I have a form which sends a POST request to my MailController, validation and everything works just as expected, but on success, I want to redirect to a Thank you page. The Post request returns either a 200 or 302 depending on the method I try but every time it only sends the HTML to the response and doesn't render the component.
I can, however, navigate to that page if I visit the URL and it loads perfectly.
I've tried
to_route
Inertia::render
Redirect()
Redirect()->route()
Redirect::route()
even sending it in response, catching it in Axio's request in the component, and redirecting from there.
This is driving me nuts I just can't see what I'm doing wrong.
TIA for your help and suggestions
//controller
<?php
namespace App\Http\Controllers;
use ...
use Inertia\Inertia;
use...
class MailController extends Controller
{
public function sendEmail(Request $request)
{
$contact = $request->validate([
......
],[
.......
]);
Mail::to('email#email.co.uk')->send(new ContactEmail($contact));
return Redirect::route('thankyou');
// return response()->json([
// 'data' => $contact,
// 'message' => 'Message sent successfully.',
// 'redirect' => route('thankyou')
// ], 201);
}
}
// route
Route::get('/thankyou', function () {
return Inertia::render('Thankyou');
})->name('thankyou');
// submit function
submitContact: function () {
axios.post('/email', this.contact)
.then(response => {
console.log(response.data.redirect);
// if (response.data.redirect) {
// return Inertia.location(response.data.redirect);
// }
}).catch(err => {
const errors = err.response.data.errors;
if (err) {
Object.keys(errors).forEach(key => {
this.errors[key] = errors[key][0];
});
}
})
}
I'm recreating some requests that are now written in plain cURL to Laravel's HTTP client. But for some reason it gets in an infinite loop when I try to call a method based on the request response.
This is the call, I have the tokens stored, and when valid everything runs fine. When request returns 401 and want to call the function that creates new tokens and after that retry the original call.
Call:
public function call($path, $methold = null, $payload = null) {
if (empty($this->token)) {
$this->auth_new();
}
$response = Http::withOptions([
'debug' => false
])->withHeaders([
'X-Csrf-Token' => str_replace('csrf_token=', '', Crypt::decryptString($this->token->csrf_token)),
'Cookie' => $cookies,
"Accept" => "*/*",
])->get($url);
if ($response->status() == 401) {
$this->auth_new();
return $this->call_new($path);
} else {
return $response;
}
}
And the auth_new:
public function auth_new() {
$data = json_encode($payload);
$response = Http::withHeaders([
'Content-Type' => 'application/json; charset=UTF-8',
'Connection' => 'keep-alive'
])
->post($this->controller->controller_url . '/api/login', $payload);
$this->token = (response stuff goes here)
}
In cURL it all is working. So when I make a request and it returns a 401, I call the auth function, it stores the new token variables and I return the call.
When I remove the return $this->auth_new() from the call function, it generates new tokens and when I refire the call, I get a valid response. So token wise everything is working. It is just when it returns a 401 and when I want to retry te call automatically it fails with the HTTP client from laravel.
I'm making a normal Inertia post to a base Laravel login route:
submit() {
this.$inertia.post("/login", {
email: this.emailAddress,
password: this.password,
}, {
preserveState: true,
preserveScroll: true,
});
}
I'm able to catch validation errors as expected, but what I'm trying to avoid is the redirect after a successful user authentication, and instead proceed in the "logged in" state (update header to show user info, etc).
The Laravel AuthenticatesUsers trait contains this contains two key methods that gets called as part of the out-of-the-box login flow
public function login(Request $request)
{
$this->validateLogin($request);
// If the class is using the ThrottlesLogins trait, we can automatically throttle
// the login attempts for this application. We'll key this by the username and
// the IP address of the client making these requests into this application.
if (method_exists($this, 'hasTooManyLoginAttempts') &&
$this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if ($this->attemptLogin($request)) {
return $this->sendLoginResponse($request);
}
// If the login attempt was unsuccessful we will increment the number of attempts
// to login and redirect the user back to the login form. Of course, when this
// user surpasses their maximum number of attempts they will get locked out.
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
and
protected function sendLoginResponse(Request $request)
{
$request->session()->regenerate();
$this->clearLoginAttempts($request);
if ($response = $this->authenticated($request, $this->guard()->user())) {
return $response;
}
return $request->wantsJson()
? new Response('', 204)
: redirect()->intended($this->redirectPath());
}
I'm struggling to figure out if it's even possible to authenticate a user without redirecting this way.
You need to utilize the javascript frontend, not Inertia::post() . One way to do this is to use Axios:
submit() {
const data = {...this.form.data()};
axios.post('/auth/login', data, {
headers: {
'Content-Type': 'application/json',
},
})
.then(res => {
console.log('login success!', res);
});
Check your form and the way you submit - do you prevent the default behavior of the form submit? It seems like you are sending a POST but the native form behavior is also triggered.
You can also set a $redirectTo in your LoginController, also check RouteServiceProvider there is a public const HOME = '/' which triggered the redirect if nothing else is given.
This are my two cents...
A few days ago I was struggling with passing the result of the script to Vue without redirecting, using Inertia visits instead of Axios.
The solution I adopted was the following:
In vue:
this.$inertia.visit(`URL`, {
method: "post",
data: { //Email and password },
preserveState: false,
preserveScroll: false,
onError: (errors) => { // do what ever is needed if validation fails },
onSuccess: () => { // do what ever is needed if validation succeeds }
});
In Laravel:
// If validation fails:
return redirect()->back()->withErrors([
'login' => 'Validation fail details.'
]);
// If validation succeeds:
return redirect()->back()->with('login', 'Success message!');
This way the page does not redirect and the user can continue exactly wherever he is.
What i'm not sure is if it's possible to pass the user info over the success redirect message. Maybe returning a array like it's done in withErrors. If not possible it's always possible to make an additional request to the server to retrieve the desired information.
Hope it's usefull.
My API has been built with Laravel 5.4 Passport, authorisation and issuing access tokens is working properly, but when dealing with a resource like below using Insomnia or Postman:
Authorization: Bearer [encrypted access token goes here which has only "manage-users" scope]
Content-Type: application/json
Accept: application/json
I send above request to this url:
http://apiendpoint.loc/api/users
which has been restricted access to this recourse to tokens which has this scopes
manage-users, test-scope-1
Route::get('/users', [
'as' => 'users',
'uses' => 'UsersController#index'
])->middleware(['auth:api', 'scopes:manage-users,test-scope-1']);
scopes have been defined in the:
AuthServiceProvider
Passport::tokensCan([
'manage-users' => 'testing',
'test-scope-1' => 'test scope',
'test-scope-2' => 'another test scope',
]);
protected $routeMiddleware = [
...,
...,
...,
'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class
];
The token used to authorize this request has "manage-users" scope only, so I expected to get json response with unauthorized access 401 in addition to the required scope to access this resource which is "test-scope-1".
although I got a HttpException "Invalid scope(s) provided." as HTML response not json
Edit
Auth-Scaffolding is not installed.
After a lot of digging, I found a way to to work around the issue earlier in the exception handler like below:
public function render($request, Exception $exception)
{
// If the request wants JSON (AJAX doesn't always want JSON)
if ($request->wantsJson()) {
if($exception instanceof MissingScopeException){
// Define the response
$response = [
'errors' => 'Sorry, something went wrong.'
];
// If the app is in debug mode
if (config('app.debug')) {
// Add the exception class name, message and stack trace to response
//$response['exception'] = get_class($exception); // Reflection might be better here
$response['message'] = $exception->getMessage();
//$response['trace'] = $exception->getTrace();
}
// Default response of 401
$status = 403;//forbidden
// If this exception is an instance of HttpException
if ($this->isHttpException($exception)) {
// Grab the HTTP status code from the Exception
$status = $exception->getStatusCode();
}
// Return a JSON response with the response array and status code
return response()->json($response, $status);
}
}
return parent::render($request, $exception);
}
so I'll be able to catch the error early and return a json object as a response.
So my problems is that the session token is generated.
and the token that i've sent via AJAX or AXIOS (cause im using vue and vue router for fetching API)
is getting a mismatch
This is the response i got when posting data
The ajax token is equal to the token in the meta tag of the main blade template
using this tag
Meta Tag in app.blade.php
<meta name="csrf-token" content="{{ csrf_token() }}">
<script>
window.Laravel = <?php echo json_encode([
'csrfToken' => csrf_token(),
]); ?>
</script>
Interceptor of Axios (purpose is to inject the csrf_token from the meta Tag)
Vue.axios.interceptors.request.use(function (config) {
config.headers['X-CSRF-TOKEN'] = Laravel.csrfToken;
console.log(config);
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
Response:
array:1 [
"SessionToken" => "JfhmtCaTiQ49BtF2VK3TysvYnEQSE9n5i1uiHegO"
]
array:1 [
"AjaxToken" => "WqKOiaunbvJbxIsnEjetFoCm1mvdUYESRqfXO2lv"
]
VerifyCSRFToken middleware method:
protected function tokensMatch($request)
{
$sessionToken = $request->session()->token();
$token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');
dd(['SessionToken' => $sessionToken],['AjaxToken' => $token]);
if (! $token && $header = $request->header('X-XSRF-TOKEN')) {
$token = $this->encrypter->decrypt($header);
}
if (! is_string($sessionToken) || ! is_string($token)) {
return false;
}
return hash_equals($sessionToken, $token);
}
So i came up with this idea but its not working because its the token that im getting from the api is null or empty
Here is the method from my RegisterComponent.vue
submitForm() {
this.axios.get('/token')
.then(response => {
this._token = response.data
this.axios.post('/register',this.data)
.then(responseNew => {
console.log(responseNew.data);
})
.catch(responseNew => {
this.errors = responseNew.data;
})
});
}
as you can see im getting a token from my api.php in routes folder
and im also using the authentication api of Laravel and put it on the api routes too
Here is the api.php
Route::group(['middleware' => 'web'], function() {
Auth::routes();
});
Route::get('/token',function() {
dd(csrf_field());
});
Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('auth:api');
Route::resource('/users','UserController');
Route::group(['middleware' => 'auth'], function () {
Route::resource('/stores','StoreController');
Route::resource('/items','ItemController');
Route::resource('/transactions','StoreController');
Route::resource('/managers','ManagerController');
Route::resource('/employees','EmployeeController');
Route::resource('/customers','CustomerController');
Route::resource('/tags','TagController');
});
So how can i prevent it from generating that token that will cause mismatch?
Anyone answering this will surely help the authentication of my SPA ( Single Page App)
and its also giving me response status 302
You seem to have a bit misunderstanding. You have the csrf token configured for axios, so every request will have a header field containing the token, then you just need to make sure every request goes through laravel's csrf token validation function before it reaches your business logic, that's all you need to do to prevent csrf. The get('/token') before post('/register') seems unnecessary.
Also, talking about the /token route itself, csrf_field is not appropriate here, since it generates a hidden form field (another way to send csrf token apart from what we talked about earlier) to be embedded in a .php file like <form>...<?=csrf_field()?>...</form> => <form>...<input type="hidden" name="laravel_csrf_token" value="***">...</form>, which makes it meaningless to request csrf_field's result via xhr.