Exercise 1: Using Windows Identity Foundation to Handle Authentication and Authorization in a WCF Service

In this first exercise, you will gain familiarity with the way in which Windows Identity Foundation (WIF) handles authentication and authorization for web service calls. You may have used WCF classes in the past to obtain similar results: the Windows Identity Foundation (WIF) makes things simpler, and while it handles the same concepts (tokens, claims), it offers a more task-based approach.

Note that, for the sake of clarity, the web service in this exercise does not take advantage of issued tokens or claims: we secure the call with username and password so that we can illustrate differences between WCF’s and Windows Identity Foundation’s object model as crisply as possible. All subsequent exercises will instead take advantage of issued tokens and claims.

Figure 2

In this exercise the client invokes the service using username & password credentials. The credentials are verified in a custom token handler, while the authorization policies (based on the name of the caller) are enforced via a custom implementation of ClaimsAuthorizationManager

Task 1 - Reviewing the Begin Solution

  1. Open Microsoft Visual Studio 2010 with administrator privileges. From Start | All Programs | Microsoft Visual Studio 2010, right-click Microsoft Visual Studio 2010 and choose Run as administrator.
  2. Open the WeatherStation.sln solution file located in the %YourInstallationFolder%\Labs\WebServicesAndIdentity\Source\Ex1-SecuringWebService\Begin folder.

    Note:
    Ensure that the WeatherStationService application of the newly created service has anonymous authentication enabled. To do this, go to IIS Manager and browse for %machine name% | Sites | Default Web Site | WeatherStationServiceEx01, select the application node in the Connections tree view and then double-click Authentication. Locate the Anonymous Authentication item in the list and check its status. To enable anonymous authentication, right-click its entry in the list view and select Enable.

  3. Review the initial solution.

    Figure 3

    Exercise 1 begin solution

    Note:
    This solution represents a classic WCF web service-WinForm web service client scenario, implemented using Visual Studio 2010 templates defaults (hence just with the default security settings of wsHttpBinding).

    The WeatherStationClient project is a WinForms application that uses the WeatherStationService WCF service to retrieve the forecast for the next 3 or 10 days and displays the results on the UI. In its initial state, the client is not wired up to the service: in this exercise we will add the necessary invocation code and secure it with username/password credentials. On the service side, we will authenticate the calls based on those credentials and will handle authorization according to the individual users.

Task 2 - Using Windows Identity Foundation to Authenticate Calls to the Service

  1. Right-click the https://localhost/WeatherStationServiceEx01 project and select Add Reference.
  2. Select the Microsoft.IdentityModel assembly in the .NET tab.

    Figure 4

    Adding a reference to Microsoft.IdentityModel

    Note:
    If you did not find the Microsoft.IdentityModel.dll assembly on the Add Reference dialog, you can add the reference including the following line in the configuration/system.web/compilation/assemblies section of the Web.config file:

    <add assembly="Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />

  3. Right-click the App_Code folder in the https://localhost/WeatherStationServiceEx01 project and select Add New Item.
  4. Create a new Class and name it CustomUserNameSecurityTokenHandler.cs
  5. Replace the content of the new class with the following code to validate the user credentials in the issued token and assign the appropriate roles for each valid user.

    (Code Snippet – Web Services And Identity Lab - Ex01 CustomUserNameSecurityTokenHandler)

    C#

    using System; using System.IdentityModel.Tokens; using Microsoft.IdentityModel.Claims; using Microsoft.IdentityModel.Protocols.WSIdentity; using Microsoft.IdentityModel.Tokens; class CustomUserNameSecurityTokenHandler : UserNameSecurityTokenHandler { public override bool CanValidateToken { get { return true; } } public override ClaimsIdentityCollection ValidateToken(SecurityToken token) { UserNameSecurityToken usernameToken = token as UserNameSecurityToken; if (usernameToken == null) { throw new ArgumentException("usernameToken", "The security token is not a valid username security token."); } string username = usernameToken.UserName; string password = usernameToken.Password; if (("paul" == username && "p@ssw0rd" == password) || ("john" == username && "p@ssw0rd" == password)) { IClaimsIdentity identity = new ClaimsIdentity(); identity.Claims.Add(new Claim(WSIdentityConstants.ClaimTypes.Name, username)); return new ClaimsIdentityCollection(new IClaimsIdentity[] { identity }); } throw new InvalidOperationException("The username/password is incorrect"); } }
    FakePre-a3946da801034a438de7f4f7fd39ea9e-53b73120b1be47c699c9a6033dad1b40FakePre-b2af022ee64a4f3084660e397b910a48-6816379eda3a4d71be1ceb8bddb14cbfFakePre-70e7f52b9dfe41aaaaaabd998f8f1a5e-87ee9f5624e4426cb55d4c435af0f207FakePre-74772c49e610496baf14451f58dbfd44-a7e9d99c43a94948a5851b8b04f263e5FakePre-5df5e853c47745e38fb1a7c06053b7e3-f249e4be43884b09b22286ea37180f1d
    

    Note:
    If you have custom credential verification logic you want to use for authenticating calls, Windows Identity Foundation offers you a mechanism for weaving it in the processing pipeline. The SecurityTokenHandler defines an interface for plugging custom token handling functionality, which you can use for controlling the credential verification process. Deriving your own class from SecurityTokenHandler you can add functionality to serialize, de-serialize, authenticate and create specific kinds of token. In our example, the CustomUserNameSecurityTokenHandler class derives from UserNameSecurityTokenHandler that handles standardized WS-Security UsernameTokens. The key method is ValidateToken: here we perform a simple check against hardcoded values.

  6. Now that we took care of the authentication part, we need to think about authorization. We are going to take advantage of MyClaimsAuthorizationManager, a class provided with the Windows Identity Federation SDK samples that allows you to express some simple condition on the incoming claims that must be satisfied in order to gain access to a resource. Right-click the App_Code folder of the https://localhost/WeatherStationServiceEx01 project and select Add Existing Item.

    Note:
    Windows Identity Foundation is not tied to a specific authorization engine: rather, it offers extensibility points that you can leverage for executing your custom authorization code within the invocation processing pipeline. The mechanism is somewhat similar to what we have seen in the authentication steps: you derive your own implementation from the ClaimsAuthorizationManager class, where you will use the CheckAccess method to verify that the incoming claims satisfy the conditions assigned to the resource being invoked. Once your implementation of ClaimsAuthorizationManager is included in the pipeline (via config), returning “false” from CheckAccess will have the effect of stopping the call. Normally the authorization conditions are assigned to resources via external files, such as the application config, so that administrators are able to modify them without altering the application codebase. Furthermore, you can expect developers to assign conditions via Code Access Security style calls (i.e. decorating via attributes and so on). Both capabilities will require some coding support. Seasoned WCF developers will compare ClaimsAuthorizationManager with ServiceAuthorizationManager: while the two can be used for similar purposes, it should be noted that ClaimsAuthorizationManager offers a simpler object model that abstracts away many details of the actual call mechanics.

  7. Select all the content in the %YourInstallationtFolder%\Labs\WebServicesAndIdentity\Source\Assets\ClaimsAuthorizationManager folder and click Add.

    Figure 5

    ClaimsAuthorizationManager files

  8. Open the Web.config file of the https://localhost/WeatherStationServiceEx01 project.
  9. Register the microsoft.IdentityModel section. Inside the configSections element add the following (shown in bold). If the configSections section does not exist, you should add it inside the configuration element.

    (Code Snippet – Web Services And Identity Lab - Ex01 microsoft.IdentityModel section)

    XML

    <configuration>    
    FakePre-ebd928d4f5ab46bb82ed4ac031a05bad-0a2620c782e743cbad38f3352c1ffa33FakePre-6d4e99bbc5424ec68895fc3a3db0908f-0fbdc96df4c945219d27768344cc30f1 <section name="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />FakePre-9fd39a647be64a5da8d854f5ef211ecc-9bbba9b302bb424c8e89a462315f6041FakePre-30916411be6942a6bdd0a4968705a233-18c419b3db9f4861b98d10ff9bb72c84FakePre-3eeb373fa1ce4642bdb11067b8f19ea5-a0e3e2ac21f743f19a3fd871d15cf239

  10. Add the following settings for the microsoft.identityModel section just before closing the configuration section.

    (Code Snippet – Web Services And Identity Lab - Ex01 microsoft.IdentityModel settings)

    XML

      ...
    <microsoft.identityModel> <service> <securityTokenHandlers> <remove type="Microsoft.IdentityModel.Tokens.WindowsUserNameSecurityTokenHandler, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> <add type="CustomUserNameSecurityTokenHandler, App_Code"/> </securityTokenHandlers> </service> </microsoft.identityModel>FakePre-dd3333905aef4c22ab582f947d598b5e-8df2103126ee450ca410f06755f92a94

    Note:
    The microsoft.identityModel element is the area of the application config that you can use to enable Windows Identity Foundation and drive its behavior. In the last step, we placed our custom username token handler in the handler’s collection. Next, we will add our implementation of ClaimsAuthorizationManager to the pipeline.

  11. Add the following setting to configure the ClaimsAuthorizationManager inside microsoft.identityModel/service.

    (Code Snippet – Web Services And Identity Lab - Ex01 ClaimsAuthorizationManager)

    XML

      ...
    FakePre-8ab2f376176b4b499824aa7a5d1f02d6-d1b6da1511fa443d96254b63ce91c28cFakePre-4cb3273de4b14e82b08fa28c01d79a60-b47eabde99864cc2a845f2691dc510a5FakePre-4ef42d993051446fb3f33ce615fca805-69649c7be16246c4b9949b52c3644493FakePre-a5449a68c5af4d49b2443d159392a4bd-c2f31cb6cd7345c785dd461bc41d1560FakePre-9d475d8b05334af0bf8a29d341fef83c-0c5e219ea8a441d79980289e37527ff0FakePre-49d13911f202471c9c18637a69affec6-d6842a8f9ff745c8a9d2aaf387710d41 <claimsAuthorizationManager type="ClaimsBasedAuthorization.MyClaimsAuthorizationManager"> <policy resource="https://localhost/WeatherStationServiceEx01/Service.svc" action="https://tempuri.org/IService/GetThreeDaysForecast"> <or> <claim claimType="https://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" claimValue="paul"/> <claim claimType="https://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" claimValue="john"/> </or> </policy> <policy resource="https://localhost/WeatherStationServiceEx01/Service.svc" action="https://tempuri.org/IService/GetTenDaysForecast"> <claim claimType="https://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" claimValue="paul"/> </policy> </claimsAuthorizationManager>FakePre-371335d6dc4d4af38b842e0e0f8d2e94-6e2e157c980749e584481eee9cb5344dFakePre-3d48a5ab11544242ab35006ee03f5117-a77ee73c13f54ec4b442251f2fe442aaFakePre-f6da373d65504e7a88f48df5a5c9cd44-5ac05856511c45d4a97a554cdba76cfc

    Note:
    The MyClaimsAuthorizationManager sample defines a very rudimentary but effective syntax for expressing constraints. In the basic case, given a certain service and associated SOAPAction (resource and action in the schema, respectively), a user can invoke the corresponding method of the web service if and only if he presents an instance of a given claim type with the requested value. Conditions in this format can be combined via boolean operators to form composite authorization criteria. In this exercise, we do not really receive claims from an STS. However, we can consider the username as a claim and assign our authorization conditions on a per-user basis.

  12. Add the following bindings section under system.serviceModel to specify the usage of WS-SecurityUsernameToken for client credentials.

    (Code Snippet – Web Services And Identity Lab - Ex01 UsernameBinding)

    XML

    <system.serviceModel>
    <bindings> <wsHttpBinding> <binding name="UsernameBinding"> <security mode="TransportWithMessageCredential"> <message clientCredentialType="UserName"/> </security> </binding> </wsHttpBinding> </bindings>FakePre-016f976918c747bfb9d3d5c8d46aa870-2d6845ed48084f1db3a1676dd4e4dc96FakePre-3cbc64b331174c1ebb16728b9b71aaec-7e47696be8e5439ead8ee777cbe04709FakePre-d06c8c13680d453ead44758e6210c5b8-c69846b31fe5475db1c7257cb00d8206

    Add the following services element to configure that the service endpoint uses the binding defined on the previous step.

    (Code Snippet – Web Services And Identity Lab - Ex01 Endpoint Configuration)

    XML

    <system.serviceModel>
    <services> <service name="WeatherStationServiceEx01.Service"> <!-- Service Endpoints --> <endpoint address="" binding="wsHttpBinding" bindingConfiguration="UsernameBinding" contract="WeatherStationServiceEx01.IService"/> </service> </services>FakePre-b52cdace63fe4315aecf8405dbe97dac-aaed87390327477a8f18f24141179d53FakePre-0a59d13fac3e4b83aa4ba4f01fc4c7a9-cf911d7067d945aa91606a06ad65a831FakePre-327b82e5361e4bd28f7bb486a2999495-a421be9a2c86449bafe1ca5755c95f7cFakePre-df20f57d41de4c6a9434ca0c790ea1ba-8c696a1feca04004865c97ac2a4707dcFakePre-5c7d07f5479d4f9c81d15b0a020051ef-e12e1a0d839f454bac1a96576a6a2207

    Note:
    For what concerns the mechanics of the web service call, this remains a WCF service. Hence, everything you know about bindings remains valid. Here we simply made sure that we use username and password credential types.

  13. Add the following service extension inside the system.serviceModel element.

    (Code Snippet – Web Services And Identity Lab - Ex01 Service extensions)

    XML

    <system.serviceModel>
    FakePre-67a8288afd8d41359d416a4383b4527c-22fed4e475bb442fa48116321e1d0b20 <extensions> <behaviorExtensions> <!-- This behavior extension will enable the service host to be Claims aware --> <add name="federatedServiceHostConfiguration" type="Microsoft.IdentityModel.Configuration.ConfigureServiceHostBehaviorExtensionElement, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> </behaviorExtensions> </extensions>FakePre-176aa99b7f8a4deb92c8c0c4e2e87954-c957b2afc0f74055a9499a9c50c32fb0

  14. Add the federation extension under the system.serviceModel/behaviors/serviceBehaviors/behavior.

    XML

    <system.serviceModel>
    FakePre-75c4a662930b45f18a6a409e09f840df-f908206e8d28412dbfc07bba9fdf0393FakePre-ab2c716372324a70a0b778ef32f35d82-2bc9546cf08b477a8421c2b08cf37354FakePre-d6ffbbaaafa844409df31a42ebe05516-05602903f346438db02593ce29acb08fFakePre-9da57745534046da966c05a4bbac468b-85653d0885b248c6b32e1114a20e12aa <federatedServiceHostConfiguration/>FakePre-b570001259c44b3cb0ea7ea10f06588d-cdf50d531ac5456a85006f1d197035c4FakePre-3179a9f9d31f4adba92e0127321aeb7b-e1b2a8f63b1e4b448504030214e7d2a0FakePre-4b986c9143c4429da0a4bd7f74781e51-3e11f565b7d84775a5dff293927ba9c6FakePre-fa464fec2c0f4032a047fed151610bbf-23084e504e274509addca93bdafc78f5FakePre-4812064b3fb2421f8cb6dd1e56037e81-801f6be2095e4dd2a91fb4894c801bbbFakePre-c2eaab73435943a0b16f21087c00d9af-e7f7a5732f9e4fa5ae2a94945e07618bFakePre-462404b47bf7423194c3cb3f1baa0852-88749f8fc20f4e9aa541dc001bf85bd5FakePre-3211682cad2b416c8d35e7aba7d4dfdb-5cb78435538c4b429f7868c929c80803

    Note:
    The ConfigureServiceHostBehaviorExtensionElement behavior extension is what enables the Windows Identity Foundation pipeline in front of the service. There are various alternative ways of achieving the same result, from using a dedicated factory to assigning it directly via code when handling hosts and channels programmatically.

  15. Build the solution.

Task 3 - Using the Service

  1. Add a reference to the service in the WeatherStationClient application. To do this, right-click the WeatherStationClient project and select Add Service Reference.
  2. In the Add Service Reference dialog, click Discover.
  3. Select the WeatherStationServiceEx01/Service.svc and click OK.

    Figure 6

    Selecting the service

  4. Open the app.config file of the WeatherStationClient project and find the endpoint element under system.serviceModel/client.
  5. Update the address attribute to use https and localhost instead of the machine name (address="https://localhost/WeatherStationServiceEx01/Service.svc").

    XML

    <configuration>
    FakePre-87e100f60ff147bc8a046a002e67b13e-dafc32ecbf824121b13c6e0445c5b291FakePre-e8af801c27044cc08fad129c9eb3af2c-f766585ca6164bbaabec66a2f8d47cfaFakePre-519777535ba341c1b9c280c209472a02-56ccb5703beb4ebaa12d620cbd2cef46FakePre-d92ca7fd02d949779858492267751574-5d471acd799344baba20457be5237578FakePre-5cd1e226b3b8434d93dbf356352c40ca-d40f8090077241289d677930ee29e29a <endpoint address="https://localhost/WeatherStationServiceEx01/Service.svc"FakePre-1bcb6f778d39420386e871b7dddf7a61-84d5044f10fe4da3bf91d40ac290fcf6FakePre-d057f62a235641ecaf8f54e54f44bf94-9e24f30d48ab484e967d736b41e4ec01FakePre-abca0745b01a449295be7e736d7a03f8-1567dc6b07e049438637928a3c7f3e75FakePre-379c2e0c0bcc4c28b0272271c40aae15-d13252eb6d504f01a9f70f384e3ad5b9FakePre-dbb6478ae3cc467d80380e579dd0e128-37392f4314e84c84b92220a36a946268

  6. Open the ForecastForm.cs code behind.
  7. Add the following using statements.

    (Code Snippet – Web Services And Identity Lab - Ex01 ForecastForm Usings)

    C#

    using System.Drawing; using System.ServiceModel.Security; using WeatherStationClient.Properties; using WeatherStationClient.ServiceReference1;

  8. Create a DisplayForecast method to receive the forecast for the next days and display the results on the UI. To do this, add the following function at the end of the ForecastForm class.

    (Code Snippet – Web Services And Identity Lab - Ex01 DisplayForecast)

    C#

    private void DisplayForecast(Weather[] weather) { this.forecastPanel.Controls.Clear(); for (int i = 0; i < weather.Length; i++) { PictureBox pic = new PictureBox(); GroupBox box = new GroupBox(); box.Text = string.Format( CultureInfo.CurrentCulture, "{0:ddd dd}: {1}", DateTime.Today.AddDays(i), weather[i]); box.Height = 145; box.Width = 130; pic.Dock = DockStyle.Fill; pic.SizeMode = PictureBoxSizeMode.CenterImage; box.Controls.Add(pic); switch (weather[i]) { case Weather.Sunny: pic.Image = Resources.sunny; break; case Weather.Cloudy: pic.Image = Resources.cloudy; break; case Weather.Snowy: pic.Image = Resources.snowy; break; case Weather.Rainy: pic.Image = Resources.rainy; break; } this.forecastPanel.Controls.Add(box); } }
    FakePre-c476b0de37be49449451407b0aea2408-dc0ca7e95cba48d78fa4b04fa4eb591eFakePre-509c3f1275e34a68a2b824d8acad0fd3-645b01c833014e72944f677b5ac6a33fFakePre-68223bebf5e9409badcf4479bfdc681a-aaa9658da43b45628ed6b270c3632925
    

  9. Implement the ShowForecast function to call the weather service and call the DisplayForecast function to display the results. To do this, insert the following code shown in bold inside the ShowForecast function.

    (Code Snippet – Web Services And Identity Lab - Ex01 ShowForecast)

    C#

    private void ShowForecast(int days, int zipCode)
    FakePre-75308a1cecaa4c34baa6450b88bee31d-c42eaf48452b4f7c9a3188361ad9fe75 using (ServiceClient client = new ServiceClient()) { Weather[] weather = null; using (UserCredentialsDialog dialog = new UserCredentialsDialog()) { dialog.Caption = "Connect to WeatherStation Service"; dialog.Message = "Enter your credentials"; if (dialog.ShowDialog() == DialogResult.OK) { if (dialog.SaveChecked) { dialog.ConfirmCredentials(true); } client.ClientCredentials.UserName.UserName = dialog.User; client.ClientCredentials.UserName.Password = dialog.PasswordToString(); try { if (days == 3) { weather = client.GetThreeDaysForecast(zipCode); } else if (days == 10) { weather = client.GetTenDaysForecast(zipCode); } this.DisplayForecast(weather); } catch (SecurityAccessDeniedException ex) { MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); client.Abort(); } catch (MessageSecurityException) { MessageBox.Show("Could not authenticate your credentials.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); client.Abort(); } } } }FakePre-8e48402923414f95b0953767a04908cd-2e21631572f84e398f13b0592ef4ba63FakePre-74a8237d4a454f2494ff2d16ee9ffa9d-e15ed9fd66684705851ee5a3dd9d4264FakePre-f02891875af4447baa5961a720a97ad2-1bcffe21c05a4ca18b2d88157cbac655FakePre-755d2f4cad6f40d2af8e4a50326be469-430584c412b24cd68a037dcad3848f72FakePre-8469f05be92d49ba860b41e9e94a87fc-9c8bbdf9b61a4895981225399a1a47ce

  10. Save all the changes.

Note:
As you can see, the client did not use anything special or different from what you would be normally doing with a classic WCF service.

Exercise 1: Verification

In order to verify that you have correctly performed all steps in this exercise, proceed as follows:

  1. In Solution Explorer, right-click the WeatherStationClient project and select Set as StartUp Project.
  2. Start debugging by pressing F5.

    Figure 7

    Client application

  3. Enter any integer value in the ZIP code textbox.
  4. Click the Get 3 days button; when prompted for credentials enter the username "john" and the password "p@ssw0rd".

    Figure 8

    Client application showing the forecast for 3 days

  5. Now, click the Get 10 days button and enter the same credentials (john/p@ssw0rd).

    Figure 9

    Access is denied message

    Note:
    You will get an "Access is denied" error message since in our authorization conditions for GetTenDaysForecast we listed paul but not john among the acceptable values of the username claim. Windows Identity Foundation is throwing a SecurityAccessDeniedException when the user is not authorized.

  6. Click the Get 10 days button again; when prompted for credentials enter the username "paul" and the password "p@ssw0rd".

    Figure 10

    Client application showing the forecast for 10 days