Search code examples
c#.netauthenticationcookies

Get ClaimsPrincipal for non-default authentication scheme


When having multiple authentication schemes, how do I access the ClaimsPrincipal for non-default ClaimsPrincipal?

I have a POC app that has 2 authentication schemes, both are cookie-based. When I login with the first (default) authentication scheme, I can see the created identity and associated claims on context.User.

However, when I try to login with the second (non-default) authentication scheme, I do not get any identities or claims at all even though the second cookie is set.

What am I missing here?

using System.Security.Claims;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Authentication;

var builder = WebApplication.CreateBuilder(args);

const string scheme1 = "cookie1";
const string scheme2 = "cookie2";

builder.Services.AddAuthentication(scheme1)
    .AddCookie(scheme1, (options) => { options.Cookie.Name = scheme1; })
    .AddCookie(scheme2, (options) => { options.Cookie.Name = scheme2; });

var app = builder.Build();

app.MapGet("/", (HttpContext context) =>
{
    var result = JsonSerializer.Serialize(context.User, new JsonSerializerOptions
    {
        ReferenceHandler = ReferenceHandler.IgnoreCycles,
        WriteIndented = true
    });

    return result;
});

app.MapGet("/login1", async context =>
{
    await SignInAsync(context, "Name1", scheme1);
    context.Response.Redirect("/");
});

app.MapGet("/login2", async context =>
{
    await SignInAsync(context, "Name2", scheme2);
    context.Response.Redirect("/");
});

app.MapGet("/logout1", async context =>
{
    await context.SignOutAsync(scheme1);
    context.Response.Redirect("/");
});
app.MapGet("/logout2", async context =>
{
    await context.SignOutAsync(scheme2);
    context.Response.Redirect("/");
});

app.UseAuthentication();

app.Run();

return;

async Task SignInAsync(HttpContext context, string name, string authScheme)
{
    var claim = new Claim(ClaimTypes.Name, name, ClaimValueTypes.String, "CIA");
    var claims = new List<Claim> {claim};
    var identity = new ClaimsIdentity(claims, authScheme);
    var user = new ClaimsPrincipal(identity);
    await context.SignInAsync(authScheme, user);
}

Solution

  • You should create authorization policy, and also add authorization to your "/" mapping, another way it will be fetching user from default scheme only.

    Steps -

    1. Add Authorization middleware and set policy -
    AuthorizationPolicy multiSchemePolicy = new AuthorizationPolicyBuilder(scheme1, scheme2)
        .RequireAuthenticatedUser()
        .Build();
    
    builder.Services.AddAuthorization(options =>
    {
        options.DefaultPolicy = multiSchemePolicy;
    });
    
    1. Use Authorization middleware right after Authentication middleware -
    app.UseAuthentication();
    app.UseAuthorization();
    
    1. Add RequireAuthorization() to "/" mapping -
    app.MapGet("/", (HttpContext context) =>
    {
        var result = JsonSerializer.Serialize(context.User, new JsonSerializerOptions
        {
            ReferenceHandler = ReferenceHandler.IgnoreCycles,
            WriteIndented = true
        });
    
        return result;
    }).RequireAuthorization();
    

    Full class will be looking like this -

    // Add services to the container.
    
    using System.Security.Claims;
    using System.Text.Json;
    using System.Text.Json.Serialization;
    using Microsoft.AspNetCore.Authentication;
    using Microsoft.AspNetCore.Authorization;
    
    const string scheme1 = "cookie1";
    const string scheme2 = "cookie2";
    
    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services.AddAuthentication()
        .AddCookie(scheme1, (options) => { 
            options.Cookie.Name = scheme1;
        })
        .AddCookie(scheme2, (options) =>
        {
            options.Cookie.Name = scheme2;
        });
    
    AuthorizationPolicy multiSchemePolicy = new AuthorizationPolicyBuilder(scheme1, scheme2)
        .RequireAuthenticatedUser()
        .Build();
    
    builder.Services.AddAuthorization(options =>
    {
        options.DefaultPolicy = multiSchemePolicy;
    });
    
    builder.Services.AddControllers();
    
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();
    
    var app = builder.Build();
    
    // Configure the HTTP request pipeline.
    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI();
    }
    
    app.UseHttpsRedirection();
    
    app.MapGet("/login1", async context =>
    {
        await SignInAsync(context, "Name1", scheme1);
        context.Response.Redirect("/");
    });
    
    app.MapGet("/login2", async context =>
    {
        await SignInAsync(context, "Name2", scheme2);
        context.Response.Redirect("/");
    });
    
    app.MapGet("/logout1", async context =>
    {
        await context.SignOutAsync(scheme1);
        context.Response.Redirect("/");
    });
    app.MapGet("/logout2", async context =>
    {
        await context.SignOutAsync(scheme2);
        context.Response.Redirect("/");
    });
    
    app.UseAuthentication();
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.MapGet("/", (HttpContext context) =>
    {
        var result = JsonSerializer.Serialize(context.User, new JsonSerializerOptions
        {
            ReferenceHandler = ReferenceHandler.IgnoreCycles,
            WriteIndented = true
        });
    
        return result;
    }).RequireAuthorization();
    
    app.Run();
    
    async Task SignInAsync(HttpContext context, string name, string authScheme)
    {
        var claim = new Claim(ClaimTypes.Name, name, ClaimValueTypes.String, "CIA");
        var claims = new List<Claim> {claim};
        var identity = new ClaimsIdentity(claims, authScheme);
        var user = new ClaimsPrincipal(identity);
        await context.SignInAsync(authScheme, user);
    }