405 Method not allowed with Guzzle POST request in PHP - php

I'm stuck since several hours on a the consumption of a locale API (which I created) with PHP and Guzzle on a laravel project (8.7).
I've created deux differents laravel projects on the same local server. One is providing some API routes and the second one consume it.
On the first project (which providing APIs) I've created several routes to create, read, update and delete datas from my database.
To access this API routes we need to first consume an API route called "login". This one handle the creation of a token according to a given couple email/password.
This token is needed to call all the others API routes.
The /api/login API is a POST request with email and password datas.
/api/login route declaration : (I would like to specify that this route is into the api.php file into my laravel project so the corresponding url is : http://xxx.xxx.x.xxx/site/public/api/login)
Route::post('login',[AdminController::class,'index']);
Index method for /api/login :
function index(Request $request)
{
if(!($request->ip() == "xxx.xxx.x.xxx")) {
Log::alert('Ip ' . $request->ip() . ' a tenté de se connecter à l\'Api');
return response([
'message' => ['Authentification failed']
], 403);
}
$admin = Admin::where('email', $request->email)->first();
if (!$admin || !Hash::check($request->password, $admin->password)) {
return response([
'message' => ['Email-password couple is incorrect']
], 403);
}
$token = $admin->createToken('my-app-token')->plainTextToken;
$response = [
'admin' => $admin,
'token' => $token
];
return response($response, 201);
}
In my second project I'm using Guzzle to consome my APIs.
/articles route declaration : (http://xxx.xxx.x.xxx/backoffice/public/api/login)
Route::prefix('articles')->group(function () {
Route::any('/', [ArticlesController::class, 'index'])->name('articles-index');
});
Index method for /articles :
public function index() {
$client = new \GuzzleHttp\Client();
$request = $client->request('POST', 'http://xxx.xxx.x.xxx/site/public/api/login/', [
'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'],
'form_params' => [
'email' => 'test#gmail.com',
'password' => 'dAvG454aquysla4'
],
'debug' => true,
]);
$response = $request->getBody()->getContents();
return view('articles.index', [
]);
}
I'm getting this error :
GuzzleHttp\Exception\ClientException Client error: 'POST http://xxx.xxx.x.xxx/site/public/api/login/' resulted in a '405 Method Not Allowed' response: <!doctype html> <html class="theme-light"> <!-- Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException: Th (truncated...)
I don't understand where is my mistake...
The /api/login is correctly define in POST http method and works perfectly with Insomnia.
Does anyone have an idea or will be able to help me using Guzzle? It's the first time I'm using it.
I'm used to consume API with fetch (ajax) in JS.
Thanks ;)

Related

Why Laravel `middleware('auth:sanctum')` causes 302 issues?

I'm trying to get some data from a Laravel API endpoint but I'm getting some very unusual redirect issues and disallowed methods. I will start by showing my javascript code first, then laravel code second.
First, I ran the following javascript code from the developer console of various websites with various top level domains to ensure I can authenticate without any CORS issues:
fetch("https://api.example.com/login",{
method:"post",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({"email":"test#test.com","password":"test","remember_me":true})
})
.then(r=>r.json())
.then(r=>console.log(r));
// I get a perfect response like this:
{ data: { message: "Yay! Success!", token: "86|S5isCezrsYb1aToAsI3xZb9Ot9Tu7WU8XeOK1q8C" } }
I then take the token and do another get request from my developer console to the /auth/user end point to get my account details like this
fetch("https://api.example.com/auth/user",{
method:"get",
header:{
Authorization:"Bearer 86|S5isCezrsYb1aToAsI3xZb9Ot9Tu7WU8XeOK1q8C",
Accept: "application/json"
},
})
.then(r=>r.json())
.then(r=>console.log(r));
But something completely weird happens. The https://api.example.com/auth/user gives my developer console a 302 response. Then my developer console automatically (without any intervention from me) makes a GET request to https://api.example.com/login. The https://api.example.com/login then gives a 405 response with this message:
Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException: The GET method is not supported for this route. Supported methods: POST. in file /var/www/api/vendor/laravel/framework/src/Illuminate/Routing/AbstractRouteCollection.php on line 117
#0 /var/www/api/vendor/laravel/framework/src/Illuminate/Routing/AbstractRouteCollection.php(103): Illuminate\Routing\AbstractRouteCollection->methodNotAllowed(Array, 'GET')
#1 /var/www/api/vendor/laravel/framework/src/Illuminate/Routing/AbstractRouteCollection.php(40): Illuminate\Routing\AbstractRouteCollection->getRouteForMethods(Object(Illuminate\Http\Request), Array)
#2 /var/www/api/vendor/laravel/framework/src/Illuminate/Routing/RouteCollection.php(162):
...etc...
This is my Laravel code:
~/routes/api.php
Route::post('login', ['as' => 'login', 'uses' => 'Api\UserController#login']); //->middleware(['throttle:6,1']);
Route::middleware('auth:sanctum')->get('/auth/user', function (Request $request) {
return $request->user();
});
~/app/Http/Controller/Api/UserController.php
public function login(LoginRequest $request)
{
$user = User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'message' => ['The provided credentials are incorrect.'],
]);
exit;
} else if (!$user->active) {
throw ValidationException::withMessages([
'message' => ['Your account approval is pending.'],
]);
exit;
}
return response()->json([
'data' => [
"message" => __("Yay! Success!"),
"token" => $user->createToken('authentication-token')->plainTextToken,
],
]);
}
So my question is, why can't the /auth/user endpoint return information about the user I'm logged in with?
EDIT
I tried php artisan route:clear which had no effect.
I tried changing the route from /auth/user to authuser in both the route/api.php and my fetch() call, and suddenly I have a CORS error. Is the auth/ path a reserved word of some kind?
Oh...i guess this config/cors.php also matters:
'paths' => [
'api/*',
'auth/user',
'login',
'logout',
'sanctum/csrf-cookie',
],
UPDATE
I created a few more routes like this:
~/routes/api.php
Route::middleware(['auth:sanctum'])->group(function () {
Route::resource('blah1', Api\Blah1::class)->only(['index', 'store', 'show', 'update', 'destroy']);
Route::resource('blah2', Api\Blah1::class)->only(['index', 'store', 'show', 'update', 'destroy']);
// .. etc...
Route::get('/auth/user', function (Request $request) {
return $request->user();
});
});
~/config/cors.php
'paths' => [
'api/*',
'auth/user',
'blah*',
'login',
'logout',
'sanctum/csrf-cookie',
],
I noticed that POSTMAN has ZERO problems getting information from auth/user and blah1, blah2, etc...
However, it is just the browser that will always give a 302 and cause a redirect when pinging auth/user, blah1, blah2, etc.... FireFox shows me these responses:
If I remove the middleware('auth:sanctum'), then I no longer get the 405 and 302 issue. However, $request->user() becomes null without the middleware('auth:sanctum'). I need a way to get the user information from Authorization: Bearer <token>

Laravel NotFoundHttpException on API

I have API URL like:
http://example.com/api/driverAcceptOrder?id=bee74e39-ff38-46a6-9e5d-6db799d2be8c&driverId=3453a3a9-7f58-434a-8dab-95c3469e6238
method is POST and it takes 2 parameter id and driverId
When I try to run this URL in postman I get:
Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException
Route
Route::post('driverAcceptOrder/{id}/{driverId}', 'Api\DriversController#driverAcceptOrder');
Controller
public function driverAcceptOrder(Request $request, $id, $driverId)
{
$order = Order::findOrFail($id);
$defs = OrderDefaultProgress::where('name', 'Driver OTW ke pelanggan')->first();
$driver = Driver::where('id', $driverId)->with('user')->first();
$order->update(['driver_id' => $driverId]);
return response()->json([
'data' => $driver,
'message' => 'Pengemudi Dalam perjalanan menuju pelanggan.'
], 200);
}
Note
Route is not restricted by Auth middleware (its public)
I've added exception to my VerifyCsrfToken file as protected $except = ['/api/*'];
Any idea?
your url is wrong
example.com/api/driverAcceptOrder?id=bee74e39-ff38-46a6-9e5d-6db799d2be8c&driverId=3453a3a9-7f58-434a-8dab-95c3469e6238
here after ? all is query paramter which is used in GET method to send data
Route::get('driverAcceptOrder',"..");
which is not found in your case that's why your getting
NotFoundHttpException
for your case url should be
example.com/api/driverAcceptOrder/bee74e39/3453a3a9-7f58-434a-8dab-95c3469e6238
this will be handel by
Route::post('driverAcceptOrder/{id}/{driverId}', 'Api\DriversController#driverAcceptOrder');
you can learn more about
GET and POST here https://www.w3schools.com/tags/ref_httpmethods.asp

How can I login manually with API between two laravel project?

I’ve two Laravel based projects and I want to login with API which the first project provides and use this authentication in the second project.
in the LoginController in second project:
public function login(Request $request)
{
$login_response = Http::post('{first_project_login_api}', [
'data' => [
"email" => $request->input('email'),
"password" => $request->input('password')
]
]);
if ($this->attemptLogin($login_response)) {
return $this->sendLoginResponse($request);
}
}
protected function attemptLogin(Response $response)
{
return $response->object()->status === 200;
}
In the second project, I don't need to database because I want to authentication in the first project but does not seems to be possible!
actually I need to know how to overwrite attemptLogin() function in LoginController.
It would be highly appreciated if anyone can advise me!😊
Instead of using login between application, i would use API keys. The easiest way to get started is to use simple API Authentication.
First create migrations for the user table.
Schema::table('users', function ($table) {
$table->string('api_token', 80)->after('password')
->unique()
->nullable()
->default(null);
});
To get keys, set them on the user either in Tinker, command or creation.
$user->api_token = Str::random(60);
$user->save();
Protect your API routes with a middleware.
Route::middleware('auth:api')->group(function() {
// your routes
});
Calling your api is as simply as.
response = $client->request('POST', $yourRoute, [
'headers' => [
'Authorization' => 'Bearer ' . $yourToken,
'Accept' => 'application/json',
],
]);
This is a fairly basic setup, for production or moving forward you should look into Sanctum or Passport. This is just a good start, from where i feel you are based on your question.

Laravel request url with parameter problem with GET Method

I have a route in api.php which look like this:
Route::get('auth/logout/{token}','UserController.php';
I tested this API endpoint using Postman with these configurations:
Method: GET
Params: key = token; value = $2y$10$Xji0VW1Qq9rtF04QlXDu1ePKNKHpRA2ppjDYWNFX.37C30sd3WSIu
Header: none
URL: localhost:8000/api/v1/logout?token=$2y$10$Xji0VW1Qq9rtF04QlXDu1ePKNKHpRA2ppjDYWNFX.37C30sd3WSIu
Here is my UserController#logout:
public function logout($token){
return response()->json([
'message' => 'Logout Success',
'token' => $token
], 200);
}
As you can see there, I just want to show a message and the $token parameter in Postman. But my problem is, Postman shows me a blank response. I can't access the URL with ? as the parameter separator. But I can access the URL with /, just like host/api/v1/auth/logout/{token_value}. But it is not what I desired. Anyone can help me?
You can remove the token route parameter:
Route::get('auth/logout', 'UserController.php');
And retrieve the token from the request in the controller:
public function logout(Request $request) {
return response()->json([
'message' => 'Logout Success',
'token' => $request->token
], 200);
}

User Signup with Laravel Passport - Password Grant

I know how the basic auth works for sign up/login on Laravel. However, I want to learn setting up how to do user sign up (for Password Grant). I set up Passport (2.0) - Passport Grant, and I can get token; however I couldn't find anything for user signup. Then I found a topic here explaining that I should call the oauth/token internally, but also couldn't figure out how to achieve it exactly.
So what I thought is creating a signup() method in a controller and handle user registration on my own, but then how would I pass necessary data to oauth/token route? Because what Laravel uses for $request is Request $request but what Passport uses is ServerRequestInterface $request and when I vardump the $request on oauth/token's issueToken method, it's totally different Request $request.
// Setup new method
public function signup(Request $request) {
// do validations
// create user
User::create([
'email' => $request->username,
'password' => bcrypt($request->password),
]);
$client = \Laravel\Passport\Client::where('password_client', 1)->first();
$oauthData = [
'grant_type' => 'password',
'client_id' => $client->id,
'client_secret' => $client->secret,
'username' => $request->email,
'password' => $request->password,
'scope' => null
]
// Here, I got confused how to pass this `oauthData` to `issueToken()` route
// and it takes it as `ServerRequestInterface $request`
}
Someone did it likes this here, but no idea how I should exactly implement this approach. Because my /oauth/token route's post method issueToken() is like this:
public function issueToken(ServerRequestInterface $request)
{
return $this->withErrorHandling(function () use ($request) {
return $this->server->respondToAccessTokenRequest($request, new Psr7Response);
});
}
I am very confused and couldn't figure out how to overcome this. What is the right way of handling such scenario where I need to signup users through api?

Categories