cross-domainsetcookiesamesitecookie-httponly

Cant set cookie when CORS request comes from iPhone. Works for windows and mac users


The Setup / Environment

Client: (React.js, vs code, axios)

POST request to backend server to set auth cookie. On every refresh Ill verify the cookie by a GET request to the auth backend server. Every axios call is done with the "withCredentials:true" property set.

Backend (.net 6 - miminal API app written in c# and visual studio 2022.)

Set the cookie to "httpOnly", "sameSite=none" and "secure".

What Works

I have tested the flow on Windows 10, 11 + Mac computer and here everythink works fine. The cookie is set and I can login to my app. The setCookie header is present here.

The problem

When I try to login from my iPhone with the latest IOS 15.4 it is not working (though it should be supported according to this https://caniuse.com/same-site-cookie-attribute).

The cookie is not set and the "getcookie" request returns null and Ill return Unauthorized.

Code:

Frontend (React js):

//run "npx create-react-app iphone-cors-test-app" and add a useEffect hook in App component //like this.

useEffect(() => {

var urlToBackendService= "https://829f-217-211-155-130.eu.ngrok.io";
axios({
  url: baseURL + '/setcookie',
  method: 'post',
  withCredentials: true,
  data: {
    Email: 'ineedhelp@please.com',
    Password: 'loveu'
  }
}).then(() => {
  axios({
    url: baseURL + '/getcookie',
    method: 'get',
    withCredentials: true
  }).then((resp) => {
    var cookieValue = resp.data;
    console.clear();
    console.log(`cookie value: ${cookieValue}`);
    alert(`cookie value: ${cookieValue}`);
  })
});

Backend (c# .net 6, visual studio 2022):

//.net core web api template > minimal api (no controllers) enable https.

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        builder =>
        {
            builder.WithOrigins("https://nameofmyreactapp.azurewebsites.net")
            .WithHeaders("accept", "content-type", "origin")
            .WithMethods("GET", "POST", "OPTIONS")
            .AllowCredentials();
        });
});
builder.Services.AddHttpContextAccessor();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseCors();
app.MapPost("/setcookie", async ([FromServices] IHttpContextAccessor httpContextAccessor, LogonRequest logonRequest) =>
{
    return await Task.Run<IResult>(() =>
    {
        //login user and get an accesstoken. set accesstoken to a httpOnly cookie.
        var accessToken = "newly generated jwt access token";

        httpContextAccessor.HttpContext!.Response.Cookies.Append(key: "accesstoken", accessToken, new CookieOptions
        {
            HttpOnly = true,

            /*This should work with an iPhone with ios 15.4 (https://caniuse.com/same-site-cookie-attribute).*/
            SameSite = SameSiteMode.None,
            Secure = true
        });
        return Results.Ok();
    });
});

app.MapGet("/getcookie", async ([FromServices] IHttpContextAccessor httpContextAccessor) =>
{
    return await Task.Run<IResult>(() =>
    {
        var accesstoken = httpContextAccessor.HttpContext!.Request.Cookies["accesstoken"];
        return string.IsNullOrEmpty(accesstoken)
        ? Results.Unauthorized()
        : Results.Ok(accesstoken);
    }
    );
});

app.Run();

public record LogonRequest(string Username, string Password);

Screenshots: setCookie request. enter image description here

getCookie request. enter image description here Please help me.

UPDATE If you want to test this with your phone I use ngrok. Sign up and follow directions. Download ngrok.exe and go to that folder in your terminal. Then start your backend localhost and type "ngrok http + your localhost address".

Example: "ngrok http https://localhost:7200" enter image description here

Then hit enter and you will get a public url to your localhost. enter image description here

Replace the client axios url (urlToBackendService) with this new public url and publish your react app to to cloud (or create another ngrok user and do the same think for the client) or if you have browserstack account or if you have another idé how to test this.


Solution

  • I just want to clarify the solution here.

    (Solution 2 is the best practice version here if you just want the short version)

    Solution 1

    I knew that it probably should work if my two sites where hosted on the same domain but since I was in early stages in development and I didnt want to create custom domains just yet, and I also had read documentation that interpreted that is should work anyways I continued with this solution.

    So my first solution (which is not idéal since localstorage is not as safe as a secure httponly cookie) was to change my auth server to be able to receive accesstoken via headers and cookies, I also made sure to return tokens in the response so I could store the tokens in localstorage. Flow example:

    1. login with username & password and send form to auth server.
    2. Get tokens from auth server response and store in a local storage variable.
    3. Send a request to auth server with accesstoken header provided by localstorage variable.

    Solution 2 (Best practice version)

    Cred to my fellow user @AndrewLeonardi and the original post from @RossyBergg which could confirmed what I expected, that it will work if I just put the two services on the same domain. I ended up with this solution:

    AuthService url: https://auth.domain.se

    Client url: https://domain.se

    The httpOnly secure cookies was now working properly and I was able to get, set & remove the cookie in the auth server. The header & localstorage implementation from "Solution 1" could be removed.