scottoffen / grapevine-legacy

Embedding A REST Server In Your Application Should Be Simple
http://scottoffen.github.io/grapevine-legacy/
Apache License 2.0
209 stars 50 forks source link

Implement CORS Support #86

Closed scottoffen closed 3 years ago

scottoffen commented 8 years ago

Given that CORS is disabled by default when using HttpListener, here is a work around until I can provide a more elegant solution.

Implementing CORS

You can implement access control via CORS for all requests using a BeforeRouting delegate. Add additional filtering logic if you only want to the policy to vary based on the request. This avoids the need to add these lines to individual routes, and allows you to manage the policy for the entire server in a single location.

server.Router.BeforeRouting += MyCorsPolicy;

Following are several ways you can define your policy.

Using the Wildcard

When using the wildcard value, it is important to remember that:

For requests without credentials, the literal value "*" can be specified, as a wildcard; the value tells browsers to allow requesting code from any origin to access the resource. Attempting to use the wildcard with credentials will result in an error. source

void MyCorsPolicy(IHttpContext context)
{
    context.Response.AddHeader("Access-Control-Allow-Origin", "*");
    context.Response.AddHeader("Access-Control-Allow-Headers", "X-Requested-With");
}

Additionally, when specifying Access-Control-Allow-Headers header:

The simple headers, Accept, Accept-Language, Content-Language, Content-Type (but only with a MIME type of its parsed value (ignoring parameters) of either application/x-www-form-urlencoded, multipart/form-data, or text/plain), are always available and don't need to be listed by this header. source

Single Origin

When specifying a single origin, it is important to remeber that:

Two URLs have the same origin if the protocol, port (if specified), and host are the same for both. source

void MyCorsPolicy(IHttpContext context)
{
    context.Response.AddHeader("Access-Control-Allow-Origin", "http://localhost:1234/");
    context.Response.AddHeader("Vary", "Origin");
}

Additionally, the Vary header should be provided:

If the server sends a response with an Access-Control-Allow-Origin value that is an explicit origin (rather than the "*" wildcard), then the response should also include a Vary response header with the value Origin — to indicate to browsers that server responses can differ based on the value of the Origin request header. source

Dynamic Origin

Limiting the possible Access-Control-Allow-Origin values to a set of allowed origins requires code on the server side to check the value of the Origin request header, compare that to a list of allowed origins, and then if the Origin value is in the list, to set the Access-Control-Allow-Origin value to the same value as the Origin value. source

void MyCorsPolicy(IHttpContext context)
{
    var domain = context.Request.UrlReferrer?.ToString();

    if (!string.IsNullOrWhiteSpace(domain) && ValidOrigins.Contains(domain))
    {
        context.Response.AddHeader("Access-Control-Allow-Origin", domain);
        context.Response.AddHeader("Vary", "Origin");
    }
}

IEnumerable<string> ValidOrigins
{
    get
    {
        yield return "http://mydomain.org/";
        yield return "http://localhost:1234";
    }
}

Dynamic Routes

If only some routes should allow CORS, you can put whatever logic you want to see in the delegate based on the incoming request. Just remember to follow the rules outlined above!

wizicer commented 7 years ago

Before this feature be implemented, in case someone need CORS support. Could add following before sending response.

context.Response.Headers["Access-Control-Allow-Origin"] = "*";
DavidFlamini commented 7 years ago

I couldn't get around enabling CORS together with authentication, any ideas?

lacu commented 7 years ago

I have not been able to solve the CORS problem, I have added the header to the response but it does not work this is my code in the app

context.Response.Headers["Access-Control-Allow-Origin"] = "*";
context.Response.AddHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
context.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With");
context.Response.AddHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS,PATCH");
context.Response.AddHeader("Access-Control-Allow-Origin", "*");

and this is the result in browser image

Someone find a solution??? Thanks!!!

DavidFlamini commented 7 years ago

@lacu I just checked my code from a couple of months ago, this is all that I had to add for CORS to work in my setup:

context.Response.AddHeader("Access-Control-Allow-Origin", "*");
context.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With");

If you are doing auth then it doesn't work, I had to remove auth for CORS to work:

This doesn't work with CORS:

server.Advanced.AuthenticationSchemes = System.Net.AuthenticationSchemes.Basic;

If you find some way to get both working together please share, I'm interested :)

lacu commented 7 years ago

@DavidFlamini , I will have to put this line in each route like this?

[RestRoute(HttpMethod = HttpMethod.PUT, PathInfo = @"^/ticket(\?[^/]*)?$")]
[RestRoute(HttpMethod = HttpMethod.PUT, PathInfo = @"^/ticket:(\?[^/]*)?$")]
[RestRoute(HttpMethod = HttpMethod.PUT, PathInfo ="/ticket")]
[RestRoute(HttpMethod = HttpMethod.OPTIONS, PathInfo = "/ticket")]
public IHttpContext Ticket(IHttpContext context)
{
    context.Response.AddHeader("Access-Control-Allow-Origin", "*");
    context.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With");
DavidFlamini commented 7 years ago

@lacu If I remember correctly, unless you are setting it upstream, then yes. You could have an upstream route set those for you, too. Please check here: https://sukona.github.io/Grapevine/en/getting-started.html "Responding To Requests"

DavidFlamini commented 6 years ago

Hi, today I wanted to test locally with both CORS + Basic Auth without configuring a proxy and run into my own post, I ended up parsing the auth header myself like so:

static private bool IsAuthorized(string AuthorizationHeader)
{
    try
    {
        var identity = Encoding.UTF8.GetString(Convert.FromBase64String(AuthorizationHeader.Split(" ")[1])).Split(":");
        var name = identity[0];
        var password = identity[1];

        if (name == "user" && password == "password")
        {
            return true;
        }

        return false;
    }
    catch (Exception ex )
    {

        return false;
    }
}
[RestRoute(PathInfo = "/api/v1/services")]
public IHttpContext Services(IHttpContext context)
{
    context.Response.ContentType = ContentType.JSON;

    if (IsAuthorized(context.Request.Headers["Authorization"]))
    {
        // Do stuff and send some response

    }
    else
    {
        context.Response.StatusCode = Grapevine.Shared.HttpStatusCode.Unauthorized;
        context.Response.SendResponse("{\"Error\": \"Unauthorized\"}");
    }
    return context;
}
scottoffen commented 6 years ago

I just finished updating the text of this issue to reflect the suggestions here for implementing CORS. Thanks for all of your input, guys!

virtruvio commented 1 year ago

Hi Scott -- read your notes for 5.0 beta 1 respecting the UseCorsPolicy method. I'm finding that the several overloads that accept an Allow-Origin add a trailing slash to the transmitted header value. (e.g. "https://my.domain.com/" rather than "https://my.domain.com" . Current chrome transmits a non-slashed origin (e.g. "https://my.domain.com" which causes the CORS preflight to fail as a mismatched origin. I've worked around this by adding an async Task method that can be called to add Response headers during OnRequestAsync event, and adding my own headers via the .AddHeader Response method. This works to match the origin & supplied Allow-Origin header. But this isn't likely to be the best practice for overcoming. Am I doing something wrong with UseCorsPolicy? Also, does the UseCorsPolicy respond to OPTIONS requests during preflight with a HTTP_OK? It doesn't appear to do so. I assume it should be handled with an OPTIONS route?