Scenario: User talks with WebApi called 'Gateway' by angularjs client. 'Gateway' is like a facade or a proxy, so all requests from User to 'Gateway' will be forwarded to another WebApis.
Security details: 'Gateway' WebApi and all others WebApis are placed in IIS with HTTPS binding and SSL client certificate option is 'Accept'. So, user will provide valid client certificate to IIS and after verification, request will be handled by WebApi.
The problem: when 'Gateway' WebApi receives a request, client certificate is presented in Request object. Then I just forward this request using HttpClient to another WebApi. But when another WebApi endpoint receives a request, there is not client certificate attached any more.
Below is a code snipped of request forwarding:
var request = Request; // income request from angularjs
var handler = new WebRequestHandler();
handler.ClientCertificates.Add(request.GetClientCertificate()); // setting up client certificate from user's request
using (var httpClient = new HttpClient(handler))
{
request.RequestUri = *chaning request address here*;
var response = await httpClient.SendAsync(request);
return ResponseMessage(response);
}
Note: If I try to go directly to the another WebApi, client certificate is presented as expected. If I try to go via 'Gateway', client certificate is presented as well in 'Gateway' request, but after it's successfully attached and request is sent, another WebApi does not receive any certificate attached.
Any ideas?
Thank you.
On your gateway server, you need to import an authentication key onto the "machine" certificate store (MMC.exe, add snap-in "Certificates", Computer account", Certficates-Personal-Certificates, import).
Grant permissions (for the auth cert) to the account which your IIS pool is using. (right-click the cert, All-Tasks, Manage private keys. Add, Advanced, Locations=[machine-name], find now, (probably) Network Service, Read).
In your web.config (on your gateway server), check your system.serviceModel / behaviors / endpointBehaviors / behavior / clientCredentials / clientCertificate. Make sure the attribute: storeLoction="LocalMachine", to use the cert from the machine key store.
Related
I am in the process of building a WCF client for a SOAP HTTPS webservice in .Net Core 2.1.
The service provider has supplied a .key and a .cert file which I have converted to a .p12 file using openssl. By adding this to a keystore I am able, through SoapUI, to successfully sent a request to the webservice (no other authentication than the certificate is required).
To do the same operation in .Net Core I have added a Connected Service to my project through the WCF wizard in Visual Studio. This service is based on the supplied service contract (WSDL file). I have then installed the .p12 certificate locally on my PC and I am using the following code to make the request. "MyService" is the connected service.
var binding = new BasicHttpsBinding();
binding.Security.Mode = BasicHttpsSecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
var endpoint = new EndpointAddress("https://x.x.x.x:8300/MyService.asmx");
var channelFactory = new ChannelFactory<MyService>(binding, endpoint);
channelFactory.Credentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
channelFactory.Credentials.ServiceCertificate.SslCertificateAuthentication = new X509ServiceCertificateAuthentication()
{
CertificateValidationMode = X509CertificateValidationMode.None,
RevocationMode = X509RevocationMode.NoCheck,
TrustedStoreLocation = StoreLocation.LocalMachine
};
channelFactory.Credentials.ClientCertificate.SetCertificate(
StoreLocation.CurrentUser,
StoreName.My,
X509FindType.FindByIssuerName,
"xxxxxxxxxxxxxxxxxxxxxxxxxx");
var service = channelFactory.CreateChannel();
ExecuteResponse response = service.Execute(new ExecuteRequest());
When running this code I am getting the following error:
System.ServiceModel.Security.MessageSecurityException: 'The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'Negotiate'.'
The strange thing is that I am allowed to make the request if I use the HttpClientHandler which tells me that there must be a mismatch between the underlying structure of the two implementations.
Anyone who knows how I can fix this error?
The certificate might just be used to establish the trust relationship between the client-side and the server-side.
For making a successful call to the service, we should keep the binding type between the client-side and the server-side consistent. Therefore, I would like to know the automatically generated client-side configuration by Adding service reference, please post the System.servicemodel section located in the appconfig of the client project.
If the server authenticates the client-side with a certificate, the error typically indicates the trust relationship has not established yet between the client-side and the server-side.
On the client-side, we should install the server certificate in the LocalCA. On the server-side, we should install the client certificate in the LocalCA certificate store.
Feel free to let me know if the problem still exists.
Edit:
Here is my question reformulated:
I have a web server with secured api endpoints - one must have been authenticated with Google prior to using them. I implemented Challenge and Callback endpoints for that.
This works well from a browser with my SPA web front-end. The user gets redirected to the Google website to sign-in and then gets redirected back to my webapp; the browser then has the authenticated cookies and the webapp can use the endpoints to update its state.
I also have a WPF application that will communicate with the web server.
I want the WPF application to do the same as the web front-end: Use the web api endpoints after being authenticated with Google. The connection between the WPF application and my web server is done through an HttpClient.
My problem is I don't know how to authenticate that HttpClient connection between the WPF app and the web server.
I tried using the same Challenge endpoint but the response I get is of course the HTML from the Google Sign-In page, so I guess I can't use that with an HttpClient...
I also tried authenticating with GoogleApis from the WPF app and use the authenticated token to set cookies in the HttpClient but apparently this is not compatible.
How to authenticate an HttpClient connection to a web api with an external provider such as Google?
Original question:
From a WPF application, the user authenticates with Google with this code:
using Google.Apis.Auth.OAuth2;
...
public void Authenticate()
{
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = "myClientId",
ClientSecret = "myClientSecret"
},
new[] { "email", "openid" },
"user",
CancellationToken.None).Result;
}
This works and the UserCredential object contains the authenticated token:
How to embed this token information in a web request made with an HttpClient in order to call my webapi endpoint?
I think the request must include some cookies to inform the server that it has been authenticated, but I don't know which ones exactly.
The endpoint on the server-side validates that the user is authenticated with the help of IdentityServer:
var result = await HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (result?.Succeeded != true)
{
throw new Exception("External authentication error");
}
If I got your question right, you just have to set the Authorization header
var credentials = await GoogleWebAuthorizationBroker.AuthorizeAsync(
clientSecrets,
new[] { "email", "openid" },
"user",
CancellationToken.None);
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
credentials.Token.TokenType,
credentials.Token.IdToken);
Maybe you'll find below a helpful hint to better understand OpenID :)
The confusion stems from mixing GoogleApis and IdentityServer frameworks.
Authentication/authorization can be achieved using either of them.
Objects from Google.Apis.Auth.OAuth2 and IdentityServer4 namespaces are not designed to interact.
No manual cookie handling is necessary, for sure.
Ask yourself to whom does Google provide trust for the user. If it calls back to WPF, then webapi trusting WPF is a separate issue.
You answer your own question in the question:
the browser then has the authenticated cookies and the webapp can use
the endpoints to update its state
HttpClient needs to send those same cookies.
How do I set a cookie on HttpClient's HttpRequestMessage
If I understood your question right, then I faced the same problem not too long ago.
The way I implemented it is that in the backend, no matter who tries to access the endpoint, they had to send a Bearer X authorization token.
The token contained the identity of the client that wanted to access the resource, and I checked if he was permitted.
No matter what kind of client wants to access the endpoint, it just has to have that authroziation header in the request that he sends and the backend will treat it the same.
In my scenario, I used an authentication service that returns a cookie to the client with a certain JWT that contains the identity information.
Then from the client I send in every request the JWT received from the authentication service as an authorization header to the backend.
The reason I had to put the JWT that I receive from the service in a header, is that the authentication service and the backend service are not in the same domain, so cookies cant be shared.
This results in such design that no matter how you authenticate the client, the end result must be some sort of token that the backend can receive and read.
Hope this helps.
I have little experience with cookies so i need some help here.
I have a scenario where i have to establish WCF connection to the remote endpoint using the https security. The situation is complicated by the thing that endpoint address in client config is actually pointing to a web proxy which redirects request to the actual endpoint if SSON auth has been detected or redirects to auth page.
Example: service address for client config is 'https://a.com/my.svc'. If auth is OK i will connect to this address, if not, i will be redirected to 'https://a.com/auth' and after successful auth will proceed again to 'https://a.com/my.svc'
If i open the address in the Web Browser i'm redirected to auth page and after successful auth (login/pwd) then redirected to the address. After the authenication the SSON cookie is generated and all consequent address queries lead me straight to the address with no additional auth.
As of now i've done the following:
Do auth using WPF WebBrowser control. I've got the cookie after that.
Made client endpoint connection. Yeah the session is used by default in prod environment, i'm not sure if i can remove it.
<customBinding>
<binding name="WSHttpsBinding_IDB2Connector">
<reliableSession inactivityTimeout="23:59:59" ordered="True"/>
<httpsTransport requireClientCertificate="false" />
</binding>
Direct connection works fine. I mean i don't connect to proxy address but use final endpoint address instead and don't use cookies.
Now the problem is how i can made my WCF connection to use SSON coookie to bypass auth page on ClientBase.Open call?
Once you have SSO cookie from auth form you can use OperationContextScope to send HTTP headers.
YourServiceClient client = new YourServiceClient();
using(new OperationContextScope(client.InnerChannel))
{
// Add a HTTP Header to an outgoing request
HttpRequestMessageProperty requestMessage = new HttpRequestMessageProperty();
requestMessage.Headers["Cookie"] = "MySSOCookie=MySSOCookieValue"; //SSOCookie content
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = requestMessage;
client.doSomething();
}
I'm trying to get mutual SSL authentication working with ServiceStack, which under the hood uses HttpListener.
I use this command on the server to bind the server certificate to the required port, and enable client certificates:
netsh http add sslcert ipport=0.0.0.0:1234 certhash=5d51087438cbea33f2a4d86214b11a866876b9c5 appid={00000000-1111-2222-3333-444444444444} clientcertnegotiation=enable
If I run this command I can confirm that it says Negotiate Client Certificate : Enabled:
netsh http show sslcert
Then I add a reservation for the namespace using:
netsh http add urlacl url=https://+:1234/ user="NT AUTHORITY\NETWORK SERVICE"
'Normal' server authentication seems to work fine; the client is receiving the server's certificate and I can use ServicePointManager.ServerCertificateValidationCallback to override what is trusted.
But if the client sends no client certificate, it works as normal. If the client sends a client certificate (whether trusted or untrusted by the server), it still works as normal. This is of course not right!
I'm adding the client certificate using HttpWebRequest.ClientCertificates.Add(X509Certificate).
I've taken a look at the traffic on the wire, and AFAICS the server is sending a list of trusted CAs and requesting a client certificate.
Do I need to override something at the server to perform verification of the certificate sent by the client, similar to how clients can use ServicePointManager.ServerCertificateValidationCallback to verify the server certificate?
ServiceStack allows you to configure 'global filters', which can intercept all requests.
I solved my problem using a global filter:
public static void Validate(IRequest request, IResponse response, object message)
{
var httpRequest = ((ListenerRequest)request).HttpRequest;
var consoleCert = httpRequest.GetClientCertificate();
...
I can then send 403 if the client certificate is not present or is invalid:
response.StatusCode = 403;
response.EndRequest();
return;
Not sure if this is the recommended approach (or indeed if there is a recommended approach, but it works for me.
I am struggling to understand and set up a Service and Consumer where the Service will run as the user logged into the Consumer.
My consumer is an MVC application. My Service is a Web Api application. Both run on separate servers within the same domain. Both are set to use Windows Auth.
My consumer code is:
private T GenericGet<T>(string p)
{
T result = default(T);
HttpClientHandler handler = new HttpClientHandler() { PreAuthenticate = true, UseDefaultCredentials = true };
using (HttpClient client = new HttpClient(handler))
{
client.BaseAddress = new Uri(serviceEndPoint);
HttpResponseMessage response = client.GetAsync(p).Result;
if (response.IsSuccessStatusCode)
result = response.Content.ReadAsAsync<T>().Result;
}
return result;
}
In my Service I call User.Identity.Name to get the caller ID but this always comes back as the consumer App Pool ID, not the logged in user. The consumer App Pool is running as a Network Service, the server itself is trusted for delegation. So how do I get the logged in User? Service code:
// GET: /Modules/5/Permissions/
[Authorize]
public ModulePermissionsDTO Get(int ModuleID)
{
Module module= moduleRepository.Find(ModuleID);
if (module== null)
throw new HttpResponseException(HttpStatusCode.NotFound);
// This just shows as the App Pool the MVC consumer is running as (Network Service).
IPrincipal loggedInUser = User;
// Do I need to do something with this instead?
string authHeader = HttpContext.Current.Request.Headers["Authorization"];
ModulePermissionsDTO dto = new ModulePermissionsDTO();
// Construct object here based on User...
return dto;
}
According to this question, Kerberos is required to make this set up work because the HttpClient runs in a separate thread. However this confuses me because I thought the request sends an Authorization header and so the service should be able to use this and retrieve the user token. Anyway, I have done some testing with Kerberos to check that this correctly works on my domain using the demo in "Situation 5" here and this works but my two applications still wont correctly pass the logged in user across.
So what do I need to do to make this work? Is Kerberos needed or do I need to do something in my Service to unpack the Authorisation header and create a principal object from the token? All advice appreciated.
The key is to let your MVC application (consumer) impersonate the calling user and then issue the HTTP requests synchronously (i.e. without spawning a new thread). You should not have to concern yourself with low-level implementation details, such as NTLM vs Kerberos.
Consumer
Configure your MVC application like so:
Start IIS Manager
Select your MVC web application
Double click on 'Authentication'
Enable 'ASP.NET Impersonation'
Enable 'Windows Authentication'
Disable other forms of authentication (unless perhaps Digest if you need it)
Open the Web.config file in the root of your MVC application and ensure that <authentication mode="Windows" />
To issue the HTTP request, I recommend you use the excellent RestSharp library. Example:
var client = new RestClient("<your base url here>");
client.Authenticator = new NtlmAuthenticator();
var request = new RestRequest("Modules/5/Permissions", Method.GET);
var response = client.Execute<ModulePermissionsDTO>(request);
Service
Configure your Web API service like so:
Start IIS Manager
Select your Web API service
Double click on 'Authentication'
Disable 'ASP.NET Impersonation'.
Enable 'Windows Authentication'
If only a subset of your Web API methods requires users to be authenticated, leave 'Anonymous Authentication' enabled.
Open the Web.config file in the root of your Web API service and ensure that <authentication mode="Windows" />
I can see that you've already decorated your method with a [Authorize] attribute which should trigger an authentication challenge (HTTP 401) when the method is accessed. Now you should be able to access the identity of your end user through the User.Identity property of your ApiController class.
The key issue with double hop is delegation of user credential to second call. I want to elaborate a little bit about it. C1 = client browser , S1 = First Server , S2 = Second Server.
Suppose our complete system support window authentication. When user access S1 from browser , its default window credential pass to server S1, but when S1 make a call to S2 , by default it don't pass credential to S2.
Resolution :
We must enable window authentication/ impersonation on both machines.
WE need to enable delegation between server so that S1 can trust to S2 and will pass credential to S2.
You can find some useful details at below links :
http://blogs.msdn.com/b/farukcelik/archive/2008/01/02/how-to-set-up-a-kerberos-authentication-scenario-with-sql-server-linked-servers.aspx
https://sqlbadboy.wordpress.com/2013/10/11/the-kerberos-double-hop-problem/
If you are trying to access service which is hosted on windows authentication then do following.
var request = new RestRequest(Method.POST);
If you want to use applications default credentials which must have access on hosted service server
request.UseDefaultCredentials = true;
or user below to pass the credentials manually
request.Credentials = new NetworkCredential("Username", "Password", "Domain");