ASP.NET Core 3.1 Use both OpenIDConnect and Custom Cookie Authentication

Viewed 3051

I have an existing application that makes use of Cookie Authentication, and would like to add the ability to authenticate users using Active Directory. The current application uses Cookie based authentication and custom authorisation - roles in a database.

I am adding bits from example located here:

Add sign-in with Microsoft to an ASP.NET Core web app

When I run the application I get an error:

System.InvalidOperationException: Scheme already exists: Cookies

What is the correct way to configure OpenIdConnect and Cookie Authentication.

// STEP 1 Basic Cookie Auth

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
        .AddCookie(options =>
            {
                options.LoginPath = "/Auth";
                options.AccessDeniedPath = "/Home/AccessDenied";
                options.Cookie.IsEssential = true;
                options.SlidingExpiration = true;
                options.ExpireTimeSpan = TimeSpan.FromSeconds(day/2.0);
                options.Cookie.HttpOnly = true; // not accessible via JavaScript
                options.Cookie.Name = "login_token";

                options.TicketDataFormat = new CustomJwtDataFormat(
                    SecurityAlgorithms.HmacSha256,
                    tokenValidationParameters);
            });

// STEP 2 OpenID Connect Auth

services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
        .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"), "OpenIdConnect", "Cookies", true);

I am not able to find any examples using both Cookie Authentication and OpenID Connect. Is this possible? Allowing users to login selectively using Active Directory authentication, or local authentication (details stored in local database).

After changing the "Cookie" name, get's rid of the error message, but breaks the local authorisation, e.g.

When a valid Username and Password is given, I typically authorise the login.

HttpContext.Response.Cookies.Append("login_token", token, GetCookieOptions());

Currently with OpenIDConnect configured User.Identity.IsAuthenticated remains false.

3 Answers

According to the error messages, it tell you that you have multiple Scheme which named cookies.

According to the AddMicrosoftIdentityWebApp Method document, you could find the third parameter name is the cookieScheme.

The cookie-based scheme name to be used. By default it uses "Cookies".

But you have already set this name at above, so you should use other one. For example: "ADCookies".

Like below:

services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
        .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"), "OpenIdConnect", "ADCookies", true);

The mixed approach is a minefield but the below is allowing use to Authenticate Users via IdentityServer4 using OIDC while authenticating the Application into AzureAD with Identity.Web to get tokens for Api calls.

services.AddAuthentication(options =>
{
    options.DefaultScheme = "IS4Cookies";
    options.DefaultChallengeScheme = "oidc";
})
.AddCookie("IS4Cookies")
.AddOpenIdConnect("oidc", "OpenID Connect", options =>
{
    options.SignInScheme = "IS4Cookies";
    // Get IdentityServer configuration from appsettings.json.
    var config = Configuration.GetSection("IdentityServerOptions").Get<IdentityServerOptions>();

    options.Authority = config.Authority;
    options.RequireHttpsMetadata = false;
    options.ClientId = config.ClientId;
    options.ClientSecret = config.ClientSecret;
    options.ResponseType = "code";
    options.SaveTokens = true;
    options.GetClaimsFromUserInfoEndpoint = true;
    options.ClaimActions.MapJsonKey("role", "role");
    options.ClaimActions.MapJsonKey("role", System.Security.Claims.ClaimTypes.Role);
    options.ClaimActions.MapJsonKey("email", "email");
    options.ClaimActions.MapJsonKey("preferred_username", "preferred_username");
    options.Events = new OpenIdConnectEvents
    {
        OnRemoteFailure = context =>
        {
            context.Response.Redirect("/");
            context.HandleResponse();
            return Task.FromResult(0);
        }
    };
})
.AddMicrosoftIdentityWebApp(Configuration, "AzureOptions")
    .EnableTokenAcquisitionToCallDownstreamApi(new string[]{"sms.all" })
    .AddInMemoryTokenCaches();

It's possible to mix two mechanisms. I use MicrosoftIdentity authentication for access to administration web pages and cookies authentication for my APIs and SignalR hubs.

I use this in startup ConfigureServices

services
    .AddAuthentication(sharedOptions =>
    {
        sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    })
    .AddCookie("CookiesApiScheme", options =>
    {
        options.SlidingExpiration = true;
        // There is no redirection to a login page for APIs and SignalR Hubs, I just made a call to /Api/Login/SignIn with credential
        options.AccessDeniedPath = new PathString("/Api/Login/AccessDenied"); // Action who just returns an Unauthorized
    })
    .AddMicrosoftIdentityWebApp(Configuration); // By default scheme is "CookieAuthenticationDefaults.AuthenticationScheme"

And in API controller you can use something like this

    [Route("api/[controller]")]
    [ApiController]
    [Authorize(Roles = Roles.ADMIN)]
    [Authorize(AuthenticationSchemes = "CookiesApiScheme")]
    public class DefaultController : ControllerBase
    {
        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }
    }

Based on this post: ASP.NET Core 2.0 AzureAD Authentication not working

Related