The original motivation for this article is about following issue:
- how does the claim identity information persist after login in ASP.NET
- why does updating of claim identity in owin context not persist in further request
The WIF(Windows Identity Foundation)
provides a Claims-Based Identity Model. And in ASP.NET
, we can already build a Claims-Aware ASP.NET Web Application. Especially, when using with different kind of authentication middleware, WIF
provides the same abstract layer to access the identity information across the whole asp.net pipeline context.
In this article we will talk about some detail about asp.net authentication middleware based on the CookieAuthenticationMiddleware
. At the end, let’s discuss more about persist claim in cookie across request.
Owin Middleware
Simple speaking, Owin pipeline is a link of middleware, and the request will dive into this link to the end(or short-circuited before end), and the response will pop up back though the pipeline middleware. When coming through the middleware, this gives opportunity to middleware to process and even short-circuit the whole process line.
Request
/=====\ /=====\ /=====\
=====> | | =====> | | =====> | |
| MW1 | | MW2 | | MW3 |
<===== | | <===== | | <===== | | <== Response
\=====/ \=====/ \=====/
This diagram is a very simple and straightforward explanation about the Owin Middleware.
And authentication middleware if one kind of middleware that will give their effect to the request and response process.
1 | using System.Threading.Tasks; |
Here is the definition the OwinMiddleware
, there is two key pieces in code:
- next: reference to the next middleware in pipeline
Invoke
abstract function: this is the how this middleware could perform on the owin conext. The usually pattern would be “Before action” + “next.Invoke()” + “after action”.
AuthenticationMiddleware
AuthenticationMiddleware
is the base class for all authentication middleware. And it implement the basic pattern for the Invoke
function for a concrete authentication middleware. Here is the flow:
And more detail code is as below:
1 | public override async Task Invoke(IOwinContext context) |
So we can see that, the concrete authentication middleware will create an authentication handler and use that handler for further processing. The main logic for a authentication middleware is mainly about how the handler is implemented.
So let’s see the peice of code for CreateHandler
in CookieAuthenticationMiddleware
:
1 | protected override AuthenticationHandler<CookieAuthenticationOptions> CreateHandler() |
AuthenticationHandler
So the main logic of authentication middleware will be focus on authentication handler. Let see a deeper graph when we expand the AuthenticationHandler
level:
The blue part will be the main part of how AuthenticationHandler
works. And the red part will be how a specific authentication handler, here is CookieAuthenticationHandler
implemented.
CookieAuthenticatonHandler
We do not have the plan to dive into every detail of how CookieAuthenticationHandler implemented. So we only focus to original issue, how claim identity does the persistence underlying.
In fact, originally, i thought the claim identity which we can access via User.Identity
will be persist in cookie. So if I update the claims in User
context, everything will be persist ( i mean update ) into the cookie later. So I can access the updated value in later request.
However, the thing is not as I wish.
Here is the expanded version for CookieAuthenticationHandler
:
The TeardownAsync
will perform the persistence logic from the identity, but not like we wish. From the diagram, we found that findally, the CookieAuthenticationHandler
use the ApplyResponseGrantAsync
to save information. And what is Grant
?
We can refer to more detail from the implementation:
1 | protected override async Task ApplyResponseGrantAsync() |
From the code, two things need to pay attention to:
- the handler will save the
grant
into cookie - the action in step 1 will only happen when we can have this
grant
Ok, let’s find the final piece about this grant
1 | public AuthenticationResponseGrant LookupSignIn(string authenticationType) |
So we finally found where the data comes from - Authentication.AuthenticationResponseGrant.
AuthenticationResponseGrant
Grant
is the concept of the information that we retreive after we do the authentication. If the cookie handler save this grant into the cookie, we need to find where is grant come from.
1 | public void SignIn(AuthenticationProperties properties, params ClaimsIdentity[] identities) |
So in fact, this grant
information is set when we call the SignIn
function of the authenticaton manager. And if we do not call SignIn
, the AuthenticationResponseGrant will be null. In that case, the cookie will not be updated.
And the User.Identity
will NOT set to this authentication grant if SignIn
is not called. That is why even update the User.Identity
but those updated information will not take effect for any further request in asp.net.
This is also explain every detail about this stackoverflow question Persisting claims across requests
How to Persist Claim
After dive into such more detail from code level, let’s discuss about the original topic.
I found a solution here from the stackoverflow How to update a claim in ASP.NET Identity?, this solution will set the AuthenticationResponseGrant
explicitly when try to update the claim items, this will trigger the sign in logic when Teardown
is called.
In fact, this is one of the solution that indeed take effect. But it has a potential issue, that is, in fact, such kind of code will trigger the sign in logic underlying, and the sign in logic will update the cookie expiration time. This behavior works as you sigin in again when you explicitly set the AuthenticationResponseGrant
. Of course, if the cookie expiration time is not a problem for your business logic, espeically when you use the SlidingExpiration, everyting should be fine.
But for my business which use the cookie authentication expiration as the web application login expiration. This will be a problem, and i should not use this way to update claim information in cookie.
So based on the implementation of Katana( i do not read the latest code, so correct me if i miss something), the identity saved in cookie is not designed for dynamically update. Those claim identity in the cookie is represented as a unique identity information that binding to a specific login and it should be immutable. And, updating the claim data in cookie with AuthenticationResponseGrant
means to a totally new login instead of updating the current login context. So all the claim data that is persisted in the cookie should not be updated within a specific login session. So that is why we can found that most of the authenticaton middleware save the claim identity information belong to the login itself to the cookie.
Yes, you may say that, how about those that do not belong to so-called “login information”, where should I save it? In fact, within asp.net project template the microsoft already provide us the way when you select “Individual Use Account”.
I do not want to dive more code here, the magic is ApplicationUserManager
. This manager is using UserStore
binding to a entityframework DbContext
object. We can use the user manager’s AddClaimAsync
|GetClaimAsync
|RemoveClaimAsync
to manipulate the claim data we want to save. The user manager will sync those saved claim everytime when the next query is comming, like what we wish. And yes, i think you already guess the conclusion, user manager is using the database to save the claim identity information underlying.
And one more option, if you just want to save some data between request, and those information is not a sensitive data, such as access code, you can use the Request.Cookie
directly, and you can see those data from chrome debug panel under Resource
tab directly. More simple, right?
Ok, claim identity in asp.net is so a big topic so i would talk more aspect on other article, let’s end here.
Any question, please just reply and discuss with me :-)
References:
- Persisting claims across requests
- Server side claims caching with Owin Authentication
- How to update a claim in ASP.NET Identity?