Como autenticar uma solicitação de um aplicativo c # em um aplicativo WebApi ASP.NET habilitado para WIF usando uma declaração SAML
Eu configurei o servidor de identidade do ThinkTecture como um STS, configurei um projeto de API da web e usei a ferramenta "identidade e acesso" no visual studio e apontei para meus metadados de federação para habilitar a autenticação federada usando o WIF. Esta é a aparência da parte relacionada do web.config:
<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>
Isso funciona muito bem para autenticar usuários que usam a API do navegador.
Agora preciso chamar a mesma API do código (C #) em um aplicativo cliente - vamos chamar esse APIClient - usando HTTPClient.
Para fazer isso, adicionei isso ao 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>
Minha suposição era de que, se eu adicionasse o manipulador de token SAML e adicionasse uma asserção de SAML ao cabeçalho HTTP Authorize, o WIF o pegaria e autenticaria a solicitação.
Eu posso chamar o STS para obter um token SAML conforme descrito no método GetSamlToken aqui:insira a descrição do link aqui
Isso me dá uma afirmação de SAML que eu atribuo ao cabeçalho HTTPClient:
client.SetToken("SAML", AuthenticationHeader);
OndeAuthenticationHeader é a declaração de SAML que recebi do servidor. O problema é que a web api não faz nada com a afirmação samle - como se nem sequer a visse, Tudo que eu recebo em resposta é um redirecionamento para o STS.
O que estou fazendo de errado? Como posso autenticar e chamar meu método de API da Web protegida de outro código sem precisar alternar para o JWT, etc.?
Agradeço antecipadamente por sua ajuda!
-- Atualizada
Eu adicionei o seguinte ao meu WebApiConfig.cs como sugerido pelo @Brock:
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;
}
No entanto, ainda recebo uma resposta 302. É assim que faço o pedido:
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);