In the
last post, I demonstrated how to consume a cross-domain WebApi service that uses SAML2 for authentication and authorization. This is useful when you want to use a shared service layer that provides data for multiple websites. However, some code work is needed to enable this on the server side as well.
In this example we'll create a new .Net WebApi layer in Visual Studio 2013. Everything in this post can be transferred to an MVC app just as easily if needed, however Ajax calls to MVC apps are most likely on the same domain so will not need this extra work. The only difference lies in whether you register CorsConfiguration in the WebApiConfig.cs or RouteConfig.cs. That said, let's get started.
First, create a new ASP.Net Web Application. We'll call the new project POC_SAML_WebApi. When you create the project, you'll be prompted for the type of web application you wish to create. Choose WebApi:
Next, you'll also need to make sure Visual Studio won't add all kinds of unnecessary authentication code and configuration to your project, as we'll add all the necessary configuration and code ourselves. Click the Change Authentication button and choose No Authentication in the pop-up. Click OK in both screens and your project will be created.
Out of the box, .Net WebApi does not support SAML2. To facilitate this, add the Thinktecture.IdentityModel library through the NuGet Package Manager.
Next, add the following code to your WebApiConfig.cs, or merge it with the existing file. Be sure to include the relevant namespaces that Visual Studio suggests:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.AllowAll().AllowAll();
var corsHandler = new CorsMessageHandler(corsConfig, config);
config.MessageHandlers.Add(corsHandler);
// default API route
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
This registers the Thinktecture CORS module for handling incoming WebApi calls. The corsConfig.AllowAll().AllowAll() call may need to be adjusted to your personal needs, but for this demonstration we'll use this. If you want more information about the different configuration options, have a look at
this post by Brock Allen. If you want to do SAML authentication for an MVC app, simply adjust above code for your RouteConfig.cs.
The last bit of code needed for SAML2 support is in the Global.asax file, which makes sure unauthorized Ajax requests aren't promptly redirected to ADFS but simply answered with a 401 error, enabling the error handling demonstrated in the last post:
protected void Application_BeginRequest(object s, EventArgs ea)
{
FederatedAuthentication.WSFederationAuthenticationModule.AuthorizationFailed += (sender, e) =>
{
if (new HttpRequestWrapper(System.Web.HttpContext.Current.Request).IsAjaxRequest())
{
e.RedirectToIdentityProvider = false;
}
};
}
Of course, you should make sure your JavaScript calling this WebApi actually adds the
X-Requested-With header for this to work.
Last but certainly not least are changes required in the Web.config file. These settings tell your application where your authentication server resides and how your application should communicate with it.
<configSections>
<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
<section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
</configSections>
<appSettings>
...
<add key="ida:AdfsMetadataEndpoint" value="[AD FS Federation Metadata endpoint]" />
<add key="ida:Audience" value="[WebApi address]" />
</appSettings>
<system.webServer>
<modules>
<add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
<add name="WSFederationAuthenticationModule" type="System.IdentityModel.Services.WSFederationAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
</modules>
</system.webServer>
<system.identityModel>
<identityConfiguration>
<audienceUris>
<add value="[WebApi address]" />
</audienceUris>
<issuerNameRegistry>
<trustedIssuers>
<add name="[AD FS certificate name]"
thumbprint="[AD FS certificate thumbprint]" />
</trustedIssuers>
</issuerNameRegistry>
</identityConfiguration>
</system.identityModel>
<system.identityModel.services>
<federationConfiguration>
<wsFederation passiveRedirectEnabled="true"
issuer="[AD FS SAML2 endpoint address]"
realm="[WebApi realm]" requireHttps="true" />
<cookieHandler requireSsl="true" />
</federationConfiguration>
</system.identityModel.services>
In these configuration settings are a couple of things you need to adjust yourself (just find/replace the following strings):
- [AD FS Federation Metadata endpoint]: This is the endpoint address for ADFS ending in "/FederationMetadata/2007-06/FederationMetadata.xml", for instance "https://adfs.mycompany.com/FederationMetadata/2007-06/FederationMetadata.xml";
- [WebApi address]: The root address where your new WebApi will be running, for instance "https://poc-saml-webapi.mycompany.com:443";
- [AD FS certificate name]: The name of the certificate you configured in ADFS for token signing;
- [AD FS certificate thumbprint]: The thumbprint of the certificate mentioned above;
- [AD FS SAML2 endpoint address]: The endpoint address for ADFS ending in "/adfs/ls"
- [WebApi realm]: The realm you'll configure in ADFS by which your WebApi server can be identified, for instance "urn:services.mycompany.com:webapi". You can pick any valid URI for this setting but it's common practice to use a urn: name here.
With this, your service is ready to be published in IIS. Make sure to configure an HTTPS binding and use the address that you configured in [WebApi address] (or vice versa, of course). One last important thing you need to do, which is often overlooked, is to enable anonymous authentication in IIS. If you don't, the OPTIONS call that is part of the CORS pre-flight will not be accepted:
Stay tuned for a demonstration on how to configure the settings needed in ADFS itself to enable single sign-on for this WebApi service or another relying party.