Angular app fails to set a cookie via ASP.NET Web API

92 views Asked by At

I'm trying to write an angular app with an ASP.NET Core Web API and a Cosmos DB. Because of sensible data I need to deal with authentication and authorization. I'm using the Microsoft.AspNetCore.Identity framework to handle the authentication.

All is going well with postman or the swagger ui but from my web api but I seem still to have a cross site problem: When I'm calling the login function from my angular app...

public login (): Observable<any> {
    let httpHeaders = new HttpHeaders({
      'Content-Type' : 'application/json',
      'Cache-Control': 'no-cache'
    });    
    let options = {
      headers: httpHeaders,
      withCredentials: true, 
      observe: 'response' as 'response'
    };
    
    var loginRequest = new LoginRequest();
    loginRequest.email = "[email protected]";
    loginRequest.password = "xyz";
    
    return this._http.post<any>(baseUrl + 'login?useCookies=true&useSessionCookies=true', loginRequest, options)
      .pipe(
        tap(res => console.log('webService.login returned ' + res)),
        catchError(error => {
          console.log('webService.login: Error!, ' + error.status);
          console.error(error);
          return throwError(() => error);
        })
      );
  }

... I get the 200 back but when I'm looking into the network tab of the debug settings of the browser I can see following warning:

This attempt to set a cookie via a Set-Cookie header was blocked because it had the SameSite=Lax attribute but came from a cross-site response which was not the response to a top-level navigation. This response is considered cross-site because the URL has different scheme than the current site.

So I can't call authorized functions although the login call succeeded with a 200 return value.

In following picture the problem is shown by a fade-in window at hovering:

cross site error

What could I do? I have tried already several settings in my Program.cs of the Web Api without success, currently it looks like following:

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Cosmos;
using Microsoft.EntityFrameworkCore;
using System;
using WebApp4.Models;
using WebApp4.Services;

var builder = WebApplication.CreateBuilder(args);

var MyAllowSpecificOrigins = "MyCorsPolicy";

builder.Services.AddCors(options =>
{
    options.AddPolicy(MyAllowSpecificOrigins,
        builder => builder
            //.WithOrigins("http://localhost:5216", "https://localhost:4200/", "http://localhost:4200/", "https://localhost:7267/", "https://localhost:7267/api/WeatherForecast/ping1")
            //.AllowAnyOrigin()
            .SetIsOriginAllowed(origin => true)
            .AllowAnyHeader()
            .WithExposedHeaders("Set-Cookie")
            .AllowCredentials()
            .AllowAnyMethod());
});

builder.Services.AddAuthentication(IdentityConstants.ApplicationScheme)
    .AddIdentityCookies()
    .ApplicationCookie!.Configure(opt => opt.Events = new CookieAuthenticationEvents()
    {
        OnRedirectToLogin = ctx =>
        {
            ctx.Response.StatusCode = 401;
            return Task.CompletedTask;
        }
    });

builder.Services.AddAuthorizationBuilder();

// Add services to the container.
builder.Services.AddDbContext<ApplicationDBContext>(
    options => options.UseCosmos(
        builder.Configuration.GetSection("CosmosDb").GetValue<string>("Account"),
        builder.Configuration.GetSection("CosmosDb").GetValue<string>("Key"),
        builder.Configuration.GetSection("CosmosDb").GetValue<string>("DatabaseName")));
    //options => options.UseInMemoryDatabase("AppDb"));

builder.Services.AddIdentityCore<MyUser>()
  .AddEntityFrameworkStores<ApplicationDBContext>()
  .AddApiEndpoints();

builder.Services.Configure<IdentityOptions>(options =>
{
    options.SignIn.RequireConfirmedEmail = true;
});
builder.Services.AddTransient<IEmailSender, EmailSender>();

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(MyAllowSpecificOrigins);

app.MapIdentityApi<MyUser>();

app.UseDefaultFiles();
app.UseStaticFiles();

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

app.UseHttpsRedirection();

app.UseAuthentication();

app.UseAuthorization();

app.UseCookiePolicy(
    new CookiePolicyOptions
    {
        MinimumSameSitePolicy = SameSiteMode.None,
        Secure = CookieSecurePolicy.None
    }
);

// protection from cross-site request forgery (CSRF/XSRF) attacks with empty body
// form can't post anything useful so the body is null, the JSON call can pass
// an empty object {} but doesn't allow cross-site due to CORS.
app.MapPost("/logout", async (
    SignInManager<MyUser> signInManager,
    [FromBody] object empty) =>
{
    if (empty is not null)
    {
        await signInManager.SignOutAsync();
        return Results.Ok();
    }
    return Results.NotFound();
}).RequireAuthorization();

app.MapControllers();

app.Run();

I tried already to change the orders of the UseCookiePolicy call or to use SameSiteMode.Strict, but nothing helped so far, I have no idea what I could do to fix the problem. Every hint is welcome. Thanks a lot

1

There are 1 answers

0
Brando Zhang On

As the error said, if your angular app and the web api's url is in different scheme, your web api's response set-cookie will be consider as cross-site cookie and not be allowed inside the browser.

To solve this issue, one possible way is you make sure your angular app and your web api is inside the same domain.

Another is I suggest you could directly using JWT authentication instead of cookie authentication for your web api.

More details about how to achieve it, I suggest you could follow this article(how to use JWT auth in asp.net core web api) and this article(how to use JWT auth inside angular).