4

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.

Wayne
  • 3,359
  • 3
  • 30
  • 50
  • just question please, where to find the namespace from the 'AddMicrosoftIdentityWebApp' method? – Hasan Othman Jul 14 '21 at 09:39
  • @Hasan namespace Microsoft.Identity.Web public static class MicrosoftIdentityWebAppAuthenticationBuilderExtension – Wayne Jul 19 '21 at 04:04

4 Answers4

3

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);
Brando Zhang
  • 22,586
  • 6
  • 37
  • 65
  • I changed the cookie name to ADCookie (no error), but my standard authentication then fails to login. I assume that the cookies need to be shared else the login will not work. Perhaps a working example of mixed login is what I need. – Wayne Jan 28 '21 at 20:45
  • Do you mean you want both AzureAd and cookie authentication could work for some controller? User could use AzureAd or cookie authentication to access the application? – Brando Zhang Jan 29 '21 at 05:38
  • Changing the Name of the Cookie got rid of the error. But wondering if it is even possible to Mix the two mechanisms. Currently I have not succeeded to get both to work that the same time. (e.g. Login with Microsoft and Login with my own mechanism). Seems like when I change the order STEP 1 and STEP 2, then I can get Basic Auth to work. I assume AddMicrosoftIdentityWebApp changes the cookie options too. – Wayne Feb 15 '21 at 20:55
1

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();
gurkan
  • 884
  • 4
  • 16
  • 25
1

This is what I use and it works, you just need to specify the configureCookieAuthenticationOptions and set the name inside there and you should be good to go, also I had to use lax for SameSite or it would not work for me.

services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(identityOptions =>
/* {identityOptions.ClientId ="";}, // if you want to specify the options manually instead of Configuration.GetSection() call*/
    Configuration.GetSection("AzureAd"),                    
    configureCookieAuthenticationOptions: authCookie => { // Setup SSO cookie                        
        authCookie.Cookie.Name ="Your.Cookie.Name.Here";// change name to hide .net identifiers in name
        authCookie.Cookie.HttpOnly = true;// make so client cannot alter cookie
        authCookie.Cookie.SecurePolicy = CookieSecurePolicy.Always;// require https
        authCookie.Cookie.SameSite = SameSiteMode.Lax;// from external resource
        // verify options are valid or throw exception 
        authCookie.Validate();
    }
);

You may or may not need all of the authCookie values here, but it should get you started in the right direction!

Mike
  • 1,525
  • 1
  • 14
  • 11
0

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