If you haven't read my previous articles on security, please take the time to read through each of them to give yourself a headstart into what concepts are being implemented here. Windows Identity Foundation (Geneva) provides a very simplistic view of the world of Security Token Services and as a result, it is very useful to understand how WS-Trust, Federation and even basic security works in our applications.
* IPrincipal, IIdentity and Claims (threading, WIF, Asp.NET Forms Authentication)
* Overview on Digital Signatures (Signing, Encrypting, Hashing)
* Windows Identity Foundation (Overview, WS-Trust, Federation)
Though, don't take my articles to be the end all in what you read. Reading is important and you should take time to dive into the topics in detail to get a better grasp.
Setting up the Security Token Service
First, download and install the Windows Identity Foundation (Geneva Framework)
Next, setup a new project in Visual Studio, preferrably a Console Application to demonstrate.
Add a reference to Microsoft.IdentityModel and System.ServiceModel, as well as System.IdentityModel, then create a new class for the Security Token Service, I will call mine HealthCenter. Inherit this class from SecurityTokenService and implement the abstract class.
public class HealthCenter : SecurityTokenService
{
public HealthCenter(SecurityTokenServiceConfiguration config) : base(config) { }
protected override IClaimsIdentity GetOutputClaimsIdentity(IClaimsPrincipal principal, RequestSecurityToken request, Scope scope)
{
throw new NotImplementedException();
}
protected override Scope GetScope(IClaimsPrincipal principal, RequestSecurityToken request)
{
throw new NotImplementedException();
}
}
You'll notice that the two methods GetOutputClaimsIdentity and GetScope are the only methods we need to setup in the service. The configuration provided is another section that you must include with the constructor.
GetScope
At this point it should be noted that and client connecting the the STS will have already passed authentication. This means authentication can be configured to use any method you want to see fit within your ecosystem. The request security token (RST) provided will tell this method who the relying party is as well as additional claim requests. This method performs the following responsibilities:
* Validate the relying party to determine if the request is on behalf of a trusted source
* Load the signing credentials for the STS (typically a certificate)
* Load the encrypting credentials based on relying party it is for (or use a generic one)
* Create a new scope for the RST with the signing and encrypting credentials
For simplicity let's adjust this to handle a specific relying party with a few specific credentials.
protected override Scope GetScope(IClaimsPrincipal principal, RequestSecurityToken request)
{
string uri = request.AppliesTo.Uri.AbsoluteUri;
if (uri == "net.tcp://localhost:2314/HealthRecords/")
{
SigningCredentials signing = new X509SigningCredentials(GetSigningCertificate());
EncryptingCredentials encrypting = new X509EncryptingCredentials(GetEncryptingCredentials(uri));
Scope rstScope = new Scope(uri, signing, encrypting);
return rstScope;
}
else
throw new InvalidRequestException("The request must apply to a trusted relying party.");
}
As recommended, we are using X509 based Signing and Encrypting credentials. This means we need to load an X509Certificate2 from the certificate store or from disk. Take a look at my previous article on how to load certificates from the certificate store.
The only difference between the signing and encrypting certificate is that the encrypting certificate is based on the applies to address. If we had setup a configuration item for this or someway of linking the address to a certificate then that part would be resolved.
GetOutputClaimsIdentity
Now that the scope has been defined for the request security token response (RSTR), all that is left are the claims about the client and any requested or related claims for the relying party. We will need to create the actual claims identity based on the request. You have the option of simply returning the same claims or injecting new ones based on the Request Claims. Take a look at the sample provided in the installation samples for more.