Passing a UserName as a supporting token.

Firstly i would like to thank Brent Schmaltz who helped me solve this problem. When trying to secure messages might require more than than the primary token to identity the client. We can then resort to sending additional information that would help in identitification or some kind of custom processing.

Basically the code below, from Brent, shows how to use a windows token to send a supporting user name token. Sometimes the user name on another system might not be your windows creds and so this would help in flowing the expected id to the service. The windows token would be used to secure the user name token in this scenario.

Basically what we do is secure the binding using wsHttpBinding and then add a UserNameSecurityTokenParameters object into the security binding element.Do note the he's specifed a custom validator to validate this information and specify the UserNamePasswordValidationMode to custom.

using System;

using System.ServiceModel;

using System.ServiceModel.Channels;

using System.ServiceModel.Security.Tokens;

using System.ServiceModel.Security;

using System.IdentityModel.Selectors;

using System.IdentityModel.Tokens;

using System.IdentityModel.Claims;

using System.IdentityModel.Policy;

using System.Security.Cryptography.X509Certificates;

using System.Security.Principal;

using System.Threading;

using System.Collections.Generic;

using System.Collections.ObjectModel;

namespace Ideas

{

class WindowsAuthDefaultClean

{

public static void Main()

{

string baseAddress = "https://localhost:8001/WindowsAuthDefault";

EndpointAddress epa = new EndpointAddress(baseAddress);

// Create the binding

WSHttpBinding wsHttpBinding = new WSHttpBinding(SecurityMode.Message);

wsHttpBinding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;

wsHttpBinding.Security.Message.EstablishSecurityContext = false;

// Create the service host and attache the custom u/p validator]

// and the service authorization manager.

ServiceHost sh = new ServiceHost(typeof(WorkService), new Uri(baseAddress));

sh.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;

sh.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNameValidator();

sh.Authorization.ServiceAuthorizationManager = new CustomServiceAuthorizationManager();

// Get the security element to add the parameter requirements.

SecurityBindingElement sbe = null;

BindingElementCollection bec = wsHttpBinding.CreateBindingElements();

sbe = bec.Find<SecurityBindingElement>();

if (sbe == null)

throw new InvalidOperationException("no SecurityBindingElement found");

sbe.EndpointSupportingTokenParameters.SignedEncrypted.Add(new UserNameSecurityTokenParameters());

CustomBinding cb = new CustomBinding(bec);

sh.AddServiceEndpoint(typeof(IWorkContract), cb, baseAddress);

sh.Open();

Console.WriteLine("Service Listening on : " + sh.BaseAddresses[0] + "\nPress <ENTER> to call service.");

Console.ReadLine();

//Create the client.

ChannelFactory<IWorkContract> cf = new ChannelFactory<IWorkContract>(cb, epa);

// validated in CustomUserNameValidator

cf.Credentials.UserName.UserName = "test1";

cf.Credentials.UserName.Password = "1tset";

IWorkContract wc = cf.CreateChannel();

try

{

Console.WriteLine(wc.DoWork("Get to work"));

}

catch (CommunicationException e)

{

Console.WriteLine(e);

}

catch (Exception e)

{

Console.WriteLine("Exception: {0}", e.ToString());

}

Console.WriteLine("\nPress <ENTER> to terminate");

Console.ReadLine();

}

}

public class CustomUserNameValidator : UserNamePasswordValidator

{

// This method validates users. It allows in two users, test1 and test2

// with passwords 1tset and 2tset respectively.

// This code is for illustration purposes only and

// MUST NOT be used in a production environment becuase it is NOT secure.

public override void Validate(string userName, string password)

{

if (null == userName || null == password)

{

throw new ArgumentNullException();

}

if (!(userName == "test1" && password == "1tset") && !(userName == "test2" && password == "2tset"))

{

throw new SecurityTokenException("Unknown Username or Incorrect Password");

}

}

}

public class CustomServiceAuthorizationManager : ServiceAuthorizationManager

{

protected override bool CheckAccessCore(OperationContext operationContext)

{

return true;

}

protected override ReadOnlyCollection<IAuthorizationPolicy> GetAuthorizationPolicies(OperationContext operationContext)

{

return base.GetAuthorizationPolicies(operationContext);

}

}

}

[ServiceContract]

public interface IWorkContract

{

[OperationContract]

string DoWork(string input);

}

public class WorkService:IWorkContract

{

public string DoWork(string input)

{ return "Input params " + input;}

}

/*

* Config solution for setting up custom U/P validator

*

<behaviors>

<serviceBehaviors>

<behavior name="CalculatorServiceBehavior" includeExceptionDetailInFaults="True">

<serviceCredentials>

<!--

The serviceCredentials behavior allows one to specify a custom validator for username/password combinations.

-->

<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Microsoft.ServiceModel.Samples.CalculatorService+CustomUserNameValidator, service" />

<!--

The serviceCredentials behavior allows one to define a service certificate.

A service certificate is used by a client to authenticate the service and provide message protection.

This configuration references the "localhost" certificate installed during the setup instructions.

-->

<serviceCertificate findValue="localhost" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" />

</serviceCredentials>

</behavior>

</serviceBehaviors>

</behaviors>

*/

Next - Web Hosted sample that implements the supporting token requirements.