3

For testing purposes, two web apps are set up, a "client" app (localhost) and a server app (Azure web app). The client sends an AJAX request to the server and receives a cookie in response. Then it makes another AJAX call to the server, but there's no cookie in the request, it's missing.

Here's the server configuration (CORS setup; https://localhost:44316 is my "client" URL):

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(o => {
            o.AddPolicy("policy1", builder =>
                builder.WithOrigins("https://localhost:44316")
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                    .AllowCredentials());
        });

        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseHttpsRedirection();

        app.UseRouting();

        app.UseCors("policy1");

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

Here's the first controller, returning the cookie:

[Route("api/[controller]")]
[ApiController]
public class AController : ControllerBase
{
    [HttpPost]
    public IActionResult Post()
    {
        var cookieOptions = new CookieOptions
        {
            HttpOnly = true,
            Expires = DateTime.Now.AddMinutes(10),
            SameSite = SameSiteMode.None
        };
        Response.Cookies.Append("mykey", "myvalue", cookieOptions);

        return Ok();
    }
}

Here's the second controller, which should receive the cookie (but it doesn't):

[Route("api/[controller]")]
[ApiController]
public class BController : ControllerBase
{
    [HttpPost]
    public IActionResult Post()
    {
        var x = Request.Cookies;

        return Ok(JsonConvert.SerializeObject(x));
    }
}

And here's the calling script from the "client" (first and second call, respectively):

function Go()
{
    $.ajax({
        url: 'https://somewebsite.azurewebsites.net/api/a',
        type: 'post',
        xhrFields: {
            withCredentials: true
        },
        success: function (data, textStatus, jQxhr)
        {
            console.log(data);
        },
        error: function (jqXhr, textStatus, errorThrown)
        {
            console.log(errorThrown);
        }
    });
}

function Go2()
{
    $.ajax({
        url: 'https://somewebsite.azurewebsites.net/api/b',
        type: 'post',
        xhrFields: {
            withCredentials: true
        },
        success: function (data, textStatus, jQxhr)
        {
            console.log(data);
        },
        error: function (jqXhr, textStatus, errorThrown)
        {
            console.log(errorThrown);
        }
    });
}

Does anyone have an idea what could be the problem here?

Marko
  • 1,502
  • 5
  • 21
  • 40
  • You want cookies to be sent to a 3rd party or you making a request to the same domain/subdomain? – epascarello Aug 27 '21 at 21:11
  • "client" web is one site, and server web is another one, there's no 3rd party involved. – Marko Aug 28 '21 at 10:00
  • If my reply is helpful, please accept it as answer(click on the mark option beside the reply to toggle it from greyed out to fill in.), see https://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work – Jason Pan Aug 30 '21 at 08:05
  • My understanding is that response headers from calls to the `Go` functions should contain the cookies, so you shouldn’t need any MVC structure to receive them from the app running on Azure? – thebadgateway Aug 30 '21 at 21:32
  • BController has to receive those cookies when called from Go2 function, but that's not always the case (I'm testing with latest Chrome, Firefox, Edge and Opera). I tried various settings for cookies in those browsers, but it didn't help. Anyway, I'm trying to understand and solve the problem, not just casually get something that "seems to work". – Marko Aug 31 '21 at 05:10

3 Answers3

3

As this document said :

Cookies that assert SameSite=None must also be marked as Secure

But you didn't, so use this instead:

var cookieOptions = new CookieOptions
            {
                HttpOnly = true,
                Expires = DateTime.Now.AddMinutes(10),
                SameSite = SameSiteMode.None,
                Secure = true
            };

And this is my test result:

enter image description here

Tiny Wang
  • 10,423
  • 1
  • 11
  • 29
  • What are your browser settings? It seems that my browsers accept or not the cookie depending on the setting for 3rd part cookies? – Marko Aug 31 '21 at 19:27
  • Edge browser with default setting, I didn't do any setting on the browser. – Tiny Wang Sep 01 '21 at 01:11
  • Then change a browser to test? – Tiny Wang Sep 01 '21 at 01:23
  • I have tested 4 different browsers and it seems that the problem is that browsers (although there are some slight differences between them) consider my cookies to be 3rd party, and the future for 3rd party cookies doesn't seem bright, ie. already today, by default they are not alllowed in most browsers. – Marko Sep 04 '21 at 15:44
  • @Marko thanks for your reply and I saw the answer above from Gary, that also made me bright : ) – Tiny Wang Sep 06 '21 at 01:31
1

I quite like the style of what you are doing here in terms of an SPA getting cookies from an API. Some recommendations below, based on experience at dealing with these issues.

PROBLEM

You are calling from the browser to an API in a different domain, meaning the cookie is third party and modern browsers will drop it aggressively.

  • Web Origin: localhost:44316
  • API Domain: myazurewebapp.com

SameSite=None is the theoretical solution from standards docs but these often do not explain current browser behaviour:

  • You have to set the Secure property, as Jason Pan says
  • But it will still not work in the Safari browser and maybe some others
  • Expect cross site cookies to be dropped in all browsers in the near future

SOLUTION

The preferred option is to design hosting domains so that only first party cookies are used, and many software companies have done this. It can be done by running the API in a child or sibling domain of the web origin:

On a developer PC you can do this simply by updating your hosts file. Note also that you can run web and API components on different ports and they will remain same site:

127.0.0.1 localhost www.example.com api.example.com
:1        localhost

The browser will then still be making CORS requests, but will consider the cookie issued by the API to be in the same site as the web origin. You can then also change the cookie settings to use SameSite=strict, for best security.

FURTHER INFO

At Curity we have published some recent articles on web security that are closely related to your question, since secure cookies used in OpenID Connect security have also had to deal with dropped cookie problems:

Gary Archer
  • 22,534
  • 2
  • 12
  • 24
0

You should know AddTransient, AddScoped and AddSingleton first. Below post will useful to you.

AddTransient, AddScoped and AddSingleton Services Differences

And you need use AddSingleton, and you will get the cookie value by key.

Offical blogs: How to work with cookies in ASP.NET Core

It works for me, you can find sample code in the blogs I provided.

My Test

1. test code

enter image description here

enter image description here

enter image description here

enter image description here

2. test result in another controller.

enter image description here

Jason Pan
  • 15,263
  • 1
  • 14
  • 29
  • Thanks for your post, but I don't understand its point. You have the same commands I use to set and read cookies. You don't use AJAX at all, which is essential for a POC, because it behaves differently than plain MVC (that's why I have to use the "withCredentials" piece of code). – Marko Aug 30 '21 at 21:16