How to authenticate a request from a c# application to a WIF enabled ASP.NET WebApi application using a SAML assertion

StackOverflow https://stackoverflow.com/questions/19391794

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);
有帮助吗?

解决方案

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top