What is the point of configuring DefaultScheme and DefaultChallengeScheme on ASP.NET Core?
Asked Answered
U

1

78

I am learning how security works in ASP.NET Core 2.0 and IdentityServer4. I set up the projects with IdentityServer, API and ASP.NET Core MVC Client App.

The ConfigureService method in the Client App is shown below. Here I am confused about DefaultScheme and DefaultChallengeScheme. What is the point of configuring those? A detailed description on how it works would be really helpful if possible.

I have already seen in addition to DefaultScheme, DefaultSignInScheme also works, but how does it work? What is the difference between those?

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

    services.AddAuthentication(options =>
    {
        options.DefaultScheme = "Cookies";
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        //options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        //options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie("Cookies")
    .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
    {
        options.SignInScheme = "Cookies";
        options.RequireHttpsMetadata = false;

        options.Authority = "http://localhost:5000/";
        options.ClientId = "mvcclient";
        options.SaveTokens = true;
    });
}
Uninspired answered 25/9, 2018 at 7:20 Comment(0)
E
253

First of all note that you are not using ASP.NET Core Identity there. Identity is the user management stack that builds on top of the authentication system. You appear to be using OpenID Connect with an IdentityServer as the provider, so your web application will only consume the OIDC information but not have to manage its own identities (it may be possible that the IdentityServer is using ASP.NET Core Identity though).

The way the authentication stack works in ASP.NET Core is that you can configure a set of authentication schemes. Some of these schemes are meant to be used in combination, for example the cookie authentication scheme is rarely used on its own, but there are also schemes that can be used completely separately (for example JWT Bearer authentication).

Authentication actions

In the authentication world, there are certain actions that you can perform:

  • Authenticate: To authenticate basically means to use the given information and attempt to authenticate the user with that information. So this will attempt to create a user identity and make it available for the framework.

    For example, the cookie authentication scheme uses cookie data to restore the user identity. Or the JWT Bearer authentication scheme will use the token that is provided as part of the Authorization header in the request to create the user identity.

  • Challenge: When an authentication scheme is challenged, the scheme should prompt the user to authenticate themselves. This could for example mean that the user gets redirected to a login form, or that there will be a redirect to an external authentication provider.

  • Forbid: When an authentication scheme is forbidden, the scheme basically just responds with something that tells the user that they may not do whatever they attempted to do. This is commonly a HTTP 403 error, and may be a redirect to some error page.

  • Sign-in: When an authentication scheme is being signed in, then the scheme is being told to take an existing user (a ClaimsPrincipal) and to persist that in some way. For example, signing a user in on the cookie authentication scheme will basically create a cookie containing that user’s identity.

  • Sign-out: This is the inverse of sign-in and will basically tell the authentication scheme to remove that persistence. Signing out on the cookie scheme will effectively expire the cookie.

Note that not all authentication schemes can perform all options. Sign-in and sign-out are typically special actions. The cookie authentication scheme is an example that supports signing in and out, but the OIDC scheme for example cannot do that but will rely on a different scheme to sign-in and persist the identity. That’s why you will usually see the cookie scheme with it as well.

Typical authentication flow

Authentication schemes can be used explicitly. When you use one of the authentication extension methods on the HttpContext, for example httpContext.AuthenticateAsync(), then you can always explicitly specify what authentication scheme you want to use for this operation.

So if you, for example, want to sign in with the cookie authentication scheme "Cookie", you could simply call it like this from your code:

 var user = new ClaimsPrincipal(…);
 await httpContext.SignInAsync(user, "Cookie");

But in practice, calling the authentication directly and explicitly like that is not the most common thing to do. Instead, you will typically rely on the framework to do authentication for you. And for that, the framework needs to know which authentication scheme to use for what operation.

That is what the AuthenticationOptions are for. You can configure those options so that you can explicitly define what authentication scheme to use as the default for each of those authentication actions:

You typically don’t configure all those properties. Instead, the framework has some default fallbacks, so you can configure just a subset of those properties. The logic is like this:

  • Authenticate: DefaultAuthenticateScheme, or DefaultScheme
  • Challenge: DefaultChallengeScheme, or DefaultScheme
  • Forbid: DefaultForbidScheme, or DefaultChallengeScheme, or DefaultScheme
  • Sign-in: DefaultSignInScheme, or DefaultScheme
  • Sign-out: DefaultSignOutScheme, or DefaultScheme

As you can see, each of the authentication actions falls back to DefaultScheme if the specific action’s default isn’t configured. So what you will typically see is the DefaultScheme being configured, and then the specific actions are configured for those where a different scheme is required.

Your example shows this pretty well: With OIDC, you will need a sign-in scheme that can persist the identity that is provided by the external authentication provider. So you will usually see the OIDC and cookie authentication schemes:

services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
    options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
});

For the user, the normal interaction is through the cookie authentication scheme: When they access the web application, the cookie authentication scheme will attempt to authenticate them using their cookie. So using the cookie authentication scheme as the default scheme for all operations.

The exception is when challenging the authentication: In that case, we want the user to be redirected to the OIDC provider, so they can log in there and return with an identity. So we set the default challenge scheme to the OIDC scheme.

In addition, we also link the OIDC scheme with the cookie scheme. When the user gets challenged and logs in with their external authentication provider, they will get sent back to the web application with their external identity. The OIDC scheme cannot persist that identity though, so it signs in using a different scheme—the cookie scheme—which will then persist the identity on behalf of the OIDC scheme. So the cookie scheme will create a cookie for the OIDC identity, and on the next request, the cookie scheme (which is the default scheme) will be able to authenticate the user again using that cookie.


So most of the time, you will be fine with just specifying the default scheme and then depending on your authentication setup maybe change one or two explicit actions. But theoretically, you can totally set up a very complex setup of different defaults and multiple schemes: The framework gives you a lot of flexibility here.

Ermin answered 25/9, 2018 at 8:6 Comment(15)
thanks for answering poke. this will be helpful many others who tries to understand depth.Uninspired
I miss more explanations about authentication like this. Great information.Centaurus
@poke, thanks for this info -- it was extremely helpful. One question, if you have multiple schemes -- say Cookie and JwtBearer, will both of those schemes be called during Authenticate to make an attempt to create an Identity? Also, does Authenticate attribute on a controller only use the Default Authentication Scheme unless you specify otherwise?Gae
@Gae The authentication middleware will by default only attempt to authenticate using the default authenticate scheme, yes. If you specify schemes using the [Authorize] attribute, then those will be authenticated as well. If you don’t specify a scheme within the attribute, I believe that no scheme will be re-authenticated, so the default authentication will still apply.Ermin
@Ermin Why do we need then parametrless overload AddAuthentication(). How and where are schemes specified in this case?Moorwort
@PavelVoronin You are right that a parameterless AddAuthentication() does not configure how the schemes are used. There are however some use cases for this: Sometimes, you won’t need a default authentication scheme, e.g. if you always call it explicitly or enforce it through authorization policies. And you can also call AddAuthentication() multiple times. E.g. if you have some utility methods that want to add additional authentication schemes, they can do that easily without having to configure the defaults. It’s mostly convenience that you do not need to configure it in the same call.Ermin
I am using IdentityServer as a part of my project and can't figure out how to set app service layer to be able to sign in the user via _signInManager.SignInAsync call. I am supplying context.Services.AddAuthentication(options => { options.DefaultSignInScheme = "bearer" // reuse scheme already set in Http API host layer } on this layer, but signing in does not seem to be working in factMcclanahan
@Mcclanahan JWT Bearer authentication does not support a sign-in since bearer authentication is completely stateless and the client is responsible of passing the bearer token on every request. This is different to the cookie scheme where a sign-in causes a cookie to be set. – If you are having troubles with your setup, I would suggest you to create a new question for it.Ermin
@Ermin thank you. Saying honestly, this topic - authentication - is huge and I feel literally lost :( I just know the time is running out for me and I need to complete this task very soon. If would be convenient for you, please have a look at: #63507127Mcclanahan
Thanks, @Ermin for this explanation!! Checking my understanding... in your example, this line options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme was optional, correct? Since DefaultScheme had already been set to the same?Kocher
@Kocher Yes, if there is no explicit SignInScheme configured, the OIDC scheme will fall pick the DefaultSignInScheme which itself will fall back to the DefaultScheme if not specified.Ermin
@Ermin Are you able to help with #76216454 ?Tansy
"The OIDC scheme cannot persist that identity though" - why can't it?Tansy
“Why can’t it?“ – Because it’s not the purpose of the OIDC scheme to persist the identity. There are other schemes that are used for this, most notably the cookie scheme, so the work is being delegated to that scheme instead of duplicating the code.Ermin
This is better and more helpful than anything I've read anywhere on Microsoft's various documentation pages. Thank you.Abate

© 2022 - 2024 — McMap. All rights reserved.