CORS errors because of internal app error - cakephp

I am creating a simple API with CakePHP 4, and I am having some issues with some CORS requests.
Access to XMLHttpRequest at 'http://localhost/myapp.api/elaborations/add.json' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Everything works with every other request and after some digging I've found that the error was an undefined index in my controller. If I fix that, the CORS error disappears. I just didn't see it in my log files, it's my bad.
It's a bit confusing seeing a CORS error because of a coding error though. I guess the issue could be in my CORS configuration, and hence this question.
This is what I've ended with, after a little bit of web search, trials and errors. I know it's ugly but I couldn't find anything better that actually worked.
How can I avoid having CORS errors for coding issues? I guess there is some redirect action somewhere, but I can't figure out how to avoid it.
<?php
namespace App\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Server\MiddlewareInterface;
class CorsMiddleware implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface
{
// Calling $handler->handle() delegates control to the *next* middleware
// In your application's queue.
$response = $handler->handle($request);
if ($request->getHeader('Origin')) {
$allowedDomains = [
'https://myapp.it',
'https://www.myapp.it',
'http://localhost:3000',
];
$origin = $_SERVER['HTTP_ORIGIN'];
if (in_array($origin, $allowedDomains)) {
header('Access-Control-Allow-Origin: ' . $origin);
}
header('Access-Control-Allow-Methods: POST, GET, PUT, PATCH, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: *');
if (strtoupper($request->getMethod()) === 'OPTIONS') {
exit(0);
}
}
return $response;
}
}

First of all, do not access superglobals in CakePHP directly, always use the abstracted APIs! Also you shouldn't echo data manually, that includes headers, again, use the abstracted APIs! Using superglobals and echoing data will only get you in trouble, it messes up the testing environment, it can lead to data not being sent (properly), etc.
That being said, for proper CORS coverage you also need to modify error handling, as the error controller will create a new response instance. You should be able to process the request/response in a custom exception renderer, at ExceptionRenderer::_getController(), like so:
// in src/Error/AppExceptionRenderer.php
namespace App\Error;
use App\Http\Middleware\CorsMiddleware;
use Cake\Controller\Controller;
use Cake\Error\ExceptionRenderer;
class AppExceptionRenderer extends ExceptionRenderer
{
protected function _getController(): Controller
{
$controller = parent::_getController();
$cors = new CorsMiddleware();
$response = $cors->setHeaders(
$controller->getRequest(),
$controller->getResponse()
);
return $controller->setResponse($response);
}
}
// in config/app.php
'Error' => [
'exceptionRenderer' => \App\Error\AppExceptionRenderer::class,
// ...
],
Your CORS middleware would supply the setHeaders() method accordingly, where you set the CORS headers if required, based on your example it could look something like this:
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface
{
$response = $handler->handle($request);
$response = $this->setHeaders($request, $response);
return $response;
}
public function setHeaders(
ServerRequestInterface $request,
ResponseInterface $response
): ResponseInterface
{
if ($request->getHeader('Origin')) {
$allowedDomains = [
'https://myapp.it',
'https://www.myapp.it',
'http://localhost:3000',
];
$origins = $request->getHeader('Origin');
$lastOrigin = end($origins);
if (in_array($lastOrigin, $allowedDomains, true)) {
$response = $response
->withHeader('Access-Control-Allow-Origin', $lastOrigin);
}
if (strtoupper($request->getMethod()) === 'OPTIONS') {
$response = $response
->withHeader(
'Access-Control-Allow-Methods',
'POST, GET, PUT, PATCH, DELETE, OPTIONS'
)
->withHeader('Access-Control-Allow-Headers', '*');
}
}
return $response;
}
See also
Cookbook > Error & Exception Handling > Custom ExceptionRenderer
Cookbook > Request & Response Objects
Cookbook > Middleware > Creating Middleware

Related

Getting CORS error rather than expected 429 response when rate limit is reached

In my backend I implemented an IpRateLimit middleware with the AspNetCoreRateLimit package in my .net core entity framework backend. When an IP Address x makes y calls in a specific time, it gets blocked for a certain time and the backend should return an 429 error and this works fine when testing with postman. But when the I make an request with axios, that is supposed to get blocked because of the ip rate limiter, I receive an axios error:
"Access to XMLHttpRequest at 'https://localhost:44372/api/Users/Login/' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource."
"POST https://localhost:44372/api/Users/Login/ net::ERR_FAILED"
After receiving this error, I have added the needed header, but it hasn't changed the result. Other axios requests to my backend (also post, put and delete) work fine, but when the ip rate limiter hits, I just get the cors error.
I implemented the limiter in my application as in the following tutorial:
https://edi.wang/post/2019/6/16/ip-rate-limit-for-aspnet-core
React axios request:
async function buildPostAndFetch(url, param, header) {
const finalurl = `${BASE_URL}${url}`;
return axios.post(finalurl, param, {headers:{"Access-Control-Allow-Origin": "*"}})
.then(res => {
response(res);
return res.data ? res.data : true;
})
.catch(err => {
handleError(err);
return false;
})
}
handleError() {
const handleError = err => {
setError(true);
if(err.request?.status === 0) {
console.log("*********************************************")
console.log(err.request);
console.log("*********************************************")
// throw new Error("API is currently offline or you are not connected to the internet:(");
} else if(err.response.status === 429) {
console.log("*********************************************")
console.log(err.response);
console.log("*********************************************")
}
}
}
When requestion and limiter hits I always get in the err.request.status === 0 path.
Most server systems/runtimes by default don’t add application-set headers to 4xx and 5xx responses but instead only add them to 2xx success responses and maybe to 3xx redirects.
So you may need to do explicit config to force headers to get added to 4xx responses, in order for that 429 response end up with the Access-Control-Allow-Origin header.
In Apache and nginx for example, that’s done by adding the always keyword to the header-setting directive. Maybe your server system has some similar thing.
You get a CORS error because that 429 error has no Access-Control-Allow-Origin header.
Firstly, ensure that you have Install-Package Microsoft.AspNetCore.Cors from NuGet.
Then, please add the following to your Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Put it before AddMvc() if there's any
services.AddCors();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// Put it before UseMvc() if there's any
app.UseCors(
options => options
.WithOrigins("http://localhost:3000")
.AllowAnyHeader()
.AllowAnyMethod()
);
app.UseMvc();
}
After finish the above configuration, by inspecting on your network tab (Browser's developer tools) you may see the one of the returned response header is access-control-allow-origin : http://localhost:3000.
Try:
app.UseCors(opt => opt
.WithOrigins("http://localhost:3000"")
.AllowAnyHeader()
.AllowAnyMethod());
The protocol (http/https) can not be omitted. Also, the cors middleware should be placed after app.UseRouting but before UseAuthorization.
You can see Middleware Order .

ERR_EMPTY_RESPONSE error in authorization Laravel

I have a web service with Laravel that solved the CORS Origin problem, but the next problem is that requests that have authorization headers return the following error server.
OPTIONS https://sandbox.example.com / api / v1 / user / net profile :: ERR_EMPTY_RESPONSE
I'm currently using cloudflare and wanted to know if this is a CDN or something else on the server.
This is preflight requests.
You need allow OPTIONS request. In first you need create CORS middleware
<?php
namespace App\Http\Middleware;
use Closure;
class Cors
{
public function handle($request, Closure $next)
{
$headers = [
'Access-Control-Allow-Methods'=> 'POST, GET, OPTIONS, PUT, DELETE',
'Access-Control-Allow-Headers'=> 'X-Requested-With, Content-Type, Accept, Origin, Authorization',
'Access-Control-Allow-Origin' => '*'
];
if($request->getMethod() === 'OPTIONS') {
// The client-side application can set only headers allowed in Access-Control-Allow-Headers
return \response('', 200, $headers);
}
$response = $next($request);
foreach($headers as $key => $value)
$response->header($key, $value);
return $response;
}
}
then add in Http/Kernel.php in array $middleware:
protected $middleware = [
// other middlewares
Cors::class
];
After it all requests with type OPTIONS will return response 200 with headers.

POST API's in Lumen (Cors Issue)

I am writing the API's in Lumen. All the GET API's are working fine in ReactJS using axios. But, POST API's are not working.I enabled the cors in Lumen by creating a middleware and use them in web.php routing file. But, still getting the same error. I can't be able to debug the problem that it's from client side or server side.
<?php
/**
* Location: /app/Http/Middleware
*/
namespace App\Http\Middleware;
use Closure;
class Cors{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next){
$headers = [
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'POST, GET, OPTIONS, PUT, DELETE',
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Max-Age' => '86400',
'Access-Control-Allow-Headers' => 'Content-Type, Authorization, X-Requested-With'
];
if ($request->isMethod('OPTIONS'))
{
return response()->json('{"method":"OPTIONS"}', 200, $headers);
}
$response = $next($request);
foreach($headers as $key => $value)
{
$response->header($key, $value);
}
return $response;
}
}
Register the middleware in bootstrap/app.php
$app->routeMiddleware([
'auth' => App\Http\Middleware\Authenticate::class,
'localization' => \App\Http\Middleware\Localization::class,
'cors' => App\Http\Middleware\Cors::class
]);
Finally, I put the requests in middleware
$router->group(['middleware' => ['cors']], function () use ($router) {
# Login API's
$router->post('/login', 'LoginController#index');
$router->post('/register', 'UserController#register');
$router->get('/user/{id}', ['middleware' => 'auth','uses' => 'UserController#get_user']);
});
But, the above solution is not working.
---------------- ERROR ---------------------
Access to XMLHttpRequest at 'http://localhost:8000/login' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
0.chunk.js:10190 POST http://localhost:8000/login net::ERR_FAILED

No Cors headers added to response

When trying to log in using an API (hosted locally) from a React application, I get this error every time:
I know there area lot of topics on this subject, but none of them helped me. Perhaps because I missed something, or don't understand the concept.
I have no idea anymore how to fix this.
Things I already tried:
- Added a HTTP middleware (code will follow): didn't work.
- Tried fixing it with the spatie/laravel-cors package: didn't work.
- Tried fixing it with the barryvdh/laravel-cors: didn't work either.
I am out of ideas. Does someone know what I am doing wrong?
My code
protected $middleware = [
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrustProxies::class,
\Spatie\Cors\Cors::class, // <-- this line would be pointed to my own middleware when that would be in use
];
The following code is pointed to instead of \Spatie\Cors\Cors::class, if I where to use my own middleware
class ApiCors
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
return $next($request)
->header('Access-Control-Allow-Origin', '*')
->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, PATCH')
->header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
}
}
There are some tricky things with implementing CORS support:
Your CORS middleware should be added to global middleware stack, because browser could send Preflight request, and you wouldn't want to have specific OPTIONS route for every API route.
Middleware doesn't need to pass Preflight request deeper to application.
CORS header(s) should be added both to Preflight requests and API request.
So, that's how it should be done:
Create middleware:
php artisan make:middleware ApiCors
Put the code:
<?php
namespace App\Http\Middleware;
use Closure;
class ApiCors {
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
*
* #return mixed
*/
public function handle($request, Closure $next)
{
$isPreflight = $request->isMethod('options') && $request->hasHeader('origin');
// we don't need to process Preflight request further
$response = $isPreflight ? response()->make() : $next($request);
if($isPreflight) {
$response
->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, PATCH')
->header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization')
->header('Access-Control-Max-Age', 86400);
}
$response->header('Access-Control-Allow-Origin', $isPreflight ? '*' : ($request->header('origin') ?? '*'));
return $response;
}
}
Register middleware:
app/Http/Kernel.php:
<?php
// ...
use App\Http\Middleware\ApiCors;
class Kernel extends HttpKernel {
// ...
protected $middleware = [
// ...
ApiCors::class,
];
// ...
protected $middlewarePriority = [
ApiCors::class, // move to the top of the chain
// ...
];
}
Test middleware:
Let's add simple API route.
routes/api.php:
Route::put('/v1/test', function () {
return response()->json(['answer' => 42]);
});
Let's start simple server (run in Laravel project root folder):
php -S localhost:8088 -t public
Next, open any webpage (or use current one) and run in developer console:
fetch('http://localhost:8088/api/v1/test', {
method: 'PUT', headers: { 'accept': 'application/json' }
}).then(r => r.json()).then(console.log.bind(console))
You should get response:
{answer: 42}
Don't forget that you should add your [external] API routes to routes/api.php, not to the routes/web.php, because web routers group has numerous middlewares which could interfere with your api ones, such as VerifyCsrfToken, for example.
MDN: Cross-Origin Resource Sharing (CORS)

Laravel Excel giving CORS error only dev server

The problem:
I am making a get request to my Laravel API and getting the following error
Cross-Origin Request Blocked: The Same Origin Policy disallows reading
the remote resource at http://www.example.com/exceptions-company-reports.
(Reason: CORS header 'Access-Control-Allow-Origin' missing)
I have followed these instructions on local and then on dev server, but I cannot figure out why Im getting this problem only on the dev server. I have even confirmed that php_zip and php_xml are enabled.
I am not getting errors in my logs.
Client side Angular code
getExceptionsReport: function getExceptionsReport() {
var apiBase = apiUrl + 'exceptions-company-reports';
var config = {
responseType: 'blob'
};
return $http.get(apiBase, config);
}
Server side:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests\PublishCompanyreportingRequest;
use DB;
use Auth;
use Excel;
class CompanyreportingController extends Controller {
public function __construct() {
$this->middleware( 'jwt.auth' );
$this->middleware( 'role:company-reports' );
}
public function exceptionsCompanyReports( PublishCompanyreportingRequest $requestData ) {
$list = DB::table( 'exceptions_reports' )->select('created_at','account_number','customer_name','fp','seriel_number','comment','grade','item_number','description')->get();
$rows = array();
foreach($list as $item) {
$rows[] = array(
"Received" => $item->created_at,
"Account Number"=> $item->account_number,
"Customer Name" => $item->customer_name,
"FP"=> $item->fp,
"Serial Number" => $item->seriel_number,
"Comment" => $item->comment,
"Grade" => $item->grade,
"Item Number" => $item->item_number,
"Description" => $item->description,
);
}
Excel::create('Filename2', function($excel) use($rows) {
// Set the title
$excel->setTitle('Company| Company Report');
// Chain the setters
$excel->setCreator('Company')
->setCompany('Company');
$excel->sheet('Exceptions Report', function($sheet) use($rows) {
$sheet->fromArray($rows);
$sheet->row(1, function($row) {
// call cell manipulation methods
$row->setBackground('#DDDDDD');
$row->setFontFamily('Calibri');
$row->setFontSize(14);
});
$sheet->setStyle(array(
'font' => array(
'name' => 'Calibri',
'size' => 14
)
));
});
// Call them separately
$excel->setDescription('A demonstration to change the file properties');
})->download('xlsx');
}
}
Laravel-Excel will not add the headers for you. So, in order to avoid CORS issues, add this header:
Excel::create('Contactos', function($excel) use ($results) {
...
})->export('xlsx', ['Access-Control-Allow-Origin'=>'*']);
I came up with a work around of sorts, I guess a little better than a workaround as there is nothing wrong with the code.
I decided to save the file to the server instead and then send that file as a response instead of relying on the extension to do it. Still quite frustrating because I will never really know what the error was. What makes it more frustrating is that I know it can work as it does on my local. I wont mark this answer as correct until in case someone has a better one.
Excel::create('Filename2', function($excel) use($rows) {
// other code
})->save('xlsx');
return response()->file(storage_path().'/exports/Filename2.xlsx');
I am also deleting the file immediately after with a DELETE request
public function destroy( $id ) {
\File::Delete(storage_path().'/exports/Filename2.xlsx');
return response()->json( [ 'success' => 'Report has been removed from server' ], 200 );
}
In my case it happened because it did not have a zip extension installed.
It was showing me Cross-Origin Request Blocked but that was not the error

Resources