.NET Auth internals pt1: basics

Posted by : on

Category : .NET Auth Internals

Why you may want to read this article

I am a technical guy, and I like to research the internals of the things I am using daily, and how they work under the hood.

There is some information about how to add authentication to your application. And guides sound like “copy this, paste there and it should work”. The problem here is that there are a lot of ways to do authentication in your app:

For me, it never was clear how it works how those things are connected, and how they can be combined and used together.

This is the entry article about authentication internals in .NET 5. Here I’m going to go through the source code of the Authentication Core of .NET and explain the basic shared services.

After that, I will launch a bunch of more specific auth internals overviews: cookies-based, JWT-based, Google-based, OAuth-based, etc.


Terminology


Authenticate

Authenticate - means checking whether the current request contains any auth and contracting User (ClaimsPrincipal) from this info.

For example:

  1. JwtBearerHandler authentication means checking the “Authorize” header and checking the token validity.
  2. CookieHandler authentication means checking and validating cookies.
  3. External auth (Google, Facebook, Microsoft) means emitting OAuth flow with configured API keys and secrets and ensuring external auth result is successful. Note, this is only in case if our application didn’t set its own cookies before, I have explained it in this article.


Sign In & Sign Out

Sign In - means making a future set of requests authenticated. It basically tries to persist the user (ClaimsPrincipal). Keep in mind not all handlers can sign in.

A good example of it is CookieHandler. It sets Cookies in response so that all subsequent requests include cookies and be authenticated.

Sign out - means making all subsequent requests not authenticated. Keep in mind not all handlers can sign out.

A good example is still CookieHandler.


General Authentication Services

Every auth implementation starts with the Registration of necessary services and then using the appropriate middleware that we’ve registered. Those are services and middlewares that are used in each authentication type: Cookie-based, JWT-based, Google-based, etc.

Before this part, let’s take a look at what will be registered. So high-level components & connectors overview (runtime) is the following:

alt_text


AuthenticationScheme

AuthenticationScheme source code

alt_text

The most important concept is Authentication Scheme, put in simple words - it is the thing that represents what auth you are going to use. There are different auth schemes:

  • Cookie: CookieAuthenticationDefaults.AuthenticationScheme
  • JWT: JwtBearerDefaults.AuthenticationScheme
  • Google: GoogleDefaults.AuthenticationScheme

And many others. The scheme is a way to tell auth middleware how it should process the request to make auth for you.

Each Authentication Scheme is tied to a particular AuthenticationHandler or AuthenticationRequestHandler by having a specific HandlerType at the 39th line above. This type is used to instantiate (or resolve using DI) AuthenticationHandler for specific Schemes to handle auth process.


IAuthenticationHandler

IAuthenticationHandler source code

alt_text

Each Authentication Scheme has a dedicated Authentication Handler that implements the interface above. Most usually we will try to cast to another derived interface to be able to sign in, sign out, etc.

For example, the Cookie-based handler interface has these SignIn and SignOut interfaces and classes extended.


IAuthenticationRequestHandler

IAuthenticationRequestHandler source code

alt_text

Some of the AuthenticationHandlers could implement IAuthenticationRequestHandler.

This interface extends IAuthenticationHandler we have discussed above and is used to handle auth and returns whether we need to authenticate with HttpContext later or not (line 19).

For now, it could be not that clear why we need this IAuthenticationRequestHandler if we already have IAuthenticationHandler. We will discuss this along with AuthenticationMiddleware. But most usually they are used to perform OAuth flow with Google, Facebook, Github, etc.

One thing to remember: if this method returns true - the auth request is considered processed and we should stop.


AuthenticationSchemeProvider

AuthenticationSchemeProvider source code

This service takes care of managing all schemes and providing them whenever required in an optimal and thread-safe manner.

The most important part here is part of adding a particular scheme:

On this line

alt_text

This provider keeps two types of schemes:

  • All schemes (line 140)
  • Schemes with the IAuthenticationRequestHandler handler type we have discussed above (line 137).


AuthenticationHandlerProvider

AuthenticationHandlerProvider source code

This provider does not have any rocket science.

It resolves particular AuthenticationHandler based on the scheme, using AuthenticationSchemeProvider (remember this HandlerType field in AuthenticationScheme class).

alt_text


AuthenticationMiddleware

AuthenticationMiddleware source code

alt_text

It is impossible to do auth without AuthenticationMiddleware. This middleware is invoked for each request to do auth process. By doing auth process it verifies the authentication ticket and sets HttpContext.User property so we can access it from controllers.

Line 55-63: we try each scheme that is tied to the IAuthenticationRequestHandler handler type to do auth process. Usually implementation of this interface are remote auth implementation like Google, Facebook, Github. This handler performs OAuth flow, and Sign In request, as explained here. If a particular request handler returned true then it means no need to continue auth, we return.

Line 65-79: In case of all IAuthenticationRequestHandlers skipped auth, or they were not found at all - we try to authenticate using context. Authentication using context will be considered later.

A good example of the handler that is implementing IAuthenticationRequestHandler is RemoteAuthenticationHandler.

It is used in most External auth handlers: Google, Facebook, Microsoft, Github, etc.

There is just part of this method: alt_text

It is a good question why we might want to keep authenticating after handling the request using some IAuthenticationRequestHandler.

I see a few possible scenarios for this:

  1. Let’s say we have Microsoft and Google auth in place, we will iterate through both of them. So there is a collection of schemes: [MicrosoftHandler, GoogleHandler]. First goes MicrosoftHandler. If we got a Google auth request the MicrosoftHandler will skip the request and return false, but after this GoogleHandler will handle it and return true.
  2. Let’s say we have JwtBearerHandler and GoogleHandler. If the request for auth is for JWT the GoogleHandler will skip and return false. Then the code flow will go out of foreach.


AuthenticationHttpContextExtensions

HttpContext.AuthenticateAsync simply resolves AuthenticationService which in turn authenticates the request.

alt_text

HttpContext.SignInAsync resolves AuthenticationService as well which signs in the request.

alt_text


AuthenticationService

AuthenticateAsync

alt_text AuthenticateAsync resolves particular IAuthenticationHandler from IAuthenticationHandlerProvider by the authentication scheme. No rocket science - just resolve from DI container by Handler Type.

Then it calls the AuthenticateAsync method on the handler and creates AuthenticateResult with the corresponding auth ticket, claims, and user.

SignInAsync

alt_text

SignInAsync is acting pretty similar to AuthenticateAsync. It resolves particular IAuthenticationHandler from IAuthenticationHandlerProvider by the authentication scheme. Then it calls the SignInAsync method on the handler.

This is a kind of wrapper on every provider that calls necessary IAuthenticationHandler methods, the main magic is in those handlers.


AuthenticateResult

AuthenticateResult source code

alt_text

This is a container with related auth info.

If authentication is successful AuthenticateResult will contain related data and authentication: AuthenticationTicket and ClaimsPrincipal.

If authentication is not successful then it will contain failure details.

AuthenticateResult contains ClaimsPrincipal. For a simpler understanding, it is a kind of authenticated user object. ClaimsPrincipal in turn contains a collection of ClaimsIdentity which in turn contains a collection of Claims. So it looks like this:

alt_text

ClaimsPrincipal source code

It is a bag with user-associated identity information. The most important part of this class for us is ClaimsIdentity.

ClaimsIdentity source code

alt_text

For ClaimsIdentity what we are interested in is a collection of claims that are related to the user.

Claim source code

A Claim is one piece of information about the authenticated user: id, email, first name, role, etc. It has type and value.


Registering services

Now let’s look at registration for all of those services we have discussed.


AddAuthentication

Registration starts with

.AddAuthentication source code

alt_text

This method in turn registers different general services for authentication. The most interesting for us is the following:

.AddAuthenticationCore source code

alt_text

These are general services needed for authentication, the most important we already discussed above.

.AddDataProtection source code

alt_text

It registers different services for the security of the auth data, for example, encryption of cookies.


UseAuthentication

After we registered all services we need to add middlewares to allow them to authenticate and authorize requests before they go to controllers/pages/etc.

For registering authentication we are using .UseAuthentication

alt_text

This method just registers important middleware which does all work named AuthenticationMiddleware which we have already discussed.


Follow up

These are the basic services that are used for different auth handlers. In the next articles - we will consider particular authentication handlers: Cookie-based, Jwt-based, Google-based, etc. They contain most of the magic inside.

Please subscribe to my social media to not miss updates.: Instagram, Telegram

I’m talking about life as a Software Engineer at Microsoft.


Besides that, my projects:

Symptoms Diary: https://blog.symptom-diary.com

Pet4Pet: https://pet-4-pet.com


About Andrii Bui

Hi, my name is Andrii. I'm Software Engineer at Microsoft with 5 years of experience.