Building a claims aware ASP.NET web application

 

This post is a follow up to my previous post about Building a claims aware WCF Service using Windows Identity Foundation. This time, I’ll lay out the steps needed to secure access to a ASP.NET web application using Windows Identity Foundation.

1) Go to the STS application and add the following code to Default.aspx:

protected void Page_PreRender(object sender, EventArgs e)
        {
            string action = Request.QueryString[WSFederationConstants.Parameters.Action];

            try
            {
                if (action == WSFederationConstants.Actions.SignIn)
                {
                    // Process signin request.
                    SignInRequestMessage requestMessage = (SignInRequestMessage)WSFederationMessage.CreateFromUri(Request.Url);
                    if (User != null && User.Identity != null && User.Identity.IsAuthenticated)
                    {
                        SecurityTokenService sts = new AppSecurityTokenService(AppSecurityTokenServiceConfiguration.Current);
                        SignInResponseMessage responseMessage = FederatedPassiveSecurityTokenServiceOperations.ProcessSignInRequest(requestMessage, User, sts);
                        FederatedPassiveSecurityTokenServiceOperations.ProcessSignInResponse(responseMessage, Response);
                    }
                    else
                    {
                        throw new UnauthorizedAccessException();
                    }
                }
                else if (action == WSFederationConstants.Actions.SignOut)
                {
                    // Process signout request.
                    SignOutRequestMessage requestMessage = (SignOutRequestMessage)WSFederationMessage.CreateFromUri(Request.Url);
                    FederatedPassiveSecurityTokenServiceOperations.ProcessSignOutRequest(requestMessage, User, requestMessage.Reply, Response);
                }
                else
                {
                    throw new InvalidOperationException(
                        String.Format(CultureInfo.InvariantCulture,
                                       "The action '{0}' (Request.QueryString['{1}']) is unexpected. Expected actions are: '{2}' or '{3}'.",
                                       String.IsNullOrEmpty(action) ? "<EMPTY>" : action,
                                       WSFederationConstants.Parameters.Action,
                                       WSFederationConstants.Actions.SignIn,
                                       WSFederationConstants.Actions.SignOut));
                }
            }
            catch (Exception exception)
            {
                throw new Exception("An unexpected error occurred when processing the request. See inner exception for details.", exception);
            }
        }

This is just a copy of the boliler plate code that is auto generated by Visual Studio when you create an ASP.NET Security Token Service Web Site.

The two most interesting method calls in the above code is the call to FederatedPassiveSecurityTokenServiceOperations.ProcessSignInRequest(…) and FederatedPassiveSecurityTokenServiceOperations.ProcessSignInResponse(..).

What ProcessSignInRequest(..) does is creating a RST (RequestSecurityToken) based on the requestMessage and a ClaimsPrincipal based on the IPrincipal passed in. It then calls the SecurityTokenService.Issue(…) method passing in the RST and the ClaimsPrincipal and returns a SignInResponseMessage based on the RSTR (RequestSecurityTokenResponse) returned by the SecurityTokenService.Issue method.

ProcessSignInResponse(…) simply writes the content of the the SignInResponseMessage to the HttpResponse output stream.

2) Add a Login.aspx page with a single button. In the button click event, add the following code to simulate a successful login

void buttonLogin_Click(object sender, EventArgs e)
       {
           if (Request.QueryString["ReturnUrl"] != null)
           {
               FormsAuthentication.RedirectFromLoginPage("Tore Senneseth", false);
           }
           else
           {
               FormsAuthentication.SetAuthCookie("Tore Senneseth", false);
               Response.Redirect("default.aspx");
           }

       }

 

3) Open the web.config file and change the authentication from Windows to Forms and deny all unauthenticated users access.

<authentication mode="Forms">
      <forms loginUrl="Login.aspx" protection="All" timeout="30" name=".ASPXAUTH" path="/"
             requireSSL="false" slidingExpiration="true" defaultUrl="default.aspx"
             cookieless="UseDeviceProfile" enableCrossAppRedirects="false"/>
    </authentication>
    <!-- Deny Anonymous users. -->
    <authorization>
      <deny users="?"/>
    </authorization>

4) Switch back to the main web application, SecureWebApplication. (Where our claims aware WCF Service is implemented) and open the web.config-file

Change authentication from Windows to None and deny anonymous access.

<authentication mode="None"/>
    <authorization>
      <deny users="?"/>
    </authorization>

6) Add Windows Identity Foundation httpModules to configuration/system.web/httpModules

<httpModules>
      <add name="ClaimsPrincipalHttpModule"
           type="Microsoft.IdentityModel.Web.ClaimsPrincipalHttpModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
      <add name="WSFederationAuthenticationModule"
           type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
      <add name="SessionAuthenticationModule"
           type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    </httpModules>

7) Add Windows Identity Foundation modules to configuration/system.webServer/modules

<modules>
      <add name="ClaimsPrincipalHttpModule"
           type="Microsoft.IdentityModel.Web.ClaimsPrincipalHttpModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
           preCondition="managedHandler"/>
      <add name="WSFederationAuthenticationModule"
           type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
           preCondition="managedHandler"/>
      <add name="SessionAuthenticationModule"
           type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
           preCondition="managedHandler"/>
    </modules>

We’ve added 3 modules, ClaimsPrincipalHttpModule, SessionAuthenticationModule and WSFederationAuthenticationModule.

What does all of these modules do? For a good explaination, take a look at this blog post by Dominic Baier. Also, you’ll find a detailed explaination about the WSFederationAuthenticationModule in the MSDN documentation here.

8 ) Modify the microsoft.identityModel/service/ element by adding a serviceCertificate element. Here, we’ll need to specify the certificate used by the STS to encrypt the security token so that the Relying party (our web site) is able to decrypt the security token once it is received. In the attached sample project, the certificate being used has the subject name STSTestCert.

<service>
      <serviceCertificate>
        <certificateReference findValue="0E2A9EB75F1AFC321790407FA4B130E0E4E223E2" storeLocation="LocalMachine" storeName="My" x509FindType="FindByThumbprint"/>
      </serviceCertificate>
    </service>

9) Add the uri of your application to the audienceUris element. 

<audienceUris>
        <add value="http://localhost:25546"/>
        <add value="http://localhost:25546/MyService.svc"/>
      </audienceUris>

The audienceUris section specifies a list of valid audience URIs for incoming SAML tokens. 
When the client sends an RST (RequestSecurityToken) to the STS, it usually includes an AppliesTo setting that indicates who the token should be issued to. If present, the STS will use this information to populate the SAML token audience uri setting. When the client receives the issued SAML token, the audience URI setting in the SAML token must match one of the audience URIs specified in the audienceUris list. If the audience Uri setting in the SAML token is not present in the audienceUris list, the token is refused. If you want to disable this behavior, you can set the mode property to “Never”, <audienceUris mode=”Never”/>.

10) To verify that your site is working, set the relying party (SecureWebSite), as the startup project of the solution and Default.aspx as start page, add an asp:Label to Default.aspx and add the following code to the Page_Load event handler.

this.labelUserName.Text = ((IClaimsPrincipal)Thread.CurrentPrincipal).Identity.Name;