How to: Create a Custom Client Identity Verifier

The identity feature of Windows Communication Foundation (WCF) enables a client to specify in advance the expected identity of the service. Whenever a server authenticates itself to the client, the identity is checked against the expected identity. (For an explanation of identity and how it works, see Service Identity and Authentication.)

If needed, the verification can be customized using a custom identity verifier. For example, you can perform additional service identity verification checks. In this example, the custom identity verifier checks additional claims in the X.509 certificate returned from the server. For a sample application, see Service Identity Sample.

To extend the EndpointIdentity class

  1. Define a new class that derives from the EndpointIdentity class. This example names the extension OrgEndpointIdentity.

  2. Add private members along with properties that will be used by the extended IdentityVerifier class to perform the identity check against claims in the security token returned from the service. This example defines one property: the OrganizationClaim property.

    public class OrgEndpointIdentity : EndpointIdentity
    {
        private string orgClaim;
        public OrgEndpointIdentity(string orgName)
        {
            orgClaim = orgName;
        }
    
        public string OrganizationClaim
        {
            get { return orgClaim; }
            set { orgClaim = value; }
        }
    }
    
    Public Class OrgEndpointIdentity
        Inherits EndpointIdentity
        Private orgClaim As String
    
        Public Sub New(ByVal orgName As String)
            orgClaim = orgName
        End Sub
    
        Public Property OrganizationClaim() As String
            Get
                Return orgClaim
            End Get
            Set(ByVal value As String)
                orgClaim = value
            End Set
        End Property
    End Class
    

To extend the IdentityVerifier class

  1. Define a new class that derives from IdentityVerifier. This example names the extension CustomIdentityVerifier.

    public class CustomIdentityVerifier : IdentityVerifier
    {
        // Code to be added.
        public override bool CheckAccess(EndpointIdentity identity, AuthorizationContext authContext)
        {
            throw new Exception("The method or operation is not implemented.");
        }
    
        public override bool TryGetIdentity(EndpointAddress reference, out EndpointIdentity identity)
        {
            throw new Exception("The method or operation is not implemented.");
        }
    }
    
    Public Class CustomIdentityVerifier
        Inherits IdentityVerifier
        ' Code to be added.
    
        Public Overrides Function CheckAccess(ByVal identity As EndpointIdentity, _
                                              ByVal authContext As AuthorizationContext) As Boolean
            Throw New Exception("The method or operation is not implemented.")
        End Function
    
        Public Overrides Function TryGetIdentity(ByVal reference As EndpointAddress, _
                                                 <System.Runtime.InteropServices.Out()> ByRef identity As EndpointIdentity) As Boolean
            Throw New Exception("The method or operation is not implemented.")
        End Function
    End Class
    
  2. Override the CheckAccess method. The method determines whether the identity check succeeded or failed.

  3. The CheckAccess method has two parameters. The first is an instance of the EndpointIdentity class. The second is an instance of the AuthorizationContext class.

    In the method implementation, examine the collection of claims returned by the ClaimSets property of the AuthorizationContext class, and perform authentication checks as required. This example begins by finding any claim that is of type "Distinguished Name" and then compares the name to the extension of the EndpointIdentity (OrgEndpointIdentity).

    public override bool CheckAccess(EndpointIdentity identity, AuthorizationContext authContext)
    {
        bool returnvalue = false;
    
        foreach (ClaimSet claimset in authContext.ClaimSets)
        {
            foreach (Claim claim in claimset)
            {
                if (claim.ClaimType == "http://schemas.microsoft.com/ws/2005/05/identity/claims/x500distinguishedname")
                {
                    X500DistinguishedName name = (X500DistinguishedName)claim.Resource;
                    if (name.Name.Contains(((OrgEndpointIdentity)identity).OrganizationClaim))
                    {
                        Console.WriteLine("Claim Type: {0}", claim.ClaimType);
                        Console.WriteLine("Right: {0}", claim.Right);
                        Console.WriteLine("Resource: {0}", claim.Resource);
                        Console.WriteLine();
                        returnvalue = true;
                    }
                }
            }
    
        }
        return returnvalue;
    }
    
    
    Public Overrides Function CheckAccess(ByVal identity As EndpointIdentity, _
                                          ByVal authContext As AuthorizationContext) As Boolean
    
        Dim returnvalue = False
        For Each claimset In authContext.ClaimSets
            For Each claim In claimset
                If claim.ClaimType = "http://schemas.microsoft.com/ws/2005/05/identity/claims/x500distinguishedname" Then
                    Dim name = CType(claim.Resource, X500DistinguishedName)
                    If name.Name.Contains((CType(identity, OrgEndpointIdentity)).OrganizationClaim) Then
                        Console.WriteLine("Claim Type: {0}", claim.ClaimType)
                        Console.WriteLine("Right: {0}", claim.Right)
                        Console.WriteLine("Resource: {0}", claim.Resource)
                        Console.WriteLine()
                        returnvalue = True
                    End If
                End If
            Next claim
        Next claimset
        Return returnvalue
    
    End Function
    

To implement the TryGetIdentity method

  1. Implement the TryGetIdentity method, which determines whether an instance of the EndpointIdentity class can be returned by the client. The WCF infrastructure calls the implementation of the TryGetIdentity method first to retrieve the service's identity from the message. Next, the infrastructure calls the CheckAccess implementation with the returned EndpointIdentity and AuthorizationContext.

  2. In the TryGetIdentity method, put the following code:

    public override bool TryGetIdentity(EndpointAddress reference, out EndpointIdentity identity)
    {
        return IdentityVerifier.CreateDefault().TryGetIdentity(reference, out identity);
    }
    
    Public Overrides Function TryGetIdentity(ByVal reference As EndpointAddress, _
                                             <System.Runtime.InteropServices.Out()> ByRef identity As EndpointIdentity) As Boolean
        Return IdentityVerifier.CreateDefault().TryGetIdentity(reference, identity)
    End Function
    
    

To implement a custom binding and set the custom IdentityVerifier

  1. Create a method that returns a Binding object. This example begins creates an instance of the WSHttpBinding class and sets its security mode to Message, and its ClientCredentialType to None.

  2. Create a BindingElementCollection using the CreateBindingElements method.

  3. Return the SecurityBindingElement from the collection and cast it to a SymmetricSecurityBindingElement variable.

  4. Set the IdentityVerifier property of the LocalClientSecuritySettings class to a new instance of the CustomIdentityVerifier class created previously.

    public static Binding CreateCustomSecurityBinding()
    {
        WSHttpBinding binding = new WSHttpBinding(SecurityMode.Message);
        //Clients are anonymous to the service.
        binding.Security.Message.ClientCredentialType = MessageCredentialType.None;
        //Secure conversation is turned off for simplification. If secure conversation is turned on, then 
        //you also need to set the IdentityVerifier on the secureconversation bootstrap binding.
        binding.Security.Message.EstablishSecurityContext = false;
    
        // Get the SecurityBindingElement and cast to a SymmetricSecurityBindingElement to set the IdentityVerifier.
        BindingElementCollection outputBec = binding.CreateBindingElements();
        SymmetricSecurityBindingElement ssbe = (SymmetricSecurityBindingElement)outputBec.Find<SecurityBindingElement>();
    
        //Set the Custom IdentityVerifier.
        ssbe.LocalClientSettings.IdentityVerifier = new CustomIdentityVerifier();
    
        return new CustomBinding(outputBec);
    }
    
    Public Shared Function CreateCustomSecurityBinding() As Binding
        Dim binding As New WSHttpBinding(SecurityMode.Message)
    
        With binding.Security.Message
            'Clients are anonymous to the service.
            .ClientCredentialType = MessageCredentialType.None
            'Secure conversation is turned off for simplification. If secure conversation is turned on, then 
            'you also need to set the IdentityVerifier on the secureconversation bootstrap binding.
            .EstablishSecurityContext = False
        End With
        ' Get the SecurityBindingElement and cast to a SymmetricSecurityBindingElement to set the IdentityVerifier.
        Dim outputBec = binding.CreateBindingElements()
        Dim ssbe = CType(outputBec.Find(Of SecurityBindingElement)(), SymmetricSecurityBindingElement)
    
        'Set the Custom IdentityVerifier.
        ssbe.LocalClientSettings.IdentityVerifier = New CustomIdentityVerifier()
    
        Return New CustomBinding(outputBec)
    End Function
    
  5. The custom binding that is returned is used to create an instance of the client and class. The client can then perform a custom identity verification check of the service as shown in the following code.

    using (CalculatorClient client = new CalculatorClient(customSecurityBinding, serviceAddress))
    {
    
    Using client As New CalculatorClient(customSecurityBinding, serviceAddress)
    

Example

The following example shows a complete implementation of the IdentityVerifier class.

class CustomIdentityVerifier : IdentityVerifier
{
    public override bool CheckAccess(EndpointIdentity identity, AuthorizationContext authContext)
    {
        bool returnvalue = false;

        foreach (ClaimSet claimset in authContext.ClaimSets)
        {
            foreach (Claim claim in claimset)
            {
                if (claim.ClaimType == "http://schemas.microsoft.com/ws/2005/05/identity/claims/x500distinguishedname")
                {
                    X500DistinguishedName name = (X500DistinguishedName)claim.Resource;
                    if (name.Name.Contains(((OrgEndpointIdentity)identity).OrganizationClaim))
                    {
                        Console.WriteLine("Claim Type: {0}", claim.ClaimType);
                        Console.WriteLine("Right: {0}", claim.Right);
                        Console.WriteLine("Resource: {0}", claim.Resource);
                        Console.WriteLine();
                        returnvalue = true;
                    }
                }
            }

        }
        return returnvalue;
    }

    public override bool TryGetIdentity(EndpointAddress reference, out EndpointIdentity identity)
    {
        return IdentityVerifier.CreateDefault().TryGetIdentity(reference, out identity);
    }
}
Friend Class CustomIdentityVerifier
    Inherits IdentityVerifier

    Public Overrides Function CheckAccess(ByVal identity As EndpointIdentity, _
                                          ByVal authContext As AuthorizationContext) As Boolean

        Dim returnvalue = False
        For Each claimset In authContext.ClaimSets
            For Each claim In claimset
                If claim.ClaimType = "http://schemas.microsoft.com/ws/2005/05/identity/claims/x500distinguishedname" Then
                    Dim name = CType(claim.Resource, X500DistinguishedName)
                    If name.Name.Contains((CType(identity, OrgEndpointIdentity)).OrganizationClaim) Then
                        Console.WriteLine("Claim Type: {0}", claim.ClaimType)
                        Console.WriteLine("Right: {0}", claim.Right)
                        Console.WriteLine("Resource: {0}", claim.Resource)
                        Console.WriteLine()
                        returnvalue = True
                    End If
                End If
            Next claim
        Next claimset
        Return returnvalue

    End Function

    Public Overrides Function TryGetIdentity(ByVal reference As EndpointAddress, _
                                             <System.Runtime.InteropServices.Out()> ByRef identity As EndpointIdentity) As Boolean
        Return IdentityVerifier.CreateDefault().TryGetIdentity(reference, identity)
    End Function

End Class

Example

The following example shows a complete implementation of the EndpointIdentity class.

public class OrgEndpointIdentity : EndpointIdentity
{
    private string orgClaim;
    public OrgEndpointIdentity(string orgName)
    {
        orgClaim = orgName;
    }

    public string OrganizationClaim
    {
        get { return orgClaim; }
        set { orgClaim = value; }
    }
}
Public Class OrgEndpointIdentity
    Inherits EndpointIdentity
    Private orgClaim As String

    Public Sub New(ByVal orgName As String)
        orgClaim = orgName
    End Sub

    Public Property OrganizationClaim() As String
        Get
            Return orgClaim
        End Get
        Set(ByVal value As String)
            orgClaim = value
        End Set
    End Property
End Class

See Also

ServiceAuthorizationManager
EndpointIdentity
IdentityVerifier
Service Identity Sample
Authorization Policy
Authorization Policy