"GET method is not supported for this route" even though it's a POST route - laravel

I have a POST route in my Laravel application:
Route::post('/register-direct', 'Auth\RegisterController#direct')->name('register.direct');
Currently the method doesn't do anything but try to log the request:
public function direct(Request $request) {
logger()->info($request->all());
}
Since this route should be accessible from outside the domain, I disabled CSRF protection for it:
class VerifyCsrfToken extends Middleware
{
/**
* Indicates whether the XSRF-TOKEN cookie should be set on the response.
*
* #var bool
*/
protected $addHttpCookie = true;
/**
* The URIs that should be excluded from CSRF verification.
*
* #var array
*/
protected $except = [
'register-direct'
];
}
However, the strangest thing is happening. Even though it's a POST route, when I try to send the request from Postman to my remote site, I get the error:
The GET method is not supported for this route. Supported methods: POST.
From what I see, it is doing some sort of redirect to /register-direct as a GET route for some reason. The request never reaches the appropriate controller method (since the logging in the method never happens).
I suspect some middleware is the culprit, but the only middleware on this controller is the guest() middleware. And when I disable this middleware, it doesn't change anything.
Additional info:
there is no other route with the same name or URI
when I send the POST request to localhost (instead of to my remote site) it works correctly
the same error appears in Postman and if I try to submit this request from another domain
I have tried moving the route from web.php to api.php and nothing changed
I have other routes in api.php that accept remote requests and work just fine

Related

Route type delete does not work in Laravel

I have following route and works
Route::post("delete-role", [RoleApiController::class, "Remove"]);
I tested it through postman like this
http://localhost/delete-role?api_token=hcvhjbhjb12khjbjhc876
Now, if I change above route and convert to type delete..
Route::delete("delete-role/{role_id}", [RoleApiController::class, "Remove"]);
it does not work. I get below error. It seems to be the reason that the api_token is missing.
I get same error when trying to update route like below
Route::delete("delete-role/{role_id}/{api_token}", [RoleApiController::class, "Remove"]);
You have to set header of your request as:
"Accept": "application/json"
in postman.
If you don't set the required header for api, Laravel Passport can't understand request as an API client and so it will redirect to a /login page for the web.
Or you can set a middleware to check it in code:
public function handle($request, Closure $next)
{
if(!in_array($request->headers->get('accept'), ['application/json', 'Application/Json']))
return response()->json(['message' => 'Unauthenticated.'], 401);
return $next($request);
}
You have an incomplete details. but I see few issues here.
You seem to be using web routes for your API requests which is a bad set-up
You do not have a route with login name.
based on the error you posted, your request seems to successfully destroyed the token and logged you out, then called the middleware App\Http\Middleware\Authenticate which supposed to redirect your request to login route which does not exist and the reason you are getting that error.
You can see from that Authenticate middleware it will supposed to redirect you to login route for unauthenticated request. thats why you need to use the api routes so you can handle the response manually
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* #param \Illuminate\Http\Request $request
* #return string|null
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
}
Also, I'm not so sure about this, but the reason you are not getting the same issue with your POST request is probably because your POST request does not call the Authenticate middleware or whatever in your routes file or class function that calls the authenticate middleware.
But again, just use api routes if you don't want un-authenticated request to automatically redirect your request to login routes which does not exist in your application
The problem is that he doesn't define route ('login'),
add in Exceptions/Handler.php
$this->renderable(function (AuthenticationException $e, $request) {
if ($request->is('api/*')) {
return response()->json(['meassge' => 'Unauthenticated']);
}
});
Then you should use Passport Or Sanctum for auth with Api,
Continue from here https://laravel.com/docs/9.x/passport
Probably, this thread could help you. Route [login] not defined
(OR)
You need to setup auth scaffolding to get login route defined.
Important: your project will be overridden if you setup auth scaffold.
So, I would only recommend doing this if it is a new project or a testing app.
See this for detail doc but install Laravel Breeze would be suffice.
It Appears you have called route('login') without having defined it in your routes, Please remove route('login') from your code or define it in your routes. eg::
Route::get('login', [YourController::class, 'methodName'])->name('login');

spring-social: ProviderSignInController: Why signIn(...) with POST only?

Is there a specific reason why a user sign-in with {providerId} can only be initiated with a POST request (to /signin/{providerId})? Why not with a GET request?
Answering my own question: Maybe because of
/**
* Process the authentication callback when neither the oauth_token or code parameter is given, likely indicating that the user denied authorization with the provider.
* Redirects to application's sign in URL, as set in the signInUrl property.
* #return A RedirectView to the sign in URL
*/
#RequestMapping(value="/{providerId}", method=RequestMethod.GET)
public RedirectView canceledAuthorizationCallback() {
return redirect(signInUrl);
}
Anyway, it would be nice if a sign-in with a GET request would be possible, too (with a different URL or a parameter narrowing the mapping).

Laravel passport change header authentication

I am using Laravel passport and it requires to send in every request the header Authentication to be sent.
Is it possible to change the name of the header to X-Access-Token?
I saw passport uses the package
League\OAuth2\Server\AuthorizationValidators;
method:
/**
* {#inheritdoc}
*/
public function validateAuthorization(ServerRequestInterface $request)
{
dd($request);
if ($request->hasHeader('authorization') === false) {
throw OAuthServerException::accessDenied('Missing "Authorization" header');
}
$header = $request->getHeader('authorization');
$jwt = trim(preg_replace('/^(?:\s+)?Bearer\s/', '', $header[0]));
I tried to change here but seems the validation of the headers happen before this method.
There are many fundamental pieces of code that rely on the existence of the authorization header.
You could roll your own if you felt so inclined.
Note also that authorization is a web standard request header. X-Access-Token is a response header pattern.
*Edit**
Given our conversation below, you can use Middleware and Middleware priority to dictate which runs first, observe requests that have an X-Access-Token and use addHeader to convert the value of that header to authorization:
php artisan make:middleware AuthorizationToolMiddleware
Then in the handle function:
public function handle($request, Closure $next)
{
$request->headers->set('Authorization', $request->headers->get('X-Access-Token'));
return $next($request);
}
This middleware should execute before other middleware in order to ensure the headers are set by the time that passport handles the request.
For Laravel 5.8 you'd have to force your custom middleware to always be on top of the call chain
So in your app\kernel.php add this -
protected $middlewarePriority = [
\App\Http\Middleware\AuthorizationToolMiddleware::class,
];

Laravel 5.5 Request is empty in Restful controller

I have such a route in my routes/web.php
Route::resource('/api/surveys', 'SurveyController');
As documentation says, it creates all needed routes for API. This is a function, that gets executed when I go for /api/surveys route:
public function index()
{
$request = request();
if(!$request->hasHeader('token')) {
return "No auth token found.";
}
$tokenCheck = $this->userService->isTokenValid($request->header('token'));
if($tokenCheck !== true) {
return $tokenCheck;
}
return $this->surveyService->all();
}
What it does, it checks if token header parameter is set, if not, it returns an error, if yes, it checks if token is valid and etc. if everything is OK, it should return surveys from database.
public function surveys() {
$request = \Request::create('/api/surveys', 'GET');
$request->headers->set('Accept', 'application/json');
$request->headers->set('token', \Cookie::get('token'));
$response = \Route::dispatch($request);
print_r('<pre>');
print_r($response);
print_r('</pre>');
}
I have a website, that should use that API I just created to get all survey records. I create a new request object, set header "token" with token I get from a cookie and then try to dispatch and get a response. But the problem is that everytime I get "No auth token found." error. That means $request->hasHeader('token') returns false, even tough I set it here in my request. If I print_r $request->all() in Restful controller, I get an empty array.
I tried Postman to access this API with token parameter, and it works fine in postman, but here, it seems that Request disappears while it travels to API controller.
What I did wrong here?
When you manually create a request and dispatch it, that works to get the routing to call the correct controller, however that does not affect the request that is bound in the container.
When your "fake" request is handled by the api controller, the request that it pulls out of the container is the original "real" request that was made by the user.
Instead of dispatching the route with your new request, you will need to app()->handle($request) the new request. This, however, will completely replace the original "real" request with your new "fake" request, so everything from the original request will be lost.
Having said all that, this method of consuming your own api is discouraged, even by Taylor. You can read his comment on this Github issue. So, consuming your own api like this may work, but you may also run into some other unforeseen issues.
The more appropriate solution would be to extract out the logic called by the api routes to another class, and then call that extracted logic from both your api routes and your web routes.

Laravel 5.0 custom 404 does not use middleware

I'm using a middleware to parse the output of the templates. This is working fine for all pages.
However when I want to show a 404 (got a custom page for that) it doesn't treat it as a http request (that's what I think) since it doesn't go through the middleware.
My question is, how to have ALL requests go through the middleware.
The error pages don't go through the routes.php.
In Kernel.php move your middleware from the $routeMiddleware array to $middleware array.
Middleware in this array will run on every request (tested in 5.1).
For people like me who spending hours in 2020 because of this weird behaviour...
Now in Laravel there is a new instrument «Fallback Routes».
Add this to /routes/web.php:
Route::fallback(function () {
return view("404"); // template should exists
});
After that all requests will go throught middlewares.
At Laravel 5.4 and probably some older ones you can modify the file app/exceptions/Handler.php and the function render like this:
if( is_a( $exception, \Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class ) ) {
return redirect()->route( 'error_404' );
}
// ... old code in the function ...
in this way every 404 errors are redirected to certain real route that acts like other routes of site.
You may also submit any data from current request to show a reasonable error at the target.
I had a use case where api routes needs to always return json response.
All routes return json response (laravel checks through $request->expectsJson()) IF user specifically ask for it by sending accept: application/json header.
But many a times user doesn't send the header and they get an html response instead.
As for my use case, all api routes will always send json response we can force the routes to return json, by manually attaching accept: application/json header using a middleware.
App\Http\Middleware\ForceJsonOnAPIs.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Str;
class ForceJsonOnAPIs
{
/**
* Handle an incoming request.
*
* #param Request $request
* #param Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next)
{
// Force Json accept type on api routes
if ($request->is('api/*') && !Str::contains($request->header('accept'), ['/json', '+json'])) {
$request->headers->set('accept', 'application/json,' . $request->header('accept'));
}
return $next($request);
}
}
Register the middleware in App\Http\Kernel.php
// add the new middleware ForceJsonOnAPIs
protected $middleware = [
\App\Http\Middleware\ForceJsonOnAPIs::class,
// rest of the middleware,
];
Important: You can assign the middle to $middlewareGroups in Kernel like web or api, But you will get into trouble when 404 exception occurs. The issue is The error pages don't go through the routes.php (Thanks to #Björn Answer above) thus the routes middleware won't get called and the 404 will return html response.
It's the same case for validation or authentication exceptions.
In my opinion, it's best to assign the middleware in the $middleware array as it runs on each request. This way all exceptions will automatically return correct exceptions as json in all routes.

Resources