- Why you may want to read this article
- Terminology
- General Authentication Services
- Registering services
- Follow up
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:
- Simple and / or custom schemes: using JWT, using Cookies, Custom auth handlers
- Using external identity providers: Google auth, and Facebook auth, with a combination of JWT, Cookies on your side.
- Implementing our own OAuth service could use Google auth, Facebook auth, Cookies, return using JWT, etc.
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:
JwtBearerHandler
authentication means checking the “Authorize” header and checking the token validity.CookieHandler
authentication means checking and validating cookies.- 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:
AuthenticationScheme
AuthenticationScheme source code
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
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
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:
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).
AuthenticationMiddleware
AuthenticationMiddleware source code
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 IAuthenticationRequestHandler
s 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:
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:
- 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 goesMicrosoftHandler
. If we got a Google auth request theMicrosoftHandler
will skip the request and return false, but after thisGoogleHandler
will handle it and return true. - 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.
HttpContext.SignInAsync resolves AuthenticationService as well which signs in the request.
AuthenticationService
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
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
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:
It is a bag with user-associated identity information. The most important part of this class for us is ClaimsIdentity
.
For ClaimsIdentity what we are interested in is a collection of claims that are related to the user.
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
This method in turn registers different general services for authentication. The most interesting for us is the following:
.AddAuthenticationCore source code
These are general services needed for authentication, the most important we already discussed above.
.AddDataProtection source code
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
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://symptom-diary.com
Pet4Pet: https://pet-4-pet.com