The claims-based architecture can be used to augment your existing security implementation. A common approach will be to create a token service that verifies the consumer identity and creates a signed token including the claims necessary for accessing a resource from a service, living within or outside the security boundaries of the consumer.
The following diagram illustrates this scenario:
The previous recipe showed us the steps to create SamlSecurityToken
. In this recipe, we will create a claims service that accepts the client credentials and returns signed SamlSecurityToken
using the group-level permissions of the user as claims. In a Single Sign-On scenario, the generated security token will be posted using form variables to the service provider to get authenticated and make a claim for service access. We will limit the scope of this recipe to understand how a token can be generated using the token service and returned back to the client.
Create a Saml11Helper
class using the methods from the previous recipe to create SamlSecurityToken
using the ClaimSet
object. Modify the serialization helper to return a token XML string instead of writing into a file.
public static string SerializeSamlToken(SamlSecurityToken token) { StringBuilder samlBuilder = new StringBuilder(); using (XmlWriter writer = XmlWriter.Create(samlBuilder)) { try { WSSecurityTokenSerializer keyInfoSerializer = new WSSecurityTokenSerializer(); keyInfoSerializer.WriteToken(writer, token); Console.WriteLine("Saml Token Successfully Created"); } catch (Exception) { Console.WriteLine("Failed to seralize token"); } } return samlBuilder.ToString(); }
To create a SamlProvider
service, perform the following steps:
System.IdentityModel
and System.ServiceModel
assemblies. Include the Saml11Helper
class in the project. SecurityTokenAuthorizationPolicy
class to define a custom authorization policy by implementing the IAuthorizationPolicy (System.IdentityModel.Policy)
interface. Define a constructor that accepts an instance of the WindowsClaimSet
object:public class SecurityTokenAuthorizationPolicy : IAuthorizationPolicy { WindowsClaimSet claims; public SecurityTokenAuthorizationPolicy(WindowsClaimSet claims) { this.claims = claims; } }
Evaluate
method:public bool Evaluate(EvaluationContext evaluationContext, ref object state) { evaluationContext.AddClaimSet(this, claims); evaluationContext.RecordExpirationTime (DateTime.Now.AddHours(10)); return true; }
The WindowsClaimSet
instance is added to evaluationContext
and an expiration policy is set to expire in 10 hours.
ClaimsTokenAuthenticator
class to validate the incoming credentials and create a WindowsClaimSet
instance using the WindowsIdentity
class. The class inherits from WindowsUserNameSecurityTokenAuthenticator (System.IdentityModel.Selectors)
and implements the ValidateUserNamePasswordCore
method:public class ClaimsTokenAuthenticator : WindowsUserNameSecurityTokenAuthenticator { protected override ReadOnlyCollection<IAuthorizationPolicy> ValidateUserNamePasswordCore(string userName, string password) { if (IsAuthenticated(userName, password)) { string name = userName.Split('')[1]; List<IAuthorizationPolicy> policies = new List<IAuthorizationPolicy>(1); WindowsClaimSet claimSet = new WindowsClaimSet(new WindowsIdentity(name), true); policies.Add(new SecurityTokenAuthorizationPolicy(claimSet)); return policies.AsReadOnly(); } return null; } private bool IsAuthenticated(string userName, string password) { //TODO: Call the provider to validate the credentials return true; } }
The IsAuthenticated
method can be used to call the identity provider (Active Directory/Forms database) and validate the credentials. For the purpose of our recipe, we will assume that the incoming credentials are valid.
ClaimsTokenManager
class by implementing ServiceCrendentialsSecurityTokenManager
(System.ServiceModel.Security). The class implements the abstract method—CreateSecurityTokenAuthenticator to return a ClaimsTokenAuthenticator
object:public override SecurityTokenAuthenticator CreateSecurityTokenAuthenticator(SecurityTokenRequirement tokenRequirement, out SecurityTokenResolver outOfBandTokenResolver) { if (tokenRequirement.TokenType == SecurityTokenTypes.UserName) { outOfBandTokenResolver = null; return new ClaimsTokenAuthenticator(); } else { return base.CreateSecurityTokenAuthenticator(tokenRequirement, out outOfBandTokenResolver); } }
ServiceCredentials
implementation that returns the ClaimsTokenManager
object in the overridden method—CreateSecurityTokenManager:public class ClaimsProviderServiceCredentials : ServiceCredentials { public ClaimsProviderServiceCredentials() : base() { } protected override ServiceCredentials CloneCore() { return new ClaimsProviderServiceCredentials(); } public override SecurityTokenManager CreateSecurityTokenManager() { return new ClaimsTokenManager(this); } }
SamlProviderService
that retrieves ClaimSet
from the service's AuthorizationContext
and creates SamlSecurityToken
using the Saml11Helper
class. In this recipe, the serialized token is returned as an XML string:public class ClaimsProviderService : IClaimsProviderService { public string GetSaml11Token() { var claimSets = new List<ClaimSet>(ServiceSecurityContext. Current.AuthorizationContext.ClaimSets); ClaimSet claimSet = claimSets.Find((Predicate<ClaimSet>)delegate(ClaimSet target) { WindowsClaimSet defaultClaimSet = target.Issuer as WindowsClaimSet; return defaultClaimSet != null; }); var accessControlClaims = claimSet.FindClaims(ClaimTypes.Sid, Rights.PossessProperty); SamlAssertion assertion = Saml11Helper.CreateSamlAssertionFromUserNameClaims (accessControlClaims); SamlSecurityToken token = new SamlSecurityToken(assertion); return Saml11Helper.SerializeSamlToken(token); } }
The client creates an instance of the ClaimsProviderService
proxy and sets the username
and password
properties for the ClientCrendentials
object:
static void Main(string[] args) { string userName = WindowsIdentity.GetCurrent().Name; Console.WriteLine("Enter your password"); string password = GetPasswordFromConsole(); ClaimsProviderServiceClient client = new ClaimsProviderServiceClient(); client.ClientCredentials.UserName.UserName = userName; client.ClientCredentials.UserName.Password = password; client.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.PeerOrChainTrust; Console.WriteLine(client.GetSaml11Token()); Console.ReadLine(); }
When the service operation is invoked, the supplied username and password is retrieved by TokenAuthenticator
, which then creates a WindowsClaimSet
object that gets assigned to the authorization policy. Assigned ClaimSet
is retrieved back in the service using AuthorizationContext
, and the group-level permissions assigned to the user are used to create the claims for SamlSecurityToken
.
In this recipe, the token is returned as an XML string. Realistically, ClaimsProviderService
would participate in a federation scenario where it issues the token to a Security Token Service in a different realm using the WS-Trust protocol specification. This is explained further, later in the chapter.