Search code examples
authenticationnunitwif

Getting authentication token from IP with nunit


I am working on adding WIF support to my WCF Data Services / ODATA server, and the first thing I'd like to do is create a nUnit test which passes some sort of identity to said server. I believe this falls under the category of an active client: there's no UI; I want to make a call out to a app.config established provider (Google, Yahoo, Windows Live, or some other provider) to get my identity token. Frankly, it doesn't matter what, just that it's more-or-less always accessable and has no administration to get the test running. (If there's some host app that I can include in my solution to act as an IP, I'd be perfectly happy with that.)

All of my existing tests use HttpRequest directly -- I am not using a generated client. While I'm creating my HttpRequest object, I check to see if I already have an authentication token to put in my headers. If not, I am trying something like this:

using (WSTrustChannelFactory factory = new WSTrustChannelFactory(
   new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
   new EndpointAddress(new Uri("https://dev.login.live.com/wstlogin.srf"))))
{
   factory.Credentials.UserName.UserName = "MYUSERNAME";
   factory.Credentials.UserName.Password = "MYPASSWORD";
   factory.TrustVersion = TrustVersion.WSTrust13;

   WSTrustChannel channel = null;
   try
   {
     var rst = new RequestSecurityToken
     {
       RequestType = WSTrust13Constants.RequestTypes.Issue,
       AppliesTo = new EndpointAddress("http://localhost:60711/Service"),
       KeyType = WSTrust13Constants.KeyTypes.Bearer,
     };

    channel = (WSTrustChannel)factory.CreateChannel();
    return channel.Issue(rst);
  }
  finally
  {
    if (null != channel)
    {
      channel.Abort();
    }
    factory.Abort();
  }
}

So to start... I don't even know if I'm aiming at the right URI for the IP, but when I changed it, I got a 404, so I figure maybe I'm on the right track there. At the moment, the channel.Issue method returns a MessageSecurityException with an inner exception of type FaultException, noting "Invalid Request". The FaultException has a Code with Name=Sender and Namespace=http://www.w3.org/2003/05/soap-envelope, which then has a SubCode with Name=InvalidRequest and Namespace=http://schemas.xmlsoap.org/ws/2005/02/trust. I don't know what to do with this information. :)

My apologies if I'm asking a very basic question. I've been looking at authentication for only a couple of days, and don't know my way around yet. Thanks for any help!

EDIT -- SOLUTION

Eugenio is right -- I am doing something a little heavyweight, and it is more of integration testing stuff. I ditched the Google/Yahoo/Live stuff, and found a modified version of SelfSTS, which I cobbled into my project. I don't fully understand what's going on just yet, but I got back a SAML token. Here is final code:

var binding = new WS2007HttpBinding();

binding.Security.Mode = SecurityMode.Message;
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
binding.Security.Message.EstablishSecurityContext = false;
binding.Security.Message.NegotiateServiceCredential = true;

using (var trustChannelFactory = new WSTrustChannelFactory(binding, new EndpointAddress(new Uri("http://localhost:8099/STS/Username"), new DnsEndpointIdentity("adventureWorks"), new AddressHeaderCollection())))
{
  trustChannelFactory.Credentials.UserName.UserName = MYUSERNAME;
  trustChannelFactory.Credentials.UserName.Password = MYPASSWORD;
  trustChannelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
  trustChannelFactory.TrustVersion = TrustVersion.WSTrust13;

  WSTrustChannel channel = null;

  try
  {
    var rst = new RequestSecurityToken(WSTrust13Constants.RequestTypes.Issue)
    {
      AppliesTo = new EndpointAddress("http://localhost:60711/Service"),
      KeyType = WSTrust13Constants.KeyTypes.Bearer
    };

    channel = (WSTrustChannel)trustChannelFactory.CreateChannel();
    GenericXmlSecurityToken token = channel.Issue(rst) as GenericXmlSecurityToken;
    ((IChannel)channel).Close();
    channel = null;

    trustChannelFactory.Close();

    string tokenString = token.TokenXml.OuterXml;
    return tokenString;
  }
  finally
  {
    if (null != channel)
    {
      ((IChannel)channel).Abort();
    }
    trustChannelFactory.Abort();
  }
}

(This code is also lifted from the same place I got the modified SelfSTS -- thanks, Wade Wegner!)

I'm not sure about the use of "adventureWorks". The version of SelfSTS that I'm using names that as the issuername in the configuration, but I haven't checked to see if there's any correlation.

So... now to hack up my actual server so that it can figure out what to do with the SAML!


Solution

  • Not all of those IPs support active calls. Even if they do, the protocols might not be compatible.

    For example, I'm not sure Google implements WS-Trust (what WIF is using under the hood). LiveID might have a WS-Trust endpoint somewhere, but not sure if it is officially supported/documented (for example, likely the error you are getting is because LiveID doesn't know about your RP: http:// localhost:60711/Service; and thus cannot issue a token for it).

    For multiple IPs like these, apps typically embed a web browser and use it for all token negotiations (using WS-Federation for example). And often they rely on a specialized STS to deal with protocol transitions (e.g. Windows Azure Access Control Service)

    In any case, what you are doing sounds a little bit heavyweight for a Unit test. It sounds more like an integration test you want to automate.

    Maybe you could start with a custom (fake) STS of your own. In which you can control everything and simulate different outputs (e.g. different claims, etc.)

    This chapter: http://msdn.microsoft.com/en-us/library/hh446528 (and the samples) can give you more information.