1

I have setup ThinkTecture identity server as a STS, Have setup a web api project, and used the "identity and access" tool in visual studio and pointed it to my federation metadata to enable federated authentication using WIF. This is what the related portion of web.config looks like:

<system.identityModel>
    <identityConfiguration saveBootstrapContext="true">
      <audienceUris>
        <add value="http://localhost:41740/" />
      </audienceUris>

    <securityTokenHandlers>
        <add type="System.IdentityModel.Tokens.SamlSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
        <add type="System.IdentityModel.Tokens.Saml2SecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
    </securityTokenHandlers>            

      <issuerNameRegistry type="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry">
        <authority name="http://auth.myserver.com/samples">
          <keys>
            <add thumbprint="F89C10B505E015774D02E323DEDA32878F794028" />
          </keys>
          <validIssuers>
            <add name="http://auth.myserver.com/samples" />
          </validIssuers>
        </authority>
      </issuerNameRegistry>
      <!--certificationValidationMode set to "None" by the the Identity and Access Tool for Visual Studio. For development purposes.-->
      <certificateValidation certificateValidationMode="None" />
    </identityConfiguration>
  </system.identityModel>
  <system.identityModel.services>
    <federationConfiguration>
      <cookieHandler requireSsl="false" />
      <wsFederation passiveRedirectEnabled="true" issuer="https://10.40.40.68/issue/wsfed" realm="http://localhost:41740/" requireHttps="false" />
    </federationConfiguration>
  </system.identityModel.services>

This works great for authenticating users who use the API from the browser.

I now need to call the same API from code (C#) in a client application - lets call that APIClient - using HTTPClient.

In order to do so, I added this to the web.config:

<securityTokenHandlers>
        <!--<add type="System.IdentityModel.Tokens.JwtSecurityTokenHandler, System.IdentityModel.Tokens.Jwt" />-->
        <add type="System.IdentityModel.Tokens.SamlSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
        <add type="System.IdentityModel.Tokens.Saml2SecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
</securityTokenHandlers>

My assumption was that If I add the SAML token handler and add a SAML assertion to the HTTP Authorize header, WIF will pick that up and authenticate the request.

I can call the STS to get a SAML token as described in the GetSamlToken method here: enter link description here

That gives me a SAML assertion that I attach to the HTTPClient header:

client.SetToken("SAML", AuthenticationHeader);

Where AuthenticationHeader is the SAML assertion I received from the server. The problem is web api doesn't do anything with the samle assertion - as if it doesn't even see it, All I get in response a a redirect to the STS.

What am I doing wrong? How can I authenticate and call my protected web api method from other code without having to switch to JWT, etc.?

Thanks in advance for your help!

-- updated

I have added the following to my WebApiConfig.cs as @Brock suggested:

public static void Register(HttpConfiguration config)
{
    // Cross Origin Resource Sharing
    //CorsConfig.RegisterCors(GlobalConfiguration.Configuration);
    CorsConfig.RegisterCors(config);


    //CorsConfiguration corsConfig = new CorsConfiguration();
    //corsConfig.AllowAll();
    //var corsHandler = new CorsMessageHandler(corsConfig, config);
    //config.MessageHandlers.Add(corsHandler);


    // authentication configuration for identity controller
    var authentication = CreateAuthenticationConfiguration();
    config.MessageHandlers.Add(new AuthenticationHandler(authentication));



    // ASP.Net web api uses NewtonSoft Json.net natively, 
    // the following line forces the web api to use the xml serializer instead of data contract serializer
    config.Formatters.XmlFormatter.UseXmlSerializer = true;

    log.Debug("Registering Web API Routes");


    // register api routes

}




private static AuthenticationConfiguration CreateAuthenticationConfiguration()
{
    var authentication = new AuthenticationConfiguration
    {
        ClaimsAuthenticationManager = new ClaimsTransformer(),
        RequireSsl = false,
        EnableSessionToken = true
    };

    #region IdentityServer SAML
    authentication.AddSaml2(
        issuerThumbprint: "F89C10B505E015774D02E323DEDA32878F794028",
        issuerName: "https://10.40.40.68/issue/wsfed",
        audienceUri: "http://localhost:41740/",//Constants.Realm,
        certificateValidator: System.IdentityModel.Selectors.X509CertificateValidator.None,
        options: AuthenticationOptions.ForAuthorizationHeader("SAML"),
        scheme: AuthenticationScheme.SchemeOnly("SAML"));
    #endregion

    #region Client Certificates
    authentication.AddClientCertificate(ClientCertificateMode.ChainValidation);
    #endregion

    return authentication;
}

However I still get a 302 response. This is how I make the request:

ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true;


var factory = new WSTrustChannelFactory(
    new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
    "https://10.40.40.68/issue/wstrust/mixed/username");
factory.TrustVersion = TrustVersion.WSTrust13;

factory.Credentials.UserName.UserName = "myusername";
factory.Credentials.UserName.Password = "password";

var rst = new RequestSecurityToken
{
    RequestType = RequestTypes.Issue,
    KeyType = KeyTypes.Bearer,
    TokenType = Thinktecture.IdentityModel.Constants.TokenTypes.Saml2TokenProfile11,
    AppliesTo = new EndpointReference("http://localhost:41740/")
};

var token = factory.CreateChannel().Issue(rst) as System.IdentityModel.Tokens.GenericXmlSecurityToken;

string myToken = token.TokenXml.OuterXml;

HttpClient client = new HttpClient(new HttpClientHandler
{
    ClientCertificateOptions = ClientCertificateOption.Automatic,
    AllowAutoRedirect = false
});

client.SetToken("SAML", myToken);
//client.SetBearerToken(myToken);

var resp = client.GetAsync("http://localhost:41740/api/clients", HttpCompletionOption.ResponseContentRead).Result;
Assert.IsTrue(resp.IsSuccessStatusCode);
hnafar
  • 612
  • 8
  • 19

1 Answers1

1

Web API v1 doesn't have plumbing to automatically look for tokens in the request. This missing feature was provided by Thinktecture IdentityModel in its Web API authentication message handler. Check the sample folder for an example (specifically for the AuthenticationConfiguration class and the AddSaml2 API):

https://github.com/thinktecture/Thinktecture.IdentityModel.45/tree/master/Samples/Web%20API%20Security

Brock Allen
  • 7,385
  • 19
  • 24
  • Thank you very much for your help Brock. I added the Saml2 AuthenticationHandler as you explained, but did not get any results. Do you know if there is anything else I could try? I compared the assertion that I get from the STS to what the STS sends to the RP in the wsresult post field in a "normal" login scenario, other than the time stamps and digestValue, SignatureValue, etc. the rest is more or less the same. I also removed the token handlers that I had added to the web.config but still can't get anything. – hnafar Oct 15 '13 at 23:28
  • 1
    I finally resolved the issue. The problem was WSFederationAuthenticationModule and SessionAuthenticationModule were redirecting the request before ThinkTecture IdentityModel Authentication hnadlers could get to it. To resolve this, in "Identity and Access" wizard I selected "Generate a controller to handle authentication..." which sets the authentication mode to Forms and removes the authorization/deny user=? attribute form web.config and redirects all unauthenticated calls to another contoller, but also gives other handlers a chance to process the request and authenticate it if necessary – hnafar Oct 16 '13 at 12:57