Mixing Forms and Token Authentication in a single ASP.NET Application (the Details)
- by Your DisplayName here!
The scenario described in my last post
works because of the design around HTTP modules in ASP.NET. Authentication related
modules (like Forms authentication and WIF WS-Fed/Sessions) typically subscribe to
three events in the pipeline – AuthenticateRequest/PostAuthenticateRequest for
pre-processing and EndRequest for post-processing (like making redirects
to a login page).
In the pre-processing stage it is the modules’ job to determine the identity of the
client based on incoming HTTP details (like a header, cookie, form post) and set HttpContext.User and Thread.CurrentPrincipal.
The actual page (in the ExecuteHandler event) “sees” the identity that the
last module has set.
So in our case there are three modules in effect:
FormsAuthenticationModule (AuthenticateRequest, EndRequest)
WSFederationAuthenticationModule (AuthenticateRequest, PostAuthenticateRequest, EndRequest)
SessionAuthenticationModule (AuthenticateRequest, PostAuthenticateRequest)
So let’s have a look at the different scenario we have when mixing Forms auth and
WS-Federation.
Anoymous request to unprotected resource
This is the easiest case. Since there is no WIF session cookie or a FormsAuth
cookie, these modules do nothing. The WSFed module creates an anonymous ClaimsPrincipal and
calls the registered ClaimsAuthenticationManager (if any) to transform it.
The result (by default an anonymous ClaimsPrincipal) gets set.
Anonymous request to FormsAuth protected resource
This is the scenario where an anonymous user tries to access a FormsAuth
protected resource for the first time. The principal is anonymous and before the page
gets rendered, the Authorize attribute kicks in. The attribute determines
that the user needs authentication and therefor sets a 401 status code and ends the
request. Now execution jumps to the EndRequest event, where the FormsAuth module takes
over. The module then converts the 401 to a redirect (302) to the forms login page.
If authentication is successful, the login page sets the FormsAuth cookie.
FormsAuth authenticated request to a FormsAuth protected resource
Now a FormsAuth cookie is present, which gets validated by the FormsAuth
module. This cookie gets turned into a GenericPrincipal/FormsIdentity combination.
The WS-Fed module turns the principal into a ClaimsPrincipal and calls the
registered ClaimsAuthenticationManager. The outcome of that gets set on the
context.
Anonymous request to STS protected resource
This time the anonymous user tries to access an STS protected resource (a
controller decorated with the RequireTokenAuthentication attribute). The
attribute determines that the user needs STS authentication by checking the authentication
type on the current principal. If this is not Federation, the redirect to
the STS will be made.
After successful authentication at the STS, the STS posts the token back to the application
(using WS-Federation syntax).
Postback from STS authentication
After the postback, the WS-Fed module finds the token response and validates
the contained token. If successful, the token gets transformed by the ClaimsAuthenticationManager,
and the outcome is a) stored in a session cookie, and b) set on the context.
STS authenticated request to an STS protected resource
This time the WIF Session authentication module kicks in because it can find
the previously issued session cookie. The module re-hydrates the ClaimsPrincipal from
the cookie and sets it.
FormsAuth and STS authenticated request to a protected resource
This is kind of an odd case – e.g. the user first authenticated using Forms
and after that using the STS. This time the FormsAuth module does its work, and then
afterwards the session module stomps over the context with the session principal.
In other words, the STS identity wins.
What about roles?
A common way to set roles in ASP.NET is to use the role manager feature.
There is a corresponding HTTP module for that (RoleManagerModule) that handles PostAuthenticateRequest.
Does this collide with the above combinations?
No it doesn’t! When the WS-Fed module turns existing principals into a ClaimsPrincipal (like
it did with the FormsIdentity), it also checks for RolePrincipal (which
is the principal type created by role manager), and turns the roles in role claims.
Nice!
But as you can see in the last scenario above, this might result in unnecessary work,
so I would rather recommend consolidating all role work (and other claims transformations)
into the ClaimsAuthenticationManager. In there you can check for the authentication
type of the incoming principal and act accordingly.
HTH