Introduction
This article describes an easy approach for programming against Windows Identity Foundation in a Single Sign-On (SS0) scenario from the angle of a relying party application. WIF supports a variety of Claims-based authentication scenarios but this document will focus upon using WIF to develop an application that supports SSO given the use of a SAML 2.0 token containing some basic claims.
WIF greatly simplifies the development process when SSO is required; further, Visual Studio facilitates the development effort by allowing the developer to code against a test instance of a local STS before shifting the code to work with a specific instance of a production STS (for example., using ADFSv2 to authenticate users against AD on their home domain and then issue a token for that user to a relying party).
WIF stands in the pipeline, to the front of the protected resource, and it controls access to the resource by handling the communications between the party responsible for authenticating the user and the resource itself.
In this document, I will provide a brief overview of the main points involved with implementing SSO based upon the use of WIF. If you would like to refer directly to the Microsoft documentation then you may do so starting with the Microsoft Security website:
http://msdn.microsoft.com/en-us/security/aa570351.aspx.
Definition of Terms
The following terms are essential to understanding the SSO process as implemented using ADFSv2 and WIF.
- Subject: The end user; this person will gain access to the protected application based upon authentication against that person's home domain and active directory.
- RP: Relying Party: This refers the domain receiving authentication information for the subject.
- IP: Identity Provider: This refers to the domain responsible for authenticating the users.
- Claim: Specific bits of information carried from the IP to the RP in a token.
- Claim Types: A class defining the basic claims that can be carried to the RP from the IP.
- Token: An encrypted XML message carrying the claims from the IP to RP.
- WS-Federation: WS-Federation extends WS-Trust; it does not dictate a specific token form.
- STS: Security token service; for example, ADFSv2.
- RST: Request Security Token (RP requests token).
- RSTR: Request Security Token Response (IP returns token).
- ADFS: Active Directory Federation Services.
Relationship (RP to IP)
The IP is responsible for publishing a policy whilst the RP complies with that policy. Policies contain the protocol, security, and claim requirements. The IP authenticates users, gathers the claims, formats and transmits the tokens containing the claims. This can all be handled simply with ADFSv2 through configuration settings.
An RP trusts tokens granted by an IP and uses those tokens to allow or deny access to an application. The relationship is established within federation metadata that defines the communication details.
Single Sign-on Scenario
In a single sign-on scenario, the RP will request a token from the IP through the STS. If the request is accepted, the IP will issue a token. When the RP receives an acceptable token it will grant access to the resource (the web application or a web service). If the RP does not receive a valid token then it will deny access to the resource.
In that narrative, a single sign on is accomplished without any need on the RP side to maintain any information on the IP's user population. Only the information provided in the token is necessary in order to confirm authorization and to provide the required details (contained in the token's claims) necessary to access the site. Nominally, one would expect to receive the token along with the user name and the user's role. Other details could also be included such as an email address; those items being typical to Active Directory and found in the claim type class. With a token in hand, we know where the user originated and who they are along with what role they carry.
Custom claims could also be created and added to the token but doing so might be beyond the reach of any entity with limited IT support. For that reason, it is suggested that only default claim types be used lest we end up doing more than limited IT support for the client company.
Roles may be established by creating one or more groups in Active Directory and adding specific users to each group to limit their access to various elements of a resource.
Getting Started
This appendix will address configuring and using WIF from within Visual Studio 2012. Upon completion of this walk-through the reader will have a working MVC4 application using Claims-based authentication with a simulated SAML 2.0 token containing both standard and custom claims. The purpose herein is to show how to set up a test environment and to use WIF to work with token based claims; it is not focused at all upon configuring ADFSv2. The same techniques demonstrated herein may be used to bind the application to an actual identity provider.
Setting up the Demonstration
Open a new instance of Visual Studio 2012; be sure to run the instance as administrator. Running and debugging the application requires administrator rights; if you attempt to run the application without administrator rights it will fail.
Create a new MVC4 application using the internet template then name the project WifMvcDemo. You could tenably use either a web forms or an earlier version of an MVC project template so long as the targeted framework is .NET 4.5.
Figure 1: Creating the WIF Demonstration Project
Figure 2: Selecting the Basic Application Template
Once the project has loaded, open the tools menu and select "Extensions and Updates".
Figure 3: Loading Extensions and Updates
Once the Extensions and Updates dialog has opened, key "Identity" into the search box. This will show the extension for the Identity and Access Tool. If it is not installed then click the tool and load it into Visual Studio 2012. Save everything and then restart Visual Studio 2012 as administrator. Once that is done we are all set to use Windows Identity Foundation to handle authentication requests.
Figure 4: Installing the Identity and Access Tool
Configuring the application to use WIF
At this point we have an MVC4 application and we have made WIF available. The next thing to be done is to configure the application to use WIF; to that end, right-click on the project name in the Solution Explorer and then select the "Identity and Access" option from the menu.
Figure 5: Identity and Access Menu Option
When the Identity and Access dialog opens, select the providers tab if it is not already set, then click the "Use the Local Development STS to test your application" option. This will enable the application to simulate working with an STS without creating one. For deployment, the second option would be selected normally. The other options, once checked, will reveal other configuration settings that will be required (for example., setting the path to the federation metadata).
Figure 6: Identity and Access Provider Tab
In this example, there are no configuration settings to make on the configuration tab but you might wish to go ahead and have a look at it anyway since it is important for deployment purposes.
In the Local Development STS tab, you should set the token format to SAML 2.0; you may leave the port number as is. In the area entitled, "Test claims to issue:" you will see a collection of claims that will be added to the token. You may edit the values, delete them, or add new ones to this claims collection.
It is important to note that the type value must be in an acceptable namespace format else it will be rejected. Go ahead and key in a new claim by moving to the bottom of the list, and start a new line by clicking in the type column. Key in http://myplace.com/petname, set the display name to "Pet Name", then key in a pet name in the last column, Value. You can go ahead too and change the other values given to use your own name and email address in lieu of the default example.
Figure 7: Local Development STS Tab
Figure 8: Adding a new claim
Before dismissing it, look to the bottom of the dialog and not the path to the federation metadata. Once you have added WIF to the project and selected the use of the local STS, a hidden folder is created in the project to contain the metadata. Further, the local STS will run on the local host but it will run in a separate port. In my local instance, the application is running on port 5857 (naturally yours will likely be different) but the local STS is running on port 14288.
If you would care to have a look at the federation metadata used by the local STS instance then select "Show all files" in the Solution Explorer, then look for the exposed FederationMetadata folder.
Figure 9: The Hidden Federation Metadata folder
You can open the federation metadata file to have a look at its contents. A quick peek at the file content reveals that the token type is set to SAML 2.0 and since this file establishes the trust relationship you should also observe the path and port number used by the relying party application. The STS will honour authentication requests made from that URL.
Figure 10: Federation Metadata
In the root of the folder you should also locate a configuration file entitled, "LocalSTS.exe.config". This is another XML document used in the test environment to set the claims carried by the token. Of course this file is not encrypted (when the data is returned as a SAML 2.0 token, the contents will be encrypted and then converted to a base 64 string). Depending upon the number of claims and their size, when the data is returned it may be broken up and we can look at that a bit when we run the application. In the claims section of this document, you can make out the claim type (expressed as a name space), the display name, and the value held for the claim.
Figure 11: Local STS Configuration
Home Controller
Since we started out with the basic template, we don't have any controller's defined. Right-click the controller folder and select the option to add a new controller. Name the controller HomeController and accept the default settings. When the controller opens, right-click the Index action result and select the option to add a view. Again, accept the default settings to create the index view.
Running the Demonstration
Before pressing on with any programming and now that have configured the application to authenticate the users against a test instance of an STS, let's run the application to see what it does. Before running the application, launch Fiddler and clear it. Once done, run the application in debug mode.
The first thing you should notice is that the application shows you authenticated as the user set in the token's claims; you should not have been presented with any sort of login screen at all to get to that point either:
Next, turn your attention to Fiddler; in the inspector tag, select Raw in the top and bottom panes, then click the first line in the results.
On the left side, look over the traffic:
Figure 12: Fiddler Capture
Look to the host column, in the first line of the capture you will see a redirect from an attempt to hit the application. In the second line, you can see a temporary redirect to the local STS running on port 14288 (your port will likely be different of course). In the third line, we get an OK from the STS, followed by a redirect back to the protected site followed by an OK from the protected site as we gain entry.
Looking at the raw output associated with the redirect back to the protected site (line 4 in the preceding), you can see the SAML token passed back to the site and split up between two cookies (FedAuth and FedAuth1); you can reassemble the entire token by pasting the tail end onto the first half of the token, and then you can convert it all from a Base 64 string; as you can see the token is present and it is encrypted. Within this token are the claims that the application will use to determine a few things about the authenticated user (aside from the fact that they were authenticated by the IP):
<?xml version="1.0" encoding="utf-8"?>
<SecurityContextToken p1:Id="_f843dfbf-2e76-4011-95bf-4de644eee20f-18FB12239B43A7AAA6A7AAE3867C9F52" xmlns:p1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512"> <Identifier>urn:uuid:60b1e6a3-6ff7-4813-8ad0-f8f3d3ff8423</Identifier>
<Cookie xmlns="http://schemas.microsoft.com/ws/2006/05/security">AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAASfzN8lThi0eDUcEDmxgekQAAAAACAAAAAAADZgAAwAAAABAAAAA/5m1q1qCw98y+o/KYFh1gAAAAAASAAACgAAAAEAAAAOcjiPAL1o0nncTLF1I98ZfQAgAA1Sp3RctKILbvtLkYRtT4HHPjLjPkJv3mfmLKahMTF7hCkjHsTcE2vJw1qYZxbVi2HgSS2chU9lC5/anixoxO+xM5pnKI9K9I0BXyEz9ucpKoZTQ8P7wehrgXSKCNc9o6aCrwsYgWymPQ8+k0zsmnyZEXZ6WsAG9S+7Od0IqT9gHXxw/RrmW6DJ7LZfOKZlVQcjnCIWEuocVGv7107v1N6VTck2Jik8eAH0Y9llnWtOFiNr4C8E2zYDYwOdujv9kgp+wQN+FSyp+b01M+mD1/mpd9H0Y0WJYsjENVz4GJriO3Z8Q2B9/25/0l8R5SO7WMfXwVy8RksjOWxmkCTIsocj0q3KhSexxN9aiCm1jx8uzIZlUyx1XruFs2Dzfn9yBemsvn3kHStcstgNTCRlA1NiFA3CRGdxatYycmVOKS8xjJtvcKu/wuPKM+YLbYFimd4SMO2rUi+gcu39dMfmDUIsIFF4B0LSPDPlmF4Ati47UdM5tLghEv5cYF9J0mfzeqWby5w9xbiw293QiDQ7ZcGqSvI6EhT2q8WFlgNr10tQYicaKlcaQefNPCdntrP3hWb/Fn96AhCHJHAIXTwzs5Nx1HnrklpS7kiLwWTw/6WL2FfLRaGMGi/3F4paUPBSwk1UV9q5cbnz8JkdCNws/T5iYH3E98qjFkd4efWng6/RLbUnmwcjzimXyt6uiY7jNIGdGvCfTEb6eFk+7bBOvydwmGlGou8trtmjzKsW+QnSKFrOtqtAcwU4+ckC8MZjRlmbYMDED/kJEiblNn6YNZcZVERopXnJwWJXbKl7bGRY6Xs7NzOYRr1CHIGVDg7AbvT3HEilLxoQD+G5L0NXf9CD2X32GC5qdIL5T8PerhjzNwUZG3onism712TLly22LhWvo7am94tELrgQjePX6HwwKTClMJvkOZaI9em6h7P5RF0yZKm2UK3lPUJutsOp3sFAAAAOrKo94QkRRqZNJHSz9x0L/+EYDy</Cookie>
</SecurityContextToken>
Coding with WIF
At this point we have a local STS set up and we have confirmed that this STS is authenticating the user and passing back a valid token to the protected application. Next up we will write a bit of code to extract claims from this token and we will then display the claims and values on the home page.
We will start off by opening up the Models folder and creating a class entitled, "TokenData.cs". In this class, we will set up some properties to contain claims information extracted from the token:
Figure 13: Token Data Class
Next we will add a class to extract data from the current token and use that to populate an instance of the token data class. The class needs to include the "System.Security.Claims", "System.Threading", and "System.Security.Principal" namespaces.
The class will process a request for token information by creating an instance of the token data class used to return the claims to the caller of the GetTokenObject method. Next it will create a new claims principal and cast the current thread principal over to that type. The current principal is represented by the user authenticated by the STS.
Once that is done, we can load the token data into the token data object by querying against the claims carried by the claims principal. We can query against a specific, defined claim types that should they exist, or we can query against the actual namespace. In this case, since we made up petname as a claim, the only we to get it back from the token is to query against its namespace, http://myplace.com/petname. One may query against the namespace in all instances but it is easier to use the claim type where it is possible.
Figure 14: Token Data Manager
Now that we have that out of the way we can start using the claims in the application. Go into the home controller's index action result and set the code to this (be certain it includes the model folder):
Figure 15: Making use of token data
In the modified code, we've defined a personalized greeting to the user by extracting their first name from the claims and we also pulled all of the token claims data into the view bag is well. Now we will switch over to the view and display the information when the page loads:
Figure 16: Showing claim data in a view
Figure 17: Index displaying the claim data
Now that we will have a go at limiting user access to specific resources as a function of their role using the claims data; this will demonstrate that the authenticated user's role can be directly used within the application.
First off, we will expand the home controller to add in three new views. Alter the content per the following:
Figure 18: Adding Restricted Views to the Home Controller
So, looking at the code changes we see that we added an admin only view, a developer only view, and an authorised user only view. The roles were set accordingly to allow only users with a role of admin to access the admin page, only users with a role of developer to access the developer page, and the last page will allow authorised users from any role to access it. Also, the index view was configured to allow anonymous users to access it (even though it would not be very interesting for an anonymous user to gain access to it since there will not be any token data to look at).
The next thing to do is to right-click the AdminPage in the home controller and select the option to add a view. Accept the default settings and then set the view's contents to the following:
Figure 19: Adding Restricted Views to the Home Controller
Then create the DevPage view the same way and set it to contain the following markup:
Figure 20: Adding Restricted Views to the Home Controller
Lastly create the authorised user's view up and set it to contain the following markup:
Figure 21: Adding Restricted Views to the Home Controller
Now, since we have set up the example claims, recall that the example user has the role of developer. As such the user will be able to log into the index page, the developer only page, and the authorised users page. Since the user is a developer and not an admin user, you will not be able to log into the admin users only page; when you attempt that you will be presented with an unauthorised user response. Run the application and try accessing each of the views to confirm that.
Figure 22: Attempting to access the Admin Users only view
Summary
This demonstration gave a quick overview for implementing WIF in a .NET 4.5 MVC application; further, it demonstrates setting up a test environment using the local STS option, extracting claims from a valid SAML 2.0 token, and it demonstrates controlling access to specific views based upon the authenticated user's current role.