6

I created the project using visual studio 2022 and chose aspnet core 6 web api template. I'm trying to set a cookie in the browser but it seems I am missing something as it is not setting anything under Network > Application > Cookies

My frontend is a react app. No extra library just yet. It's the default project after running the npx create-react-app <project-name> command.

I can call the /weatherforecast endpoint no problem. But for some reason, it is not setting the cookie.

frontend call

const getData = async () => {
  await axios.get("/weatherforecast");
};

WeatherForecastController.cs

public IActionResult Get()
{
  Response.Cookies.Append("myjwt", "ABCDE", new CookieOptions
  {
    Secure = true,
    HttpOnly = true,
    SameSite = SameSiteMode.None
  });

  return Ok();
}

Program.cs

var builder = WebApplication.CreateBuilder(args);

const string AllowAllHeadersPolicy = "AllowAllPolicy";

builder.Services.AddCors(options =>
{
    options.AddPolicy(AllowAllHeadersPolicy,
        builder =>
        {
            builder
                .WithOrigins(new[] { "http://localhost:3000" })
                .AllowAnyMethod()
                .AllowAnyHeader()
                .AllowCredentials();
        });
});

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

app.UseCors(AllowAllHeadersPolicy);

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

The end goal of what I am trying to do is to store the refresh token in the cookie with the HttpOnly=true

What I tried:

  • I am using insomnia to test the endpoint. Working perfectly! Setting the cookie too.
  • Found this question but no clear answer.
Boy Pasmo
  • 8,021
  • 13
  • 42
  • 67
  • Cookies can't be set in http localhost (ignored by the browser). Try to add SSL for localhost in react make it has https:locahost:port and try to set it. – mrsridhar Jul 29 '22 at 04:10
  • @mrsridhar is it possible to set is to ssl when in development? – Boy Pasmo Aug 01 '22 at 16:08

3 Answers3

6

Below view is what you technically trying to do:

enter image description here

You can do that in multiple ways

Option 1: Build-in middleware

services.AddSession(options =>
    {
        options.Cookie.Name = ".AdventureWorks.Session";
        options.IdleTimeout = TimeSpan.FromSeconds(10);
        options.Cookie.IsEssential = true;
    });

and then you have to instruct to use middleware (this is something that you seems to be missing)

app.UseSession();

Option 2: Manually instruct your API that cookie need to be added:

public HttpResponseMessage Get()
{
    var resp = new HttpResponseMessage();

    var cookie = new CookieHeaderValue("session-id", "12345");
    cookie.Expires = DateTimeOffset.Now.AddDays(1);
    cookie.Domain = Request.RequestUri.Host;
    cookie.Path = "/";

    resp.Headers.AddCookies(new CookieHeaderValue[] { cookie });
    return resp;
}

Microsoft provides example with MessageHandler (for ASP.Net) and DelegatingHandler (for API inbound or outbound connections in Core - can be attached to HttpClientFactory Implement DelegatingHandler in ASP.NET Core 5.0 Web API?). You also can use normal MiddleWare to intersect and update reguest/response data (example is here ASP .NET Core webapi set cookie in middleware)

Pseudo-code example with DelegatingHandler

using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;

public class SessionIdHandler : DelegatingHandler
{
    public static string SessionIdToken = "session-id";

    async protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        string sessionId;

        // Try to get the session ID from the request; otherwise create a new ID.
        var cookie = request.Headers.GetCookies(SessionIdToken).FirstOrDefault();
        if (cookie == null)
        {
            sessionId = Guid.NewGuid().ToString();
        }
        else 
        {
            sessionId = cookie[SessionIdToken].Value;
            try
            {
                Guid guid = Guid.Parse(sessionId);
            }
            catch (FormatException)
            {
                // Bad session ID. Create a new one.
                sessionId = Guid.NewGuid().ToString();
            }
        }

        // Store the session ID in the request property bag.
        request.Properties[SessionIdToken] = sessionId;

        // Continue processing the HTTP request.
        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);

        // Set the session ID as a cookie in the response message.
        response.Headers.AddCookies(new CookieHeaderValue[] {
            new CookieHeaderValue(SessionIdToken, sessionId) 
        });

        return response;
    }
}

Middleware msdn https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-5.0

Maksym
  • 820
  • 1
  • 8
  • I have my `set-cookie` in the response header but its not appearing in the application>cookies tab and I can't seem to send it back to the server. I'm using axios with params of `withCredentials` set to `true`. Am I missing something? – Boy Pasmo Aug 01 '22 at 16:07
  • @BoyPasmo Try out option 2 with simple Get method that does set cookie explicitly. As for axios https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials has no effect on same site request. This policy indicates whether or not cross-site Access-Control requests should be made using credentials such as cookies (this is not the same as to make sure cookie is sent). – Maksym Aug 02 '22 at 10:09
1

You can set the cookie in your app.Use section. It can look something like this:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.Use(async (context, next) =>
    {
        var cookieOpt = new CookieOptions()
        {
            Path = "/",
            Expires = DateTimeOffset.UtcNow.AddDays(1),
            IsEssential = true,
            HttpOnly = false,
            Secure = false,
        };
        context.Response.Cookies.Append("MyCookieId", "SomeCookieID", cookieOpt);

        await next();
    });

}

This will apply the cookie to all calls being made. From there you can request that cookie in the response headers and read from it whatever you need.

You seem to be ok with setting the cookie at the beginning and not after a call is made. If you need to do that comment and I can show you that way too. Its simply modifying the response message back to include a cookie within the response header.

mathis1337
  • 1,426
  • 8
  • 13
1

I just solved my problem. I thought you only need to add withCredentials: true (using axios btw) whenever you want to send the cookie back to the server. Turns out, you need to add that property if you want to get the cookie from the server as well. Now the browser is storing the cookie in Application > Cookies. Appreciate all the help <3

Boy Pasmo
  • 8,021
  • 13
  • 42
  • 67