Why you may want to read this article
The prerequisite for this article is the entry article about common services for Auth in .NET
This is the second article about authentication internals in .NET 5. Here I’m going to go through the source code
of the Authentication Core of .NET, by implementing authentication using Cookies
.
We will overview the basic magic of cookie-based auth in .NET, whether it is stateless or stateful, how it works, and all the magic underhood. On top of that we will do demo in the end.
Cookie Authentication Handler
It is a time to research how a particular IAuthenticationHandler
implementation works. For now, we will take a look at cookies-based authentication.
AddCookie
Regularly, we register the cookies authentication handler using this method .AddCookie
All calls go to this method. 36-40 lines are registering configurations and 42 line registers the handler itself. AddScheme
implementation is here
But there is nothing interesting so let’s dive into CookieAuthenticationHandler
. But before this, we also need to consider CookieAuthenticationOptions.
CookieAuthenticationOptions
CookieAuthenticationOptions source code
We need to understand some implementation from it:
CookieManager
The cookie manager is used to provide cookies from requests and write them to responses.
It is initialized here
ChunkingCookieManager is a service that breaks down long cookies for responses and reassembles them from requests.
TicketDataFormat
Ticket data format is used to serialize/deserialize + encrypt/decrypt identity (user info) from cookies.
It is Initialized here
CookieAuthenticationHandler - Magic
CookieAuthenticationHandler source code
To be able to authenticate the request successfully we should sign in it first.
CookieAuthenticationHandler extends SignInAuthenticationHandler which has SignInAsync
method.
You may be interested in what is ResolveTarget()
, what is target at all, and why it is here. To put it simply we can combine different authentication schemes and this method is needed for managing those combinations. You could see the answer for “why” by following this stack overflow question
Target here is some another authentication scheme. If you have some different ForwardSignIn schemes in your authentication options it will try to sign in with this scheme and bypass the current handler.
The main behavior for signing in is implemented in HandleSignInAsync
method which is abstract and implemented in our CookieAuthenticationHandler
.
HandleSignInAsync - first part
Line 292: EnsureCookieTicket
tries to get cookies, then read, unencrypt, extract principal verify expiration of authentication ticket. Also, this operation sets additional data like session id.
Here it ensures operation is done once.
Then method ReadCookieTicket does:
Line 141: reads cookie from the request
Line 147: unprotect it (reads and unencrypt using data protection), from this ticket we can get user-specific data (identity).
Line 153-167: tries to save session key for future if the session is enabled (it might not be enabled)
Line 172-182: checks whether auth ticket is not expired
If all checks are passed it returns AuthenticateResult.Success
with auth ticket.
Line 293: builds different cookie options like SameSite, HttpOnly, SecurePolicy, etc.
HandleSignInAsync - second part
Line 295: builds signin context with all info available at this point including user info, httpcontext, scheme, cookie options, etc.
Line 304-317: updates issued and expired time for authentication validity.
Line 319: calls SigningIn event to notify subscribers.
Line 321: updates expired time for cookies (not for authentication validity).
HandleSignInAsync - third part
Line 329-346: if the session store is configured - then a tricky thing happens - it stores the user in this store and puts only the session id into auth ticket. It means that your cookie will contain only the session id encrypted.
IMPORTANT NOTE
If your session store is an InMemory store, you cannot do straightforward scaling of servers, because your application is stateful.
In case of storing all user identity info in cookies, you could configure the same data protection keys for all replicas and do your replication as you want because the instances will be stateless.
Line 348: serializing and encryption of cookie value using data protection.
Line 350-354: writing the cookie value into the response (header).
Line 356-363: publishing signed in the event to subscribers
Line 366: setting additional headers and return URL, calling events.
Line 191: The actual authentication happens here. It reads user identity from cookies (already discussed in HandleSignInAsync
) and returns successful AuthenticateResult if the cookie is valid and it is not expired along with the authentication ticket.
Line 199: we try to refresh cookies’ expiration if the remaining time is less than half of the whole expiration time.
Demo
For the example purpose I’ve created a simple demo that could be found by following this link:
It is a pure backend without anything unnecessary. You could use it through the swagger because cookies are set to domain.
Registering scheme and other authentication services along with CookieAuthenticationHandler:
Sign in using cookies
:
Line 15: this method is supposed to check the password hash against stored one in your database or any other storage. But for the sample, it checks whether the username and password are mines.
Line 22-32: setting claims identity with necessary claims (user data).
Line 34: setting auth cookies.
As you can see we have set authentication cookies.
Authenticate using cookies
:
This endpoint just outputs one of your claims (Name). If you are authenticated you will see the username.
As you can see we can successfully read user identity from cookies.
Thank you for your attention. Please leave feedback.
Follow up
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