Revalidate Credentials on WCF UserNamePasswordValidator on each call - c#

I am using a custom Username/Password Validator on WCF over NetTcp, to authenticate clients connecting to my WCF Service. What I noticed, is that once a client gets authenticated, never gets validated again, meaning that if I want to revoke access from a client, I would need to manually force him to disconnect.
My serviceHost configuration looks like this:
_serviceHost.Description.Behaviors.Add(credentialsBehavior);
_serviceHost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
_serviceHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = _userValidator;
_serviceHost.Credentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
_serviceHost.Credentials.ServiceCertificate.SetCertificate(AppSettingsManager.I.CertificateStoreLocation, AppSettingsManager.I.CertificateStoreName, AppSettingsManager.I.CertificateFindBy, AppSettingsManager.I.CertificateFindValue);
and my clients connect using ChannelFactory:
var client = new DuplexChannelFactory<T>(new InstanceContext(this), binding, endpointAddress);
client.Credentials.UserName.UserName = ConnectionProperties.Authentication.Credentials.Username;
client.Credentials.UserName.Password = ConnectionProperties.Authentication.Credentials.Password;
client.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = AppSettingsManager.I.CertificateValidationMode;
client.CreateChannel();
Is there a way to have the client credentials validated on every call, or periodically?

Generally speaking, after invocation, the server will automatically close the connection, it depends on the following parameter of the binding.
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/configuring-timeout-values-on-a-binding
Of course, we can also close it manually on the client.
client.Close()
In addition, I could not get your point. the session is continual, and you have set up the credential in the code snippets before calling the service. What do you mean that Never Gets Validate again?
In my opinion, if you want to revoke access from a client, you could change the validation logic on the server side.

Related

Generated WCF SOAP client uses current user for windows authentication instead of given credentials

I'm kind of new to the whole WCF and SOAP topic so please be kind.
I'm using a generated SOAP Client with .net6. In another project we successfully worked with the same Web Service using the old .net Framework 2.0 Web References and the same credentials.
Strange enough everything seemed to work fine at first. Until I realized, that it does not use the given credentials to authenticate. Instead it authenticates with my own domain user.
I also tried to get it to work with explicitly setting the binding with a BasicHttpBinding but I only could get the same broken logic to work or I got various authentication/protocol/security errors.
So it seems the authentication is basically working. It just doesn't use the provided credentials. So my question is: How can I configure it to work with the provided identity?
I also found out that it might have anything to do with a cached Windows token. But how can I get rid of it. How to prevent caching in the first place?
EDIT:
Specified the variable types explicitly.
string url = "http://someServer/AdministrationService.asmx";
AdministrationServiceSoapClient client = new AdministrationServiceSoapClient(
AdministrationServiceSoapClient.EndpointConfiguration.AdministrationServiceSoap,
url);
WindowsClientCredential credential = client.ClientCredentials.Windows;
credential.ClientCredential.UserName = "username";
credential.ClientCredential.Password = "password";
credential.ClientCredential.Domain = "DOMAIN";
GetServerInfoRequest getServerInfoRequest = new GetServerInfoRequest
{
// some stuff set here
};
GetServerInfoRequest getServerInfoReply = await client.GetServerInfoAsync(getServerInfoRequest);
As far as I know, BasicHttpBinding has security disabled by default, but can be added setting the BasicHttpSecurityMode to a value other than None in the constructor. It can be configured according to the instructions in BasicHttpBinding and BasicHttpBinding Constructors.
By default, setting up client credentials involves two steps: determining the type of client credential required by the service and specifying an actual client credential, as described in this document.
After waiting a day it is working. It seems that the cached credentials became invalid somehow.
Strange enough the simple service creation from above is not working anymore. Instead I have to use the following.
var client = new AdministrationServiceSoapClient(
new BasicHttpBinding()
{
Security = new BasicHttpSecurity()
{
Mode = BasicHttpSecurityMode.TransportCredentialOnly,
Message = new BasicHttpMessageSecurity()
{
ClientCredentialType = BasicHttpMessageCredentialType.UserName,
},
Transport = new HttpTransportSecurity()
{
ClientCredentialType = HttpClientCredentialType.Windows,
ProxyCredentialType = HttpProxyCredentialType.Windows,
}
},
},
new EndpointAddress(url));

AutoRest is requiring ServiceClientCredentials in the constructor, but I can't obtain that data without instantiating the client object

I generated an API client with AutoRest and am using the --add-credentials parameter so that I can pass in a bearer token. In order to get the token, I need to be able to instantiate the object and call my login method like this:
var client = new IOIWebAPI(new Uri("https://localhost:44325", UriKind.Absolute));
var loginResult = client.Login(authModel);
The problem is that every constructor requires ServiceClientCredentials. From what I understand, I need to create an instance of TokenCredentials, which includes the token string. But I can't do that because I can't get the token string without calling Login. And I can't call Login without having the token string.
I'm sure I'm just misunderstanding how to consume the API client. But any ideas on what I'm doing wrong here?
My best guess is that --add-credentials does not support unauthenticated endpoints. When you add creds, AutoRest assumes everything needs auth. I submitted an issue for this, but I suspect it won't be addressed any time soon.
My workaround was to create a TokenHelper class. I copy and pasted the code that AutoRest generated from my login endpoint into that class. So the code stays consistent, but it's not ideal because I may forget to update the endpoint if it ever changes.
var tokenHelper = new TokenHelper(baseUri);
var tokenResult = tokenHelper.GetTokenAsync(authModel).GetAwaiter().GetResult();
var token = new TokenCredentials(tokenResult.AccessToken, "Bearer");
var client = new IOIWebAPI(baseUri, token);

CRM Context Object persists connection even when creating new instance

I have a system in place for a WCF Service, in which I take in some credentials from the client. I then try to authenticate with CRM using these credentials. If the authentication fails, I use a pre-defined service account, with the credentials stored in web.config.
What I have found is, no matter what, the first set of credentials used persists for any further requests, no matter how much I tear down the first object. I even instantiate new objects, wrap each context in a using statement, etc.
I have watered the code down into a simple 'connect, retry' block, and this suffers the same issue. The code is as follows:
try
{
var connection = new CrmConnection();
connection.ServiceUri = new Uri("https://my.crm.dynamics.com/");
connection.ClientCredentials = new ClientCredentials();
connection.ClientCredentials.UserName.UserName = "removed1";
connection.ClientCredentials.UserName.Password = "removed1";
using (var crm = new CrmOrganizationServiceContext(connection))
{
var req = new Microsoft.Crm.Sdk.Messages.WhoAmIRequest();
var resp = (Microsoft.Crm.Sdk.Messages.WhoAmIResponse)crm.Execute(req);
}
}
catch (Exception ex) { }
try
{
var connection = new CrmConnection();
connection.ServiceUri = new Uri("https://my.crm.dynamics.com/");
connection.ClientCredentials = new ClientCredentials();
connection.ClientCredentials.UserName.UserName = "removed2";
connection.ClientCredentials.UserName.Password = "removed2";
using (var crm = new CrmOrganizationServiceContext(connection))
{
var req = new Microsoft.Crm.Sdk.Messages.WhoAmIRequest();
var resp = (Microsoft.Crm.Sdk.Messages.WhoAmIResponse)crm.Execute(req);
}
}
catch (Exception ex) { }
Assume that removed1 is incorrect and removed2 is correct. The second call will fail instantly with a token exception, saying invalid credentials. If removed1 is correct, and removed2 is not, the first will authenticate and get the WhoAmIRequest fine. Then, removed2 should fail, but it does not, as it seems to still hold the connection using the old credentials. The invalid credentials still allows the service to make requests. Not good!
The bizarre thing is, the code for the authentication is in a separate project. I have included this project in a simple console application, and everything works fine. I can only assume this is something to do with the WCF service and the way it holds connections. I've tried manually disposing, calling garbage collection, setting to null, etc. I've also tried using web config connection strings called by name (hard coded 2 test ones), tried manually creating the connection string settings with unique names, using CrmConnection.Parse(), etc.
I have even copy pasted the code i'm using directly into a console application, and it works fine. Due to this, I am convinced it is to do with the behavior of a WCF service, and not the code itself. I set the class to have the behavior of
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
But no luck. If it is of any importance, this code is running in a message inspector class which implements IDispatchMessageInspector.
How can I ensure that I can get a fresh session? Thanks.
You are using the default constructor of the CrmConnection class. When doing that, your connection is cached by name. This name is supposed to be the name of the ConnectionStringSettings, but using this constructor that property is never being set and keeps its default value, thus always returning the first connection object created.
Just use another overload of the constructors, e.g. that using a connection string or accepting the service url, credentials etc.
The CrmConnection class was designed to offer an easy way to create connectionstrings in config files, similar to database connection strings. It had its issues and has been removed from the Dynamics CRM 2016 SDK.

LdapConnection Bind() always fails over SSL

I need to query some information with Active Directory that is only accessible when authenticated over SSL. I can make anonymous connections without issue, but I always get an "LDAP server is unavailable error" when trying to use SSL. There's a lot of forum topics about this, and I've reviewed them but have not found a solution. This code is an ASP.NET MVC app being run on IIS Express.
LdapConnection conn = new LdapConnection("ldap.xxx.com:636/OU=xxx,DC=xxx,DC=xxx,DC=xxx");
//conn.AutoBind = false;
conn.SessionOptions.ProtocolVersion = 3;
conn.AuthType = AuthType.Basic; //Tried with Negotiate as well
conn.Credential = new NetworkCredential(#"domain\user", "userPW", "domain");
conn.SessionOptions.SecureSocketLayer = true;
conn.SessionOptions.VerifyServerCertificate = new VerifyServerCertificateCallback((dev, cer) => true);
//conn.Timeout = new TimeSpan(1, 0, 0);
conn.Bind();
I use that container string with a PrincipalContext to validate the credentials for this account, that validation is successful (that takes place before this code). The container places me into the correct node and the account credentials are a match. Which is why this error puzzles me. Protocol version is correct as well. I've set the verifycert callback to return true regardless of the certificate.
The error is being thrown on the first line, if I remove the container it creates the connection, but then hangs indefinitely when I call .Bind(). If I specify the domain after the connection is made, the "unavailable error" is thrown on .Bind(). I don't understand why it fails with the container because it works when passed in with a PrincipalContext.
Thank you for any help.

WCF UserNamePasswordValidator - Access credentials after validation

I am using the UserNamePasswordValidator class as part of the UserName security with WCF. This all works great and the Validate function of the class gets called and works properly.
How then can I find out what UserName was used in my service functions?
For example say if a client connects and requests a list of logs using something like
IList<Log> Logs() { ... }
How can that function know which UserName was used on that request?
What I want to do is log what UserName calls what function within the service.
Not sure, but you may be looking for
var userName = OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name;
I believe there is something in the operation context. Try this:
OperationContext oc = OperationContext.Current;
ServiceSecurityContext ssc = oc.ServiceSecurityContext;
string client = ssc.PrimaryIdentity.Name;

Categories