Camilo Terevinto
Camilo Terevinto

Reputation: 32068

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
     }
});

Upvotes: 2

Views: 7486

Answers (1)

Shaun Luttin
Shaun Luttin

Reputation: 141542

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

A second reasonable solution

The linked sample application from the comments calls services.AddAuthentication and sets SignInScheme to "Cookies". That changes the default sign in scheme for all of the authentication middleware. Result: the call to app.UseOpenIdConnectAuthentication is now equivalent to 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());
});

A third reasonable solution

To emphasize the importance of consistency, here is a third reasonable solution that uses an arbitrary sign in scheme name.

services.AddAuthorization(configuration =>
{
    configuration.AddPolicy("OpenIdConnect", new AuthorizationPolicyBuilder()
        .AddAuthenticationSchemes("Foobar")
        .RequireAuthenticatedUser().Build());
});

Here you are using CookieAuthenticationDefaults. Delete that line.

app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
    SignInScheme = "Foobar"
});

Upvotes: 4

Related Questions