Issues with HttpOnly Cookies in ASP.NET Core Web API and ReactJS Integration

Problem Summary

I am currently enhancing my skills in ASP.NET Core Web API by working on implementing a Refresh/Access token-based authentication system with a ReactJS application. While testing with tools like ThunderAPI and Insomnia, everything functions correctly. However, when I attempt to use the same process in my React app, it doesn’t behave as expected.

Login Endpoints Implementation

Here is my frontend code for logging in:

export const logIn = async (credentials) => {
    const result = await fetch(`${API_BASE_URL}/api/auth/signin`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(credentials)
    });
    if (!result.ok) throw new Error('Login failed.');
    const data = await result.json();
    localStorage.setItem('token', data.accessToken);
    return data;
};

And the server-side implementation looks like this:

[HttpPost]
[Route("signin")]
public async Task<IActionResult> SignIn([FromBody] LoginData loginRequest)
{
    var user = await _userManager.FindByNameAsync(loginRequest.Username);

    if (user == null || !await _userManager.CheckPasswordAsync(user, loginRequest.Password))
        return BadRequest("Invalid credentials.");

    var refreshToken = _tokenService.GenerateRefreshToken(user);
    Response.Cookies.Append("refresh_token", refreshToken, new CookieOptions()
    {
        HttpOnly = true,
        Secure = false,
        Expires = DateTime.UtcNow.AddDays(7)
    });

    var accessToken = _tokenService.GenerateAccessToken(user);
    return Ok(new { AccessToken = accessToken });
}

Current Issue

The React app successfully hits the endpoint, receives the access token, but the HttpOnly refresh token isn’t being sent back to the browser. I suspect this could be linked to my CORS settings, or how I’m processing the cookies and bearer tokens.

CORS Configuration

Here’s how I’ve set up my CORS:

public static void ConfigureCors(this IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddPolicy("MyCorsPolicy", builder =>
        {
            builder.WithOrigins("http://localhost:5173")
                   .AllowAnyMethod()
                   .AllowAnyHeader()
                   .AllowCredentials();
        });
    });
}

Implementation Steps

builder.Services.ConfigureCors();
app.UseCors("MyCorsPolicy");

JWT Authentication Configuration

In my Program.cs, I have:

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidIssuer = builder.Configuration["JWT:Issuer"],
        ValidateAudience = false,
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(builder.Configuration["JWT:Key"])),
        ValidateLifetime = true,
    };
});

Conclusion

The intention behind this setup was to enhance security by utilizing both the access and refresh tokens for authentication. I believe my understanding of HttpOnly cookies and ReactJS may be lacking in this area. Any suggestions, insights, or resources to help resolve this problem would be greatly appreciated. Thank you!

Hi Hermione_Book,

It looks like you're making good progress on integrating ASP.NET Core Web API with a ReactJS frontend. HttpOnly cookies can indeed be tricky when it comes to cross-origin requests. Here are a few steps to help resolve the issue:

  • CORS Configuration: Make sure that your CORS policy includes .AllowCredentials(), which you already have. This is necessary when the client needs to send cookies along with the request, ensuring they are correctly processed.
  • Use SameSite Cookies: Although HttpOnly cookies are not accessible via JavaScript, SameSite=None; Secure should be set for cross-site requests in secure contexts. Modify your cookie options as follows:
    Response.Cookies.Append("refresh_token", refreshToken, new CookieOptions
      {
          HttpOnly = true,
          Secure = true, // Use true in production
          Expires = DateTime.UtcNow.AddDays(7),
          SameSite = SameSiteMode.None // Enable cross-site cookie
      });
      
    This should help your HttpOnly cookie be accepted by the browser.
  • Frontend Configuration: Ensure that when making requests from your React app, you include credentials by adding credentials: 'include' in the fetch options like so:
    const result = await fetch(`${API_BASE_URL}/api/auth/signin`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(credentials),
          credentials: 'include'
      });
      
    This will send the appropriate cookies with the request.
  • Check Network Security: If testing locally, ensure your server is set to allow non-secure cookies by temporarily disabling secure cookies (though this should be enabled for production).

By configuring your cookies correctly and ensuring they're sent with your requests, you should find that your authentication tokens are handled smoothly.

Hope this helps! If you encounter further issues, feel free to share more details.

— David Grant

In your setup, it appears you've encountered a common issue when handling HttpOnly cookies for authentication between ASP.NET Core Web API and ReactJS.

Here are some additional steps and considerations to help refine your implementation:

  • Secure Contexts: As mentioned, for SameSite=None cookies, the Secure attribute should be set to true to allow cross-origin cookies. Since this isn’t possible in development without HTTPS, make sure to test on a secure server.
  • Verify API and Client Base URLs: Double-check that your CORS configuration and the URLs used in the React application are consistent. Any dissonance might cause cookies not to be recognized due to origin mismatches.
  • Handling of Tokens: While the access token is stored in localStorage, consider maintaining compliance with best security practices by managing it similarly to the refresh token through cookies. Only use localStorage if your access scope is minimal and low-risk.
  • Inspect Cookie Header: Utilize developer tools in your browser to examine if cookies are being passed and set correctly. Check the 'Network' tab during the sign-in process to confirm the presence of the Set-Cookie header, verifying that it contains the expected properties.
  • Diagnostics and Logging: Implement extensive logging on both the server and client sides to capture key interactions. This helps identify any discrepancies or failures in the request/response lifecycle.
  • Review Browser Restrictions: Modern browsers might impose restrictions that require specific configurations (like using HTTPS). Consider consulting your browser's requirements for cross-origin resource sharing and cookie handling.

By ensuring these points are meticulously configured, you should improve the reliability of your authentication mechanism in integrating your ASP.NET Core Web API with a React application. If you run into further issues, sharing specific error messages or headers seen in the browser might shed more light on the challenge.

Hey Hermione_Book,

HttpOnly cookies in React can be challenging due to their inherent security restrictions. Here's a streamlined approach to tackle the issue:

  • Ensure Correct CORS: You've configured .AllowCredentials() in CORS, which is crucial. Make sure both frontend and backend are aligned in terms of URL origins.
  • Update Cookie Settings: Modify your cookie settings to handle cross-origin requests correctly:
    Response.Cookies.Append("refresh_token", refreshToken, new CookieOptions
    {
        HttpOnly = true,
        Secure = true, // Use true for HTTPS
        Expires = DateTime.UtcNow.AddDays(7),
        SameSite = SameSiteMode.None
    });
    
  • Include Credentials in Fetch: Modify your fetch call to include credentials:
    const result = await fetch(`${API_BASE_URL}/api/auth/signin`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(credentials),
        credentials: 'include'
    });
    
  • Test Secure Connections: For development, use HTTPS to ensure that cross-origin cookies work. Browsers often require Secure cookies for SameSite=None.

Hope this helps! Addressing these points should improve your app's ability to handle cookies efficiently.

Hello Hermione_Book,

To ensure your setup works efficiently, let’s focus on practical solutions to address your HttpOnly cookie challenge between ASP.NET Core Web API and ReactJS:

  • Correct CORS Setup: You've included .AllowCredentials() in your CORS policy, which is a must for allowing cookies. Confirm that both your frontend and backend address the same domain or URL structure to avoid origin conflicts.
  • Configure Cookies for Cross-Origin: Update your HttpOnly cookie settings to be cross-origin compatible by setting:
    Response.Cookies.Append("refresh_token", refreshToken, new CookieOptions
      {
          HttpOnly = true,
          Secure = true, // Enable this setting when using HTTPS connections
          Expires = DateTime.UtcNow.AddDays(7),
          SameSite = SameSiteMode.None // Needed for cross-site cookie sharing
      });
      
    This ensures your HttpOnly cookie is sent across origins.
  • React Fetch Configuration: Make sure credentials are included when making requests from your React app by adding:
    const result = await fetch(`${API_BASE_URL}/api/auth/signin`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(credentials),
          credentials: 'include'
      });
      
    This allows the browser to send and receive cookies with requests.
  • Secure Testing Environment: If possible, set up a secure (HTTPS) development environment even while testing, as most browsers require it to send Secure cookies when SameSite=None is used.
  • Network Monitoring: Use browser developer tools to confirm that cookies are being set and sent properly in your network requests. Look for Set-Cookie headers to ensure cookies have the correct attributes.

Implement these steps, and your integration should run smoothly, handling cookies as intended. If issues persist, more details on the error logs would help diagnose further.

Best,

— David Grant