Set AllowedOrigins based on condition using Laravel-Cors - laravel

I was previously using a custom cors middleware in order to handle Allow Origin based on my environment. But after upgrading to 5.5, I had an issue with preflight OPTIONS cors so I switched to laravel-cors library. But now I don't know how I can handle different cases just by a config file. I'm wondering if anyone has experienced a similar issue. This is my previous custom cors middleware:
public function handle($request, Closure $next)
{
$origin = \Config::get('app.url', "https://mysite.ca");
// Set origin for localhost developing, else just use the staging server
if( isset( $_SERVER['HTTP_ORIGIN'] ) && $_SERVER['HTTP_ORIGIN'] === 'http://app.localhost:3333' ) {
$origin = 'http://app.localhost:3333';
}
$response = $next($request);
$response->headers->set('Access-Control-Allow-Origin', $origin);
$response->headers->set('Access-Control-Expose-Headers','Authorization');
$response->headers->set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PUT, DELETE');
$response->headers->set('Access-Control-Allow-Headers', 'Content-Type, Accept, Authorization, X-Requested-With, Application');
$response->headers->set('Access-Control-Allow-Credentials','true');
return $response;
}

laravel-cors already by default automatically does the equivalent of what the custom code in the question is doing — that is, laravel-cors sets the Access-Control-Allow-Origin value conditionally based what the value of the Origin header is and if you’ve allowed it in your config.
However, as far as what the code in the question does, it’s not clear why you want to ever send an Access-Control-Allow-Origin response header with its value set to the app.url value.
What I mean is, app.url’s value is the URL for the same server you’ve installed laravel-cors at, right? That is, it’s the application to which you want to allow cross-origin requests from particular origins.
If that’s the case, then you don’t need to explicitly allow requests from app.url, because those aren’t cross-origin requests so they’re allowed already without you needing to do anything.
Another point is that app.url isn’t an origin — instead it’s a URL, potentially with a path. But origins don’t have paths. So you don’t actually want to be setting the value of $origin to app.url unless you’re certain that your app.url has no path (not even a trailing slash).
All that said, if you really want to get the exact behavior of the custom code in the question, you can set your $origin variable as a global variable and then set the allowedOrigins array like this:
return [
/*
|--------------------------------------------------------------------------
| Laravel CORS
|--------------------------------------------------------------------------
|
| allowedOrigins, allowedHeaders and allowedMethods can be set to array('*')
| to accept any value.
|
*/
'supportsCredentials' => true,
'allowedOrigins' => [$origin, 'http://app.localhost:3333'],
'allowedHeaders' => ['Content-Type', 'Accept', 'Authorization', 'X-Requested-With', 'Application'],
'allowedMethods' => ['POST', 'GET', 'OPTIONS', 'PUT', 'DELETE'],
'exposedHeaders' => ['Authorization'],
'maxAge' => 0,
]
Those are the complete config settings for doing the equivalent of the custom code in the question.
Given the allowedOrigins value above, the conditional logic laravel-cors follows is this:
if the Origin request-header value matches the value of $origin, then send back the response header Access-Control-Allow-Origin with its value set to the $origin value
else if the Origin request-header value is http://app.localhost:3333, then send back the response header Access-Control-Allow-Origin: http://app.localhost:3333
else don’t send back any Access-Control-Allow-Origin response header
That’s what you need if you want to allow cross-origin requests from either the value of $origin or http://app.localhost:3333 but not from any other origins.
It’s true that does something a bit different from what the custom code in the question does — in that the code in the question causes an Access-Control-Allow-Origin response header with the $origin value to be sent back even to origins that are not allowed.
But you don’t want to be doing that anyway. In the case of a request coming from an origin that’s not allowed, there’s no point in sending back any Access-Control-Allow-Origin header at all — because the absence of the Access-Control-Allow-Origin response header tells the browser “don’t allow frontend JavaScript code running at this origin to access responses from our server”.
Beyond that there’s no point in publicly leaking information about what any of the the allowed origins are — which is what you’d be doing if you sent a default Access-Control-Allow-Origin response header set to $origin, as the custom code in the question does.

Related

Laravel - Vue Axios CORS policy

The request
axios.get("https://maps.googleapis.com/maps/api/geocode/json?latlng="+this.currentLocation.lat+","+this.currentLocation.lng+"&key="+this.apiKey).then((response) => {
if (this.town === response.data.results[0].address_components[2].long_name){
return
}
else{
this.town = response.data.results[0].address_components[2].long_name
this.getSuggested();
this.getAllEvents();
}
}).catch(er => {
console.log(er)
})
When i'm trying to get the town of a location i get this error
Access to XMLHttpRequest at 'API Route' from origin 'http://localhost:8000' has been blocked by CORS policy: Request header field x-requested-with is not allowed by Access-Control-Allow-Headers in preflight response.
When i remove
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
from bootstrap.js, the request works just fine.
What exactly is the problem here?
Any server you’re sending a request to will, usually by default, reject it if it contains headers which are not CORS-safelisted.
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
This code tells axios to attach a custom header to every request. Ergo, your request is being sent with a non safelisted header, which the server won’t permit, so you receive the error.
To permit the request, the server must be configured to allow the custom header.

CSRF Token Name on Django Documentation is not Matching the Actual Name of the Variable in AJAX Header

I was struggling to send and recieve CSRF token, and I found, in the end, that Django was not able to get the token value because its name was different from the recommended one in its documentation. Why?
(I am doing AJAX on a HTTPS address and requests are cross-site.)
Django documentation recommends that I add token to AJAX header in following way:
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
Here, the name is X-CSRFToken, which somehow becomes HTTP_X_CSRFTOKEN.
On the other hand, Django is looking up the cookie under CSRF_COOKIE.
Line 278 in csrf.py of CsrfViewMiddleware:
csrf_token = request.META.get('CSRF_COOKIE')
if csrf_token is None:
# No CSRF cookie. For POST requests, we insist on a CSRF cookie,
# and in this way we can avoid all CSRF attacks, including login
# CSRF.
return self._reject(request, REASON_NO_CSRF_COOKIE)
I cannot change the variable name because I get this error:
Request header field CSRF_COOKIE is not allowed by Access-Control-Allow-Headers in preflight response.
So, I ended up changing the variable name in the source code from CSRF_COOKIE to HTTP_X_CSRFTOKEN. Are there any way to make this work?
(I do not do #csrf_exempt, so please do not recommend.)
The problem is not from Django, if you read closely here: https://docs.djangoproject.com/en/2.0/ref/csrf/#how-it-works you will understand how it works and what kind of logic they follow.
The problem is that you are not allowing the headers:
Request header field CSRF_COOKIE is not allowed by Access-Control-Allow-Headers in preflight response.
If you search for this ACAH you will find that you must edit your server config file to allow this kind of posts.
The other case is that you may not be sending properly the header and that's why it's looking for the cookie. In that case you can try adding this to your header:
xhr.setRequestHeader('X-CSRFToken': $('meta[name="token"]').attr('content') });

Method PUT is not allowed by Access-Control-Allow-Methods in preflight response vue-laravel App

I am new in vue. For ajax request I am using axios and for back-end I am using Laravel. Whenever I send a POST and GET request it's works fine. But while trying to send a PUT Request its showing Method PUT is not allowed by Access-Control-Allow-Methods in preflight response vue-laravel App. I had read lots of answer in Stackoverflow and github but none of them worked for me.
Here is the client side code:
axios.put('http://127.0.0.1:8000/api/photo/6', this.photo, { headers: getHeader() })
.then(response => {
console.log(response.data.message);
}).catch(err => {
this.errors = err.response.data.errors;
});
Here is CROSS middleware code:
//allowed client
//now only for localhost vue cli
$domains = ['http://localhost:8080'];
if(isset($request->server()['HTTP_ORIGIN'])){
$origin = $request->server()['HTTP_ORIGIN'];
if(in_array($origin, $domains)){
header('Access-Control-Allow-Origin: '.$origin);
header('Access-Control-Allow-Headers: Origin, Content-Type, Authorization');
}
}
return $next($request);
Try this in backend :
app/Http/Middleware/Cors.php
public function handle($request, Closure $next)
{
return $next($request)
->header('Access-Control-Allow-Origin', '*')
->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE,
OPTIONS');
}
app/Http/Kernel.php $routedMiddleware array add
'cors' => \App\Http\Middleware\Cors::class,
Route/api.php
Route::group(['middleware' => 'cors'], function () {
Route::put('/v1/employees', 'Employees#store');
});
Particular GET, HEAD and POST HTTP requests do not trigger the CORS preflight OPTIONS check. They are called Simple Requests. They only send a subset of HTTP headers along. Any other additional header, like an Authorization header (think JWT authentication), will trigger a preflight OPTIONS request. The details on this can be found on https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
I think the easiest way for you to work with this, is to use the laravel-cors package on the backend (see https://github.com/barryvdh/laravel-cors).
While doing development, you can simply be very permissive by setting allowedOrigins, allowedHeaders and allowedMethods to *. Later on, during testing and subsequently production, you should tune it to be more strict. By that time you'll have a much better hang of CORS concepts, and then you can configure it fully to your needs.

Using wildcard for subdomain in Access-Control-Allow-Origin

I'm using Express for my website and using credential xhr. I want to request to http://example.com from http://admin.example.com or http://service1.example.com, and this is my Access-Control-Allow-Origin part in express server:
// CORS
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', 'http://*.example.com');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,Content-Type');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE');
next();
});
But when I try credential xhr from http://admin.example.com to http://example.com, it fails with:
Fetch API cannot load http://example.com/api/v1/authentication/signin.
Response to preflight request doesn't pass access control check: The
'Access-Control-Allow-Origin' header has a value 'http://*.example.com'
that is not equal to the supplied origin. Origin
'http://admin.example.com' is therefore not allowed access. Have the
server send the header with a valid value, or, if an opaque response
serves your needs, set the request's mode to 'no-cors' to fetch the
resource with CORS disabled.
Looks like it causes from browser didn't understood what exactly *.example.com means, and refuse the request.
I want to request from these domains:
example.com
admin.example.com
service1.example.com
service2.example.com
[anything].example.com
I'm using Fetch API for XHR, and set credentials: true. Is there a something that I missed? Any advice will very appreciate it.
I agree with Derric's comment. The other thing though is that origin headers can be spoofed, so this is not a secure solution.
app.use(function (req, res, next) {
if (req.headers.origin.endsWith('example.com')) {
res.setHeader('Access-Control-Allow-Origin', 'http://' + req.headers.origin)
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,Content-Type')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE')
}
next()
})
First off, IIRC; express documentation explicitly asks you not to use
lambda expression for the middlewares.
Coming to the CORS issue, a wildcard subdomain is not valid in the context. The support was added pretty recently (in May '16), and until then, the CORS header must be an exact match of the domain name.
You can however, process your req.hostname value and add that to the response header:
// CORS
app.use(function (req, res, next) {
if (req.hostname.endsWith('example.com')) {
res.setHeader('Access-Control-Allow-Origin', 'http://' + req.hostname)
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,Content-Type')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE')
}
next()
})
Adding another small adjustment here. We should also consider "protocol":
app.use(function (req, res, next) {
if (req.headers.origin.endsWith('example.com')) {
res.setHeader('Access-Control-Allow-Origin', req.protocol + '://' + req.headers.origin)
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,Content-Type')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE')
}
next()
})
All the previous answers suffer from a vulnerability, exposing the api to anyone that registers a badsiteexample.com domain name. i guess they tried to include both the main domain and subdomains in the check, but there is no need as if the api is on the main domain that would not be a cors request. and if you did need it, you should use two separate conditions for main domain and subdomains, or a regex.
req.headers.origin.endsWith('.example.com') || req.headers.origin == 'example.com'
req.headers.origin.match(/(\.|^)example\.com$/)

Restify Delete Method

CORS is starting to fry my brain a bit. Everything is good now, apart from one method. I'm building an app with backbone on the frontend and node.js/restify on the backend. The server.coffee looks like this:
server.get '/todos', todos.find_all
server.get '/todos/:id', todos.find_by_id
server.del '/todos/:id', todos.delete
Whenever a model in backbone calls destroy however I get this rather annoying error:
MLHttpRequest cannot load http://localhost:8080/todos/. Method DELETE is not allowed by Access-Control-Allow-Methods.
I read about this a bit and using restify done the following:
unknownMethodHandler = (request, response) ->
if(request.method.toLowerCase() == 'options')
allowHeaders = ['Accept', 'Accept-Version', 'Content-Type', 'Api-Version']
if(response.methods.indexOf('OPTIONS') == -1) then response.methods.push('OPTIONS')
response.header 'Access-Control-Allow-Credentials', true
response.header 'Access-Control-Allow-Headers', allowHeaders.join(', ')
response.header 'Access-Control-Allow-Methods', ['GET', 'DELETE', 'TEST!']
response.header 'Access-Control-Allow-Origin', request.headers.origin
response.send 204
else
response.send new restify.MethodNotAllowedError()
server.on 'MethodNotAllowed', unknownMethodHandler
But even still, I get this as the response header:
HTTP/1.1 204 No Content
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: X-Api-Version, X-Request-Id, X-Response-Time
Connection: Keep-Alive
Date: Mon, 04 Feb 2013 12:24:25 GMT
Server: restify
X-Request-Id: fbd4e15a-a22e-48b6-bf5c-a46b94926748
X-Response-Time: 0
I just don't get what I'm doing wrong!
If you're expecting a response, you should use a '200' response code, not a 204 as that's a No Content response. See the W3C Spec for the details
9.7 DELETE
The DELETE method requests that the origin server delete the resource identified by the Request-URI. This method MAY be overridden
by human intervention (or other means) on the origin server. The
client cannot be guaranteed that the operation has been carried out,
even if the status code returned from the origin server indicates that
the action has been completed successfully. However, the server SHOULD
NOT indicate success unless, at the time the response is given, it
intends to delete the resource or move it to an inaccessible location.
A successful response SHOULD be 200 (OK) if the response includes an entity describing the status, 202 (Accepted) if the action has not
yet been enacted, or 204 (No Content) if the action has been enacted
but the response does not include an entity.
If the request passes through a cache and the Request-URI identifies one or more currently cached entities, those entries SHOULD
be treated as stale. Responses to this method are not cacheable.
You're seeing the Access-Control-Allow-Origin: * in the response header. This is coming from the .../restify/lib/router.js preflight() method. The comment states "user will need to defined their own .opts handler".
Use server.opts method to wirte your own handler for OPTIONS request.
Below is the example you can use.
Also tell me if you are using set-credentials flag to true while making request from the browser. This handle in that case would have to respond with access cookies.
In the example below, I am returning the allowed origin for exact match.
You can tweak it to be substring match also. But always return the exact value as found in request header origin in the response header 'Access-Control-Allow-Origin'. Its a good practice.
server.opts('/api/(.)*', (req, res) => {
const origin = req.header('origin');
const allowedOrigins = ['example.com', 'example.org'];
if (allowedOrigins.indexOf(origin) === -1) {
//origin is not allowed
return res.send(405);
}
//set access control headers to allow the preflight/options request
res.setHeader('Access-Control-Allow-Origin', header);
res.setHeader('Access-Control-Allow-Headers', 'Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version');
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
// Access-Control-Max-Age header catches the preflight request in the browser for the desired
// time. 864000 is ten days in number of seconds. Also during development you may want to keep
// this number too low e.g. 1.
res.setHeader('Access-Control-Max-Age', 864000);
return res.send(200);
});
Just set header res.setHeader('Access-Control-Allow-Methods', '*');
Here is the answer: https://github.com/mcavage/node-restify/issues/296#issuecomment-12333568

Resources