Camilo Terevinto Camilo Terevinto - 1 month ago 24
C# Question

Configuring different authorization/authentication schemes

I am implementing security on an ASP.NET Core 1.0.1 application, which is used as a Web API. I am trying to understand if and how to implement 2 different authentication schemes.

Ideally, I would like to allow authentication via Azure Active Directory or via username/password for specific back-end services that contact the application.

Is it possible to configure ASP.NET Core for such a setup where an endpoint either authenticates through Azure AD or JWT token?

I tried with something like this, but upon calling the generate token endpoint, I get a 500 with absolutely no information. Removing the Azure AD configuration makes the endpoint work perfectly:

services.AddAuthorization(configuration =>
{
configuration.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build());

configuration.AddPolicy("OpenIdConnect", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(OpenIdConnectDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build());
});

app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
ClientId = Configuration["Authentication:AzureAD:ClientId"],
Authority
= Configuration["Authentication:AzureAd:AADInstance"]
+ Configuration["Authentication:AzureAd:TenantId"],
ResponseType = OpenIdConnectResponseType.IdToken,
SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme
});

app.UseJwtBearerAuthentication(new JwtBearerOptions
{
TokenValidationParameters = new TokenValidationParameters
{
ClockSkew = TimeSpan.FromMinutes(1),
IssuerSigningKey = TokenAuthenticationOptions.Credentials.Key,
ValidateAudience = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidAudience = TokenAuthenticationOptions.Audience,
ValidIssuer = TokenAuthenticationOptions.Issuer
}
});

Answer

Use the OpenIdConnectDefaults.AuthenticationScheme constant when you add the authorization policy and when you add the authentication middleware.

Here you are using OpenIdConnectDefaults. Good. Keep that line.

services.AddAuthorization(configuration =>
{
    ...

    configuration.AddPolicy("OpenIdConnect", new AuthorizationPolicyBuilder()
        .AddAuthenticationSchemes(OpenIdConnectDefaults.AuthenticationScheme) // keep
        .RequireAuthenticatedUser().Build());
 });

Here you are using CookieAuthenticationDefaults. Delete that line.

app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
    ...

    SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme // delete
});

Why?

When your OpenIdConnect authorization policy runs, it will look for an authentication scheme named OpenIdConnectDefaults.AuthenticationScheme. It will not find one, because the registered OpenIdConnect middleware is named CookieAuthenticationDefaults.AuthenticationScheme. If you delete that errant line, then the code will automatically use the appropriate default.

Edit: Commentary on the sample and another reasonable solution.

The linked sample application from the comments calls services.AddAuthentication and sets SignInScheme to "Cookies". By setting SignInScheme there, we change the default sign in scheme for all of the authentication middleware. Result: the call to app.UseOpenIdConnectAuthentication is equivalent to calling this.

app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions 
{
    SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme
}

That is exactly what Camilo had in the first place. So why did my answer work?

My answer worked because it does not matter what SignInScheme name we choose; what matters is that those names are consistent. If we set our OpenIdConnect authentication sign in scheme to "Cookies", then when adding an authorization policy, we need to ask for that scheme by name like this:

services.AddAuthorization(configuration =>
{
    ...

    configuration.AddPolicy("OpenIdConnect", new AuthorizationPolicyBuilder()
        .AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme) <----
        .RequireAuthenticatedUser().Build());
 });
Comments