SharePoint is a popular document collaboration platform from Microsoft, capable of running multiple web applications which in turn consist of multiple websites. SharePoint also comes with out-of-the-box support with other Microsoft products such as Office 365 and Active Directory.
But what if you want to use SharePoint with non-Active Directory accounts? Or have SSO across all of your applications, even on mobile devices? Even Azure AD B2C struggles with this due to its lack of support for SAML 1.1 tokens. This is where traditional identity providers start to struggle, and flexible solutions, such IdentityServer, excel.
IdentityServer is a framework that provides out-of-the-box Single Sign-On (SSO) and security for your APIs. By default, IdentityServer supports OAuth and OpenID Connect; however, it can also support the WS-Federation protocol and SAML tokens thanks to a commercial add-on.
This tutorial will look at how you can configure IdentityServer as a “Trusted Identity Token Issuer” for SharePoint using WS-Federation. This article was written against SharePoint 2013.
Configuring IdentityServer for WS-Federation
In this demo, it’s assumed you have already have a working implementation of IdentityServer, capable of authenticating users. If you do not, check out my tutorial for both IdentityServer4 and Duende IdentityServer. In-memory store will work fine for this tutorial.
To have IdentityServer act as an identity provider via WS-Federation you are going to use the Rsk.WsFederation compenent from identityserver.com. This component is available on nuget for both IdentityServer4 or Duende IdentityServer. In this tutorial, samples will use the IdentityServer4 version.
dotnet add package Rsk.WsFederation.IdentityServer4
To use this library, you will need a license key. Demo licenses can be emailed to you on the IdentityServer WS-Federation product page.
Resources, Clients & Relying Parties
You now need to configure your resources (what applications can request, allowing for scoped access), client applications (the list of applications allowed to use IdentityServer), and relying parties (extra, WS-Federation specific, details about your clients). In code, these look like:
public class Config {
public static List<IdentityResource> GetIdentityResources() {
return new List<IdentityResource> {
// The sub/nameid claim
new IdentityResources.OpenId(),
// All claim for user profile info (think name, date of birth, etc.)
new IdentityResources.Profile(),
new IdentityResources.Email()
};
}
public static List<Client> GetClients() {
return new List<Client> {
new Client {
// The realm of your RP
ClientId = "urn:sharepoint",
// Required for ws-fed clients
ProtocolType = IdentityServerConstants.ProtocolTypes.WsFederation,
// Trust uri of your SharePoint web application (web app, appended with _trust/default.aspx)
RedirectUris = {"http://win-ok5ojhtg3d9/_trust/default.aspx"},
// SAML token lifetime (in seconds)
IdentityTokenLifetime = 36000,
// Links to configured resources
AllowedScopes = {"openid", "profile", "email"}
}
};
}
public static List<RelyingParty> GetRelyingParties() {
return new List<RelyingParty> {
new RelyingParty {
// Same as ClientId. Used to link config
Realm = "urn:sharepoint",
// SAML 1.1 token type required by SharePoint
TokenType = WsFederationConstants.TokenTypes.Saml11TokenProfile11,
// Transform claim types from oidc standard to xml types
// Only mapped claims will be returned for SAML 1.1 tokens
ClaimMapping = new Dictionary<string, string> {
{JwtClaimTypes.Subject, ClaimTypes.NameIdentifier},
{JwtClaimTypes.Email, ClaimTypes.Email}
},
// Defaults for RS256
DigestAlgorithm = SecurityAlgorithms.Sha256Digest,
SignatureAlgorithm = SecurityAlgorithms.RsaSha256Signature,
// Using the subject's ID as the SAML NameID value
SamlNameIdentifierFormat = WsFederationConstants.SamlNameIdentifierFormats.UnspecifiedString
}
};
}
}
Users
For users, you’ll need a sample user with an ID and an email claim. If you are using the Quickstart UI test users, then it would look something like the following:
public static List<TestUser> GetUsers() {
return new List<TestUser> {
new TestUser {
SubjectId = "B9734696-5CC4-45FC-8674-C9340449D082",
Username = "scott",
Password = "password",
Claims = new List<Claim> {new Claim(JwtClaimTypes.Email, "[email protected]"}
}
};
}
Registratering the WS-Federation component and relying parties
Now you need to configure IdentityServer to use the WS-Federation plugin using AddWsFederationPlugin
and a relying party store.
You do this in your ConfigureServices
method of your Startup
class.
public void ConfigureServices(IServiceCollection services) {
// existing registrations...
services.AddIdentityServer()
.AddSigningCredential("CN=ScottBrady91")
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetUsers())
.AddWsFederationPlugin(options => { /* configure license here */ })
.AddInMemoryRelyingParties(Config.GetRelyingParties());
}
The WS-Federation component also comes with it’s own endpoints, which means you'll need to add UseIdentityServerWsFederationPlugin
to your Configure
method.
public void Configure(IApplicationBuilder app) {
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer()
.UseIdentityServerWsFederationPlugin();
app.UseAuthorization();
app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute());
}
For your signing credentials, you’ll need to use a private key in an X.509 certificate.
This is a requirement of SharePoint - it will use the corresponding public key, but also validate the certificate chain, so make sure you have access to any root certificates.
You can retrieve a certificate from your machine’s certificate store by name using something like AddSigningCredential("CN=ScottBrady91")
.
To create your own X.509 and RSA keys, check out OpenSSL.
SharePoint Integration
Now that IdentityServer is ready, you’ll need to configure IdentityServer to be a Trusted Identity Provider within SharePoint.
As you previously saw, you are going to need both the certificate containing IdentityServer’s public key and all other certificates in the chain, up to the root authority, that need to be added to SharePoint. Unfortunately we cannot get around this as SharePoint does not use WS-Federation metadata.
To add these certificates and add IdentityServer 4 as a trusted identity provider you can use the SharePoint 2013 Management Shell:
$realm = "urn:sharepoint"
$identityProviderUrl = "http://rskdev/wsfederation"
# Add the root CA for the Identity Server token signing certificate
$rootCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("C:\Users\Administrator\Desktop\ScottBrady91Root.cer")
New-SPTrustedRootAuthority -Name "Token Signing Cert Root" -Certificate $rootCert
# Add the Identity Server token signing certificate
$signingCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("C:\Users\Administrator\Desktop\ScottBrady91.cer")
New-SPTrustedRootAuthority -Name "Token Signing Cert" -Certificate $signingCert
# Create SP Claim Mappings (Required)
$nameIdClaimMap=New-SPClaimTypeMapping -IncomingClaimType "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" -IncomingClaimTypeDisplayName "NameId" -LocalClaimType "https://identityserver/name"
$emailClaimMap=New-SPClaimTypeMapping -IncomingClaimType "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" -IncomingClaimTypeDisplayName "Email" -SameAsIncoming
# Create SP Trusted Identity Provider
New-SPTrustedIdentityTokenIssuer -Name "IdentityServer4" -Description "WS-Federation Identity Provider" -Realm $realm -SignInUrl $identityProviderUrl -ClaimsMappings $nameIdClaimMap, $emailClaimMap -IdentifierClaim $nameIdClaimMap.InputClaimType -ImportTrustCertificate $signingCert -UseWReply
Your final configuration should look something like this:
Now you can go into your SharePoint Central Administration site and configure a web application to use IdentityServer as it’s Trusted Identity Provider. This is done in the Application Management > Manage Web Applications, then for your chosen web application, select Authentication Providers. Then you can select IdentityServer as an authentication provider. You can deselect windows now, or once you have finished configuration.
Saving changes here can take quite a while.
You can also set a user policy using an "All Users" policy for users from IdentityServer.
This will resolve to something like c:0!.s|trusted|identityserver4
.
Authorization for users is done within SharePoint (for each site or web app, it’s up to you).
IdentityServer is only responsible for user authentication and creating SAML tokens.
And now, when you visit our web application, you will get challenged by IdentityServer to authenticate, and if successful, you will end up in your SharePoint application.
Advanced
This is just the basic integration of IdentityServer with SharePoint, using the well known method of configuring a SharePoint identity provider. The next steps get a little more advanced and are not covered in this intro article.
IdentityServer & the SharePoint People Picker
Currently the people picker within SharePoint will not be able to resolve individual users from IdentityServer, meaning you cannot configure permission rules based on IdentityServer users. To work around this, you must create a claims provider for IdentityServer for use within SharePoint. This claims provider will need to query IdentityServer’s user store. This could be done directly to the database or using an API (protected by IdentityServer using OAuth).
User Profile Synchronization
One feature we lose that was out of the box with AD is a hook for the user profile synchronization service. This would require custom development to recreate this functionality.
Source Code
You can sample source code for IdentityServer using WS-Federation on Rock Solid Knowledge’s GitHub.