Claims-based authentication in .NET4.5 MVC4 with C#: External authentication with WS-Federation Part 3 Various advanced topics
March 14, 2013 11 Comments
In the previous post we looked at some basic features of WS-Federation in a .NET4.5 MVC4 web app. In this post we’ll dive into some advanced features around WS-Federation.
Topics we’ll discuss include:
- Dynamic WS-Federation configuration at runtime
- WS-Federation authentication events
- Logging off
The features will be presented in a demo at the end as usual.
Dynamic configuration
We saw in Part 2 how WS-Federation can be configured in web.config. Those settings are of course static. However, occasionally you may need to set some properties dynamically. You might have these values stored in a database or you need to query a web service for the right configuration.
You can achieve this using Global.asax in the Application_Start() method. First wire up the FederationConfigurationCreated event as follows:
FederatedAuthentication.FederationConfigurationCreated += FederatedAuthentication_FederationConfigurationCreated;
…where FederatedAuthentication_FederationConfigurationCreated will initially look like the following:
void FederatedAuthentication_FederationConfigurationCreated(object sender, FederationConfigurationCreatedEventArgs e) { throw new NotImplementedException(); }
The incoming FederationConfigurationCreatedEventArgs has a property called FederationConfiguration. This property in turn has other properties, such as Name, ServiceCertificate, IdentityConfiguration, etc. that allow you to dynamically set the WS-Federation properties at runtime. Thus you get programmatic access to all WS-related configuration elements in web.config. Feel free to inspect what’s available using IntelliSense.
WS-Federation authentication events
The WSFederationAuthenticationModule, which is available as a property of FederationAuthentication has events similar to those of the SessionAuthenticationModule which we discussed before.
Type FederatedAuthentication.WSFederationAuthenticationModule. in the editor and inspect the available events using IntelliSense. The names of the events are pretty self-explanatory. We will look at the event called RedirectingToIdentityProvider in the demo. This event is fired when the HTTP request is redirected to the STS. Here you have the chance to modify the data that’s sent to the STS at runtime. You would hook up the event in Global.asax in the Application_Start() method:
FederatedAuthentication.WSFederationAuthenticationModule.RedirectingToIdentityProvider += WSFederationAuthenticationModule_RedirectingToIdentityProvider;
…which looks initially like this:
void WSFederationAuthenticationModule_RedirectingToIdentityProvider(object sender, RedirectingToIdentityProviderEventArgs e) { throw new NotImplementedException(); }
The RedirectingToIdentityProviderEventArgs parameter has a property called SignInRequestMessage which allows you to modify the outgoing message properties, such as Realm, BaseUri etc. You can create a new SignInRequestMessage as follows:
SignInRequestMessage signInRequestMessage = new SignInRequestMessage(new Uri("https://stsuri"), "the realm identifier"); String completeQueryString = signInRequestMessage.WriteQueryString();
You can then send the query string to the STS. This is the technique to be used with the Login link on your web page. As the web app does not have its own Login page any more the Login link needs to lead to the external STS using the query string constructed from the SignInRequestMessage object. We’ll see in the demo how this works.
Logging off
Locate the LogOff action in AccountController.cs. If you followed the posts in the series then you may have the following calls in there at present:
WebSecurity.Logout(); FederatedAuthentication.SessionAuthenticationModule.SignOut();
You can delete them and add the following call instead:
FederatedAuthentication.WSFederationAuthenticationModule.SignOut();
This will clear out the FedAuth session cookie. However, this will not sign you out of the STS. There’s a technique called Single SignOut which will do that for you. We won’t look at that in this blog post but in the next one.
Sliding expiration
Sliding session token expiration was explained here. You use the same techniques in the case of WS-Federation.
Server-side security token caching
This is very similar to FederatedAuthentication.SessionAuthenticationModule.SessionSecurityTokenReceived we saw here:
FederatedAuthentication.WSFederationAuthenticationModule.SessionSecurityTokenCreated += WSFederationAuthenticationModule_SessionSecurityTokenCreated;
In WSFederationAuthenticationModule_SessionSecurityTokenCreated you would then set the reference mode to true:
e.SessionToken.IsReferenceMode = true;
The effect is the same as before: not the entire Claims collection is serialised into FedAuth but only an identifier and the server will locate the serialised Claims collection using that token identifier.
Demo
Open the MVC4 internet application we’ve been working on in this series. Let’s see how dynamic configuration works first. Go to Global.asax and locate the Application_Start() method. Wire up the RedirectingToIdentityProvider event as follows:
FederatedAuthentication.WSFederationAuthenticationModule.RedirectingToIdentityProvider += WSFederationAuthenticationModule_RedirectingToIdentityProvider;
The skeleton of WSFederationAuthenticationModule_RedirectingToIdentityProvider will look like this:
void WSFederationAuthenticationModule_RedirectingToIdentityProvider(object sender, RedirectingToIdentityProviderEventArgs e) { SignInRequestMessage signInRequestMessage = e.SignInRequestMessage; }
Set a breakpoint at ‘SignInRequestMessage signInRequestMessage = e.SignInRequestMessage’ and run the application. Click the About link and you’ll see that code execution stopped at the break point. Inspect the SignInRequestMessage property in the Locals window and you’ll see it holds the values of the Request Url and the Realm:
Here we have the chance to dynamically set these properties based on a DB lookup or a web service call or a similar mechanism.
The SignInRequestMessage object can be used to redirect the user to the external Login page if the user clicks the Login link on the main screen. In a traditional forms-based app the user would then see the internal Login page but that is not an option any more.
Locate the Index action within HomeController.cs. There are two ways to construct the SignIn message. One is using a constructor:
SignInRequestMessage signInRequestMessage = new SignInRequestMessage(new Uri("https://andras1/idsrv/issue/wsfed"), "http://localhost:2533/");
You pass in the STS login URI and the realm. I simply copied the values from web.config.
Another way to set these properties is the following:
FederatedAuthentication.FederationConfiguration.WsFederationConfiguration.Realm = "http://localhost:2533/"; FederatedAuthentication.FederationConfiguration.WsFederationConfiguration.Issuer = "https://andras1/idsrv/issue/wsfed";
We’ll go with the constructor type of solution. Add the following just before the return statement in the Index action:
SignInRequestMessage signInRequestMessage = new SignInRequestMessage(new Uri("https://andras1/idsrv/issue/wsfed"), "http://localhost:2533/"); ViewBag.StsSignInUrl = signInRequestMessage.WriteQueryString();
For simplicity we add the query string to the ViewBag. Ideally it would be part of a ViewModel, but that’s beside the point in this discussion. Open Index.cshtml in the Views/Home folder. Add the following markup somewhere in the View:
<p> <a href="@ViewBag.StsSignInUrl">Log in here!</a> </p>
Run the application now and click the generated Login link on the home page. If you set up the STS login URL correctly then you’ll be redirected to the STS where you can sign in. Upon successful login you’ll be redirected to the MVC4 web app with Hello, [username] in the top right hand corner meaning that you successfully logged in to you site.
We modified the LogOut action before to make sure that the FedAuth cookie is removed. Press the ‘Log off’ link and you should then be logged out of the web site. Great! However, keep in mind what we said about the logoff method before: the session ended as far as the MVC application is concerned but is still alive on the STS side. Press the About link again and you should be automatically logged in without having to provide the username and password.
You can end the STS session by a technique called Single SignOut which is one of the things we’ll look at in the next blog post among other features such as Single SignOn.
You can view the list of posts on Security and Cryptography here.
Hi, thanks for your great post………….
In your previous post. “Claims-based authentication in MVC4 with .NET4.5 C# part 2: storing authentication data in an authentication session” you mentioned this function.
private void CreateSession(ClaimsPrincipal transformedPrincipal)
{
SessionSecurityToken sessionSecurityToken = new SessionSecurityToken(transformedPrincipal, TimeSpan.FromHours(8));
FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionSecurityToken);
}
So my question is, if i am using WSfederation. do i need to update this “FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionSecurityToken);” If yes, then what i need to replace it with.
thanks in advance
Hi Ashish,
If I understood your question correctly then you’ll find the answer in the blog post after this one:
External authentication with Claims and WS-Federation in MVC4 .NET4.5 Part 4: Single SignOut and Single SignOn
//Andras
Hello Andras,
There is a small typo regarding putting the login-link in the Index.cshtml at the end of the post.
It says: Log in here!
But I guess it should be: Log in here!
Hi Anders,
You’re right, thanks, I’ve corrected the error.
//Andras
Hi, I am using my company provided STS authentication. I am getting error as below for simple web page. Appreciate any help on this.
Server Error in ‘/’ Application.
——————————————————————————–
Value cannot be null.
Parameter name: username
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.ArgumentNullException: Value cannot be null.
Parameter name: username
Source Error:
Line 86: //}
Line 87:
Line 88: foreach (Claim claim in currentPrincipal.Claims)
Line 89: {
Hello,
You cannot provide a null as the username parameter to a Claim. Check the values returned by the STS.
//Andras
Hi Andras,
I would like to get a token from an ADFS 3.0 STS endpoint using username/password using .NET4.5 and pass the token to a trusted RP (workday). What ADFS endpoint should I use and should I be asking my administrator for the workday/ADFS certificate?
As I understand it, the STS and RP have mutual trust, but is the mutual trust required between STS client and STS? If not, what endpoint should I be using?
The RP is the workday site and I am not sure if they support active SAML tokens. They do support browser based passive federation with ADFS. The problem I am trying to work around is the extra ADFS login screen.
Thanks
Hi Andras,
Do you have a sample for this which we can download ?
Thanks
Hi Balu,
Some of it is available here:
https://github.com/andras-nemes/claimsinmvc4demo
//Andras
Hello Andras,
Your blog is a one of the best things on internet for people like me who are starting with Claims based authentication. I highly appreciate your articles. THANK YOU
This article has helped me setup and configure ws-fed to use an external IP, however how can an application manage Claims internally by itself? e.g. My application has to use an external IP for authentication and receives claims from the IP however, the administrator of the application wants to manage the Roles/Claims within the application. How can this be implemented with an external IP?
I am guessing, the application will still have to implement a UserManager / UserStore which kicks in after the authentication to check if the user is already in a local db otherwise create it. and then the administrator can assign the Claims to the specified user. However I do not know where this code will go?
Do you have any suggestions.
Hi, I really appreciate the detailed explanation that was put out here. I was wondering if I need to deploy my claims based authentication application in a web farm is it enough to use machinekeysessionsecuritytokenhandler and provide the section with valid keys or do we need to provide distributed caching for sessionsecuritytokencache as well? If my sessiontoken.IsReferencemode = false. Then we don’t need to provide distributed cache for our sessionsecuritytoken right? Please advice
-Thanks,