Identity Server 3 using WS-Federation

Scott Brady
Scott Brady
Identity Server ・ Updated June 2017

IdentityServer3 is no longer supported. I highly recommend that you consider moving to IdentityServer4 or Duende IdentityServer.

Identity Server 3 is by design an OpenID Connect Provider, however many developers do not have the luxury of using the latest and greatest authentication protocols or have to integrate with existing Identity Providers incompatible with OpenID Connect. To solve this the Identity Server team have implemented various features that enable developers to use the WS-Federation protocol.

OpenID Connect vs WS-Federation

The best way to compare OpenID Connect and WS-Federation is to look at the reason they exist (i.e. the problem they solved) and the technologies they typically use.

WS-Federation was created by Microsoft as an extension of WS-Trust, providing a federated identity architecture. It allowed businesses to move away from intranet only and start exposing their services or using external services. In a Microsoft environment this would be using WS* technologies, over SOAP and using SAML and XML. You can think of it as business to business communication, where both businesses would be using the same or a very similar stack.

OpenID Connect is the latest and greatest in authentication protocols, building upon the existing OAuth2 protocol (which by itself is an authorization framework) and adding authentication. This moves away from tightly bound enterprise scenarios, using the much more lightweight JSON and JSON Web Tokens (JWTs), communicating over HTTP. An immediate benefit of this is that it opens up the world of mobile devices to your application. You can read more about OpenID Connect and its benefits in some of my previous articles.

To get a good comparative overview of WS-Federation, OAuth and OpenID Connect, check out the first 10 minutes or so of this Dominick Baier talk from NDC 2014 or the initial chapter of his Pluralsight course: Introduction to OAuth2, OpenID Connect and JWTs.

Identity Server and WS-Federation

Identity Server supports two types of WS-Federation compatibility: one for communicating with WS-Federation Identity Providers (for example ADFS) and another for exposing Identity Server as an Identity Provider using WS-Federation.

Identity Server as a Relying Party

Allowing Identity Server to use WS-Federation Identity Providers such as ADFS is as exactly the same as configuring any other external identity provider, when using Microsoft’s OWIN security packages.

This is done using middleware from the Microsoft.Owin.Security.WsFederation nuget package and placed in the typical ConfigureIdentityProviders method that can be passed into the AuthenticationOptions property of your IdentityServerOptions. If you are unfamiliar with this process, check out the Identity Provider section of the Identity Server documentation, this has information on how to setup up social providers such as Google and also some of what we are about to cover.

Configuring ADFS as a WS-Federation Identity Provider

To demonstrate Identity Server using a WS-Federation Identity Provider, we will look at a simple implementation using ADFS. This example will assume you have a working Identity Server implementation such as that found in my Identity Server implementation guide and that you have a functioning ADFS server.

Relying Party Wizard

On our ADFS installation we can use the 'Add Relying Party Trust Wizard' to add Identity Server as a relying party.

Data Source

We will need to enter our relying party data manually.

Display Name

Enter a simple display name for the relying party. This is only for reference when managing ADFS.

ADFS Profile

Choose your ADFS profile. All pretty self explanatory so far.

Relying Party URL

Here we enable support for WS-Federation Passive Protocol and enter the URL for our Identity Server relying party, in this case the HTTPS localhost URL set up in the implementation guide. Note that this a URL, so there's a trailing / . From personal experience it pays to be consistent with this.

Relying Party Identifiers

We now have the opporunity to add any more identifiers to our relying party. For instance, we could add the other style of identifiers such as urn:identityServer.

Once this has done we can continue with default settings until the relying party is complete.

Claim Transformations

Once we create a relying party we are automatically given the option to create some claim transformations.

Claim Rule Template

Here we'll use 'Send LDAP Attributes as Claims'.

Claims Rules

Now we'll set up some basic claim transformations. In this case UPN, email address and name. Note that Identity Server requires a subject or name identifier claim to log in an identity, otherwise an error will be returned. In this case I am using the UPN.

Identity Server Setup

We'll need the following nuget package:

install-package Microsoft.Owin.Security.WsFederation

Now for the ConfigureIdentityProviders method:

public static void ConfigureIdentityProviders(IAppBuilder app, string signInAsType) {
    app.UseWsFederationAuthentication(
        new WsFederationAuthenticationOptions {
            Wtrealm = "urn:identityServer",
            MetadataAddress = "https://adfs.domain.com/federationmetadata/2007-06/federationmetadata.xml",
            AuthenticationType = "adfs",
            Caption = "ADFS",
            SignInAsAuthenticationType = signInAsType
        });
}

The Wtrealm can be one of the Relying Party Identifiers we configured earlier within ADFS, in this case urn:identityServer

The MetaAddress is the address of our ADFS server appended with /federationmetadata/2007-06/federationmetadata.xml.

The value of AuthenticationType can be used to explicitly specify an authentication type to use and Caption is the value used in the Identity Server login screen. As with all external identity providers when working with Identity Server, the SignInAsAuthenticationType must be parameter driven, to allow Identity Server to set it.

Now we can run the solution and login using the ADFS external identity provider, letting the WS-Federation OWIN middleware to take over and bring us to a login screen similar to that below:

ADFS Login Screen

If you need further help setting up Identity Server as a relying party in ADFS check out this article by Vittorio Bertocci. Final tip, if you are working across different servers, ensure your SSL certificates are trusted on both sides (authorities and all). I’ve found this can cause confusion within some test environments, especially since both require HTTPS. Again, when setting up your claims mapping, ensure you have a name identifier set as this or a subject is required by Identity Server. Also don’t fall into the trap of thinking the Identity Server token signing certificate is the same as an SSL certificate.

Identity Server over WS-Federation

Identity Server communicating using the WS-Federation protocol is possible thanks to a plugin developed by the Identity Server team. This plugin turns Identity Server into a WS-Federation Identity Provider, which can be communicated with in the same way as any other WS-Federation resource.

This plugin is available in the IdentityServer3.WsFed package and functions in a similar way to the core Identity Server middleware.

We will again build upon the Identity Server implementation from the implementation guide and we will need the following package:

install-package IdentityServer3.WsFederation

First we need to create some Relying Parties. As WS-Federation is quite different to OpenID Connect it requires its own list of Relying Parties, this implementation's equivalent of the Clients you are used to. Identity Server does not currently have a persistence layer for Relying Parties, however they do offer another InMemory Service, that uses a list of Relying Parties, that we can use to demonstrate functionality.

public static class RelyingParties {
    public static IEnumerable<RelyingParty> Get() {
		return new List<RelyingParty> {
			new RelyingParty {
				Realm = "urn:testClient", 
				Name = "testclient", 
				Enabled = true, 
                ReplyUrl = "https://localhost:4004/TestClient/", 
				TokenType = TokenTypes.Saml2TokenProfile11, 
				ClaimMappings =
					new Dictionary<string, string> {
						{ "sub", ClaimTypes.NameIdentifier }, 
						{ "name", ClaimTypes.Name }, 
						{ "given_name", ClaimTypes.GivenName }, 
						{ "family_name", ClaimTypes.Surname }, 
						{ "email", ClaimTypes.Email }, 
						{ "upn", ClaimTypes.Upn }
					}
            }
        };
    }
}

Here we are creating the realm for the relying party, reply urls, token types and any claim mappings (in this case overwriting the cleaner OpenID Connect claim types and replacing them with the xml namespaces that WS-Federation style applications often find useful).

The available token types are SAML 1.1, 2.0 and JWT. Here we are mostly using the default settings for options for lifetimes and algorithms, but these are also configurable to an extent. Check out the official documentation for all relying party configuration options.

SAML 1.1

Note that if you are using SAML 1.1 you will need a claims prefix, as this token type will error if any claim types are not in the format of namespace/claim. This can be achieved using the Relying Party DefaultClaimTypeMappingPrefix property. The value of this property will be prefixed to all claim types that do not have an explicit claim map.

Now we need to register these with our Identity Server implementation. This is done by adding the following to our owin setup:

private void ConfigureWsFederation(IAppBuilder pluginApp, IdentityServerOptions options) {
    var factory = new WsFederationServiceFactory(options.Factory);
    factory.Register(new Registration<IEnumerable<RelyingParty>>(RelyingParties.Get()));
	factory.RelyingPartyService = 
        new Registration<IRelyingPartyService>(typeof(InMemoryRelyingPartyService));

pluginApp.UseWsFederationPlugin(new WsFederationPluginOptions { IdentityServerOptions = options, Factory = factory }); }

This method is then registered with our Identity Server implementation using the PluginConfiguration property of IdentityServerOptions.

public void Configuration(IAppBuilder app) {
    var options = new IdentityServerOptions {
        // usual configuration here.
        Factory = factory,
        PluginConfiguration = ConfigureWsFederation
    };
    app.UseIdentityServer(options);
}

Now we can start interacting with Identity Server using WS-Federation. Identity Server uses the path /wsfed for all ws-federation interactions, allowing OpenID Connect functionality to continue. Metadata is available via /wsfed/metadata.

WS-Federation Metadata

Requests can then be made using the usual WS-Federation parameters:

  • wa: Sign in using a value of wsignin1.0 and sign out using wsignout1.0
  • wtrealm: Value dictates which relying party you are logging into
  • whr: This allows you to select a specific identity provider to log in with(see our ADFS AuthenticationType earlier), similar to the acr_values idp property used in OpenID Connect
  • wctx: This value is round tripped (think OAuth state or OIDC nonce)

To interact with Identity Server using WS-Federation, use the Microsoft.Owin.Security.WsFederation OWIN library. You do not need to use any third party plugins or software.

Warning: Trying to log into a relying party that does not exist will cause the login screen to be displayed, let you log in and only then tell you are using an invalid client.

Relying Party Persistence

You can move away from the in-memory user store to something a bit more maintainable, by using the IdentityServer3.WsFederation.EntityFramework library. This would that make our ConfigureWsFederation method look something like the below:

private void ConfigureWsFederation(IAppBuilder pluginApp, IdentityServerOptions options) {
    var efConfig = new EntityFrameworkServiceOptions {
        ConnectionString = "DefaultConnection"
    };

var factory = new WsFederationServiceFactory(options.Factory); factory.RegisterRelyingPartyService(efConfig);
pluginApp.UseWsFederationPlugin(new WsFederationPluginOptions { IdentityServerOptions = options, Factory = factory }); }

You can find a fully configured example in the WS-Federation Entity Framework repository itself.

Source Code

I've added an implementation of the WS-Federation plugin to my example implementation of Identity Server. Look for Startup_WsFederation and the owin:AppStartup value of WsFederation.