Avatar of patd1
patd1
Flag for United States of America asked on

wcf service with usename password validation

I have created a wcf service and want to do username password validation in this service. So I created a custom UserNamePassValidator  as shown in the code.

class UserNamePassValidator : System.IdentityModel.Selectors.UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
            if (userName == null || password == null)
            {
                throw new ArgumentNullException();
            }

            if (!(userName == "MyUser" && password == "MyPass"))
            {
                throw new FaultException("Incorrect Username or Password");
            }
        }

Open in new window


The wcf configuration is as follows:
<system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="wsHttpBinding" allowCookies="true"
          maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647">
          <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647"
            maxArrayLength="2147483647" maxBytesPerRead="2147483647" />
          <security mode="Message">
            <message clientCredentialType="UserName"/>
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    <client />
    <services>
      <service behaviorConfiguration="MyWCF_Behavior" name="MyWCF_ReportService.MyWCF_ReportService">
        <endpoint address="wsHttpBinding" binding="wsHttpBinding" bindingConfiguration=""
          name="wsHttpBinding" bindingName="wsHttpBinding" contract="MyWCF_ReportService.IMyWCF_ReportService" />
        <endpoint address="mex" binding="mexHttpBinding" bindingConfiguration=""
          name="mex" bindingName="mex" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost/MyWCF_ReportService/" />
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="MyWCF_Behavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceCredentials>
            <serviceCertificate findValue="localhost" storeLocation="LocalMachine"
              storeName="My" x509FindType="FindBySubjectName" />
            <userNameAuthentication userNamePasswordValidationMode="Custom"
              customUserNamePasswordValidatorType="MyWCF_ReportService.UserNamePassValidator, MyWCF_ReportService" />
          </serviceCredentials>
          <serviceDebug />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

Open in new window


The service is hosted on IIS.

I also created a certificate and installed it.
I created a desktop client that consumes the wcf service to generate report. code as follows:
 private void button1_Click(object sender, EventArgs e)
        {
            Dictionary<string, string> d = new Dictionary<string, string>()
	        {
	            {"MemberID", "MX12345"}
	        };
            // Method 1: Create the client using the configuration file

            MyWCF_ReportServiceReference.MyWCF_ReportServiceClient c = new MyWCF_ReportServiceReference.MyWCF_ReportServiceClient("wsHttpBinding");
            c.ClientCredentials.UserName.UserName = "ussr";
            c.ClientCredentials.UserName.Password = "pswd";
            c.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
            try
            {
                Byte[] ReportArray = c.GenerateSSRSReport("/DataMgt/Sample1", d);
                SaveReport(ReportArray);
            }
            catch (FaultException<MyFaultException> ee) { textBox2.Text = ee.Detail.Reason; }
            catch (System.InvalidOperationException ie) { textBox2.Text = ie.Message; }
            catch (TimeoutException te) { textBox2.Text = te.Message; }
            catch (FaultException fEx) { textBox2.Text = fEx.Message; }
            catch (CommunicationException ce) { textBox2.Text = ce.Message; }
            finally
            {
                c.Close();
            }
        }

 public static void SaveReport(byte[] reportBytes)
        {
...... //code to save file on server
}

Open in new window


As you can see the UserNamePassValidator  class in the WCF service has the user name password different from what is passed in the client code. But, it still generates the report and saves it correctly.  I was expecting it to throw new FaultException("Incorrect Username or Password") but, that is not happening. Why?

Thanks for your help.
.NET ProgrammingWCF

Avatar of undefined
Last Comment
Aaron Jabamani

8/22/2022 - Mon
patd1

ASKER
Hi, can someone help me with this. Why is my WCF service as described above works with incorrect credentials?

Thanks.
Aaron Jabamani

Does the method, UserNamePassValidator  is invoked when you make a service request ? Did you debug ?
patd1

ASKER
When I set the break points in MyWCF_ReportService UsernamePasswordValidator, and start debugger, I get message  "the break point will not be hit, no symbols have been loaded for this document."

But I have configured to use this in the app.config on both client and service side.
 customUserNamePasswordValidatorType="MyWCF_ReportService.UserNamePassValidator, MyWCF_ReportService" 

Open in new window


Is there anything else I am supposed to do?

Thanks for your help.
Experts Exchange is like having an extremely knowledgeable team sitting and waiting for your call. Couldn't do my job half as well as I do without it!
James Murphy
Aaron Jabamani

From the wcf service project in visual studio, attach the asp.net web process.  From your client hit the URL, now you should be able debug...
patd1

ASKER
I am able to run the debugger, the message is only informational about the break points in UserNamePassValidator. I need to know why this code is not hit. The WCF service works from the test client using debugger and also using the desktop client that I created, but it does not throw faultException if I pass wrong username and password, while I have programmed it to throw faultexception if the username password does not match in the code.
patd1

ASKER
I enabled logging , and then invoked the service. The wcfLog.svcLog file was created. I double click the file to open and then searched for UserNamePasswordValidator entry in it. I was not able to find any entry with this.

Please help me find what's wring. How to implement a custom username password validatio in my WCF.
Get an unlimited membership to EE for less than $4 a week.
Unlimited question asking, solutions, articles and more.
Aaron Jabamani

patd1

ASKER

Below links should help you.

http://msdn.microsoft.com/en-us/library/aa702565.aspx

I did everything as mentioned in this first link, but it does not seem to work.
Aaron Jabamani

Since you are using certificate, as mentioned in the URL , you should consider below setting...



When using transport-level security over HTTP(S), set the clientCredentialType attribute of the <transport> of <wsHttpBinding> or <transport> of <basicHttpBinding> to Basic.
Your help has saved me hundreds of hours of internet surfing.
fblack61
patd1

ASKER
Do you mean in the code section below should change to
 <security mode="Transport">
            <message clientCredentialType="?"/> //Basic is not an option here.
          </security>    


<system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="wsHttpBinding" allowCookies="true"
          maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647">
          <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647"
            maxArrayLength="2147483647" maxBytesPerRead="2147483647" />
          <security mode="Message">
            <message clientCredentialType="UserName"/>
          </security>          
        </binding>
      </wsHttpBinding>
    </bindings>

Open in new window

Aaron Jabamani

Yes. Please check with the URL,http://msdn.microsoft.com/en-us/library/ms733836.aspx

Basic, is one of the values.
patd1

ASKER
ok. I think I now understand what you meant.
I have modified the security section in config file as follows, re-built the service, and updated service reference in the client, but, I can still get results with wrong username password.

<security mode="Message">
            <transport clientCredentialType="Basic" proxyCredentialType="Basic"
                realm="" />
            <message clientCredentialType="UserName" negotiateServiceCredential="true"
                algorithmSuite="Default" />
          </security>

Open in new window

Get an unlimited membership to EE for less than $4 a week.
Unlimited question asking, solutions, articles and more.
ASKER CERTIFIED SOLUTION
Aaron Jabamani

Log in or sign up to see answer
Become an EE member today7-DAY FREE TRIAL
Members can start a 7-Day Free trial then enjoy unlimited access to the platform
Sign up - Free for 7 days
or
Learn why we charge membership fees
We get it - no one likes a content blocker. Take one extra minute and find out why we block content.
Not exactly the question you had in mind?
Sign up for an EE membership and get your own personalized solution. With an EE membership, you can ask unlimited troubleshooting, research, or opinion questions.
ask a question
patd1

ASKER
I changed the name of binding configuration to 'wsHttpBindingConfig' and also used it in the endpoint configuration. Now when I test my service in the Visual Studio 2010 test client, I get the error copied below. But, when I run it from my desktop client that I created from where I am sending wring username password, It works and returns result. I updated the service reference in the Desktop client application.

Error on Visual studio test client
"Failed to invoke the service. Possible causes: The service is offline or inaccessible; the client-side configuration does not match the proxy; the existing proxy is invalid. Refer to the stack trace for more detail. You can try to recover by starting a new proxy, restoring to default configuration, or refreshing the service."

Error detail: Secure channel cannot be opened because security negotiation with the remote endpoint has failed. This may be due to absent or incorrectly specified EndpointIdentity in the EndpointAddress used to create the channel. Please verify the EndpointIdentity specified or implied by the EndpointAddress correctly identifies the remote endpoint.

Inner Exception:
The request for security token has invalid or malformed elements.
   at System.ServiceModel.Security.SecurityUtils.ThrowIfNegotiationFault(Message message, EndpointAddress target)
   at System.ServiceModel.Security.SspiNegotiationTokenProvider.GetNextOutgoingMessageBody(Message incomingMessage, SspiNegotiationTokenProviderState sspiState)

New config file code:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
      <section name="MyWCF_ReportService.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
    </sectionGroup>
  </configSections>
  <system.web>
    <compilation debug="true" />
  </system.web>
  <!-- When deploying the service library project, the content of the config file must be added to the host's 
  app.config file. System.Configuration does not support config files for libraries. -->
  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="wsHttpBindingConfig" maxBufferPoolSize="2147483647"
          maxReceivedMessageSize="2147483647" allowCookies="true">
          <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647"
            maxArrayLength="2147483647" maxBytesPerRead="2147483647" />
          <security mode="Message">
            <transport clientCredentialType="Basic" proxyCredentialType="Basic"
              realm="" />
            <message clientCredentialType="UserName" negotiateServiceCredential="true"
              algorithmSuite="Default" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    <client />
    <services>
      <service behaviorConfiguration="MyWCF_Behavior" name="MyWCF_ReportService.MyWCF_ReportService">
        <endpoint address="wsHttpBinding" binding="wsHttpBinding" bindingConfiguration="wsHttpBindingConfig"
          name="wsHttpBinding" bindingName="wsHttpBinding" contract="MyWCF_ReportService.IMyWCF_ReportService" />
        <endpoint address="mex" binding="mexHttpBinding" bindingConfiguration=""
          name="mex" bindingName="mex" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost/MyWCF_ReportService/" />
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="MyWCF_Behavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceCredentials>
            <serviceCertificate findValue="localhost" storeLocation="LocalMachine"
              storeName="My" x509FindType="FindBySubjectName" />
            <userNameAuthentication userNamePasswordValidationMode="Custom"
              customUserNamePasswordValidatorType="MyWCF_ReportService.CustomUserNamePassValidator, MyWCF_ReportService" />
          </serviceCredentials>
          <serviceDebug />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

  <applicationSettings>
    <MyWCF_ReportService.Properties.Settings>
      <setting name="MyWCF_ReportService_RE2005_ReportExecutionService"
        serializeAs="String">
        <value>http://myserver/ReportServer/ReportExecution2005.asmx</value>
      </setting>
      <setting name="MyWCF_ReportService_RS2005_ReportingService2005"
        serializeAs="String">
        <value>http://myserver/ReportServer/ReportService2005.asmx</value>
      </setting>
    </MyWCF_ReportService.Properties.Settings>
  </applicationSettings>
</configuration>

Open in new window

patd1

ASKER
Finally with clues from apeter, I could fix the configuration on both service and client side and now my service throws error if I pass wrong username password, and works fine when I pass correct username password. THANK YOU apeter.

I still have another minor problem related to the same issue.
My custom validator is programmed to  throw MyFaultException for missing or wrong credentials. But when I run the desktop client I get a error popup "System.ServiceModel.Security.MessageSecurityException. An unsecured or incorrectly secured fault was received from the other party. See the inner FaultException for the fault code and detail."

Why am I Not getting my custom error?
  [DataContract]
    public class MyFaultException
    {
        private string _reason;
        [DataMember]
        public string Reason
        {
            get { return _reason; }
            set { _reason = value; }
        }
    }

Open in new window


 class CustomUserNamePassValidator : System.IdentityModel.Selectors.UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)        
        {
            if (userName == null || password == null)
            {
                MyFaultException theFault = new MyFaultException();
                theFault.Reason = "Security Error Missing Credentials: ";
                throw new FaultException<MyFaultException>(theFault);
                
            }

            if (!(userName == "MyUser" && password == "MyPass"))
            {
                MyFaultException theFault = new MyFaultException();
                theFault.Reason = "Security Error Wrong Credentials: ";
                throw new FaultException<MyFaultException>(theFault);
             

            }
            
        }
    }

Open in new window


Client code:
private void button1_Click(object sender, EventArgs e)
        {
           Byte[] ReportArray = null;           
            MyWCF_DesktopClient.MyWCF_ReportServiceReference.MyWCF_ReportServiceClient c = new MyWCF_DesktopClient.MyWCF_ReportServiceReference.MyWCF_ReportServiceClient("wsHttpBinding");
            c.ClientCredentials.UserName.UserName = User;
            c.ClientCredentials.UserName.Password = Pass;            
            c.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
            c.Open();
            try
            {
                ReportArray = c.CreateReport("WX0143441");
            }
            catch (FaultException<MyFaultException> ee) { textBox2.Text = ee.Detail.Reason; }
            catch (FaultException fEx) { textBox2.Text = fEx.Message; }
            finally
            {
                c.Close();
            }

            if (ReportArray != null)
            {
                try
                {
                    SaveReport(ReportArray);
                }
                catch (Exception)
                {
                    throw new Exception("File save exception");
                }
            }

                   
        }

Open in new window

SOLUTION
Log in to continue reading
Log In
Sign up - Free for 7 days
Get an unlimited membership to EE for less than $4 a week.
Unlimited question asking, solutions, articles and more.
patd1

ASKER
I changed me code as suggested, new code copied below.
Now I get an error on Client.Close()
Error: System.ServiceModel.CommunicationObjectFaultedException: The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state."}

All I want is to be able to handle the exception on the client side. Thanks for all your help until now.

custom usernamePasswordValidator in the service
class CustomUserNamePassValidator : System.IdentityModel.Selectors.UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)        
        {
           if (userName == null || password == null)
            {
                throw new SecurityTokenException("security Error: No Credentials Passed");
      
            }

            if (!(userName == "MyUser" && password == "MyPass"))
            {
                throw new SecurityTokenException("security Error: Wrong Credentials Passed");
            }
            
        }
}

Open in new window


Client code:
 private void button2_Click(object sender, EventArgs e)
        {
            Byte[] ReportArray = null;
            credentials MyCredentials = new credentials();


            // Create the client using the configuration file
            MyWCF_DesktopClient.MyWCF_ReportServiceReference.MyWCF_ReportServiceClient c = new MyWCF_DesktopClient.MyWCF_ReportServiceReference.MyWCF_ReportServiceClient("wsHttpBinding");
            //MyCredentials = GetWebServiceCredentials();
            
            //test with bad credentials
            MyCredentials.UserName = "uuuu";
            MyCredentials.Passwrd = "pppp";
            ///////////////////////////////

            c.ClientCredentials.UserName.UserName = MyCredentials.UserName;
            c.ClientCredentials.UserName.Password = MyCredentials.Passwrd;    

            c.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
            try
            {
                c.Open();
                ReportArray = c.CreateReport("WX0143441");
            }
            catch (FaultException<MyFaultException> ee) { textBox2.Text = ee.Detail.Reason; }
            catch (FaultException fEx) { textBox2.Text = fEx.Message; }
            catch (Exception excp) { textBox2.Text = excp.Message; }
            finally
            {
                    c.Close(); //***********exception thrown here************
             }
            if (ReportArray != null)
            {
                try
                {
                    SaveReport(ReportArray);
                }
                catch (Exception)
                {
                    throw new Exception("File save exception");
                }
            }
        }

Open in new window

This is the best money I have ever spent. I cannot not tell you how many times these folks have saved my bacon. I learn so much from the contributors.
rwheeler23
Aaron Jabamani

Since the channel is not opened, you can't close it.  Add a if condition before close to check if 'c' is open state then close it
patd1

ASKER
How will the client know it didn't open due to wrong credentials?
SOLUTION
Log in to continue reading
Log In
Sign up - Free for 7 days
Get an unlimited membership to EE for less than $4 a week.
Unlimited question asking, solutions, articles and more.