I have configured a new Azure hosted WCF service with a custom username validator. The validator class will validate username and passwords from an existing aspnetUsers table in an Azure database.
I have configured the service using TransportWithMessageCredentials binding so the clients will be providing their usernames and passwords in clear text in the request.
My code will then look up the user and get their hashed password from the database and then use this to hash the password sent via the service. If they match then allow the request.
to validate the password I am using this code.
public static bool checkPassword(string hashedPassword, string password)
{
byte[] buffer4;
if (hashedPassword == null)
{
return false;
}
if (password == null)
{
throw new ArgumentNullException("password");
}
byte[] src = Convert.FromBase64String(hashedPassword);
if ((src.Length != 0x31) || (src[0] != 0))
{
return false;
}
byte[] dst = new byte[0x10];
Buffer.BlockCopy(src, 1, dst, 0, 0x10);
byte[] buffer3 = new byte[0x20];
Buffer.BlockCopy(src, 0x11, buffer3, 0, 0x20);
using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, dst, 0x3e8))
{
buffer4 = bytes.GetBytes(0x20);
}
return ByteArraysEqual(buffer3, buffer4);
}
So my question really is, Is sending the username and passwords in this way secure enough? As everything is going over https I am assuming it is but would like some guidance as I am fairly new to security in general.
The service will also be IP restricted.
Here is my service model config.
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="MyServiceBehaviour">
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="MyValidatorClass,MyNameSpace" />
</serviceCredentials>
<!-- To avoid disclosing metadata information, set the values below to false before deployment -->
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<binding name="HttpBinding" maxReceivedMessageSize="2097152" receiveTimeout="00:02:00" sendTimeout="00:02:00">
</binding>
<binding name="HttpsBinding" maxReceivedMessageSize="2097152" receiveTimeout="00:02:00" sendTimeout="00:02:00">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service name="MyService" behaviorConfiguration="MyServiceBehaviour">
<endpoint address="" binding="basicHttpBinding" bindingConfiguration="HttpsBinding" contract="MyContract" />
<host>
<baseAddresses>
<add baseAddress="https://MyServiceInAzure.net/" />
</baseAddresses>
</host>
</service>
</services>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
And here is what the clients will be using:
Client.Service call = new Client.ServiceClient();
call.ClientCredentials.UserName.UserName = "MyUsername";
call.ClientCredentials.UserName.Password = "MyPassword";
var result = call.PostCall("Hello World");
Thanks
So my question really is, Is sending the username and passwords in this way secure enough?
Yes. Since everything which send by HTTPS will be encrypted, you could send the user name and password without encrypted manually. Per my opinion, it is not recommend to send user name and password every time when sending request to your service. I suggest you generate a token at the first time and using the token to validate your request for a period of time.
Related
I have created a Service with below Service contract
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetData(int value);
}
With below Custom Authentication (not in App_Code)
public class CustomAuthenticator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
try
{
if (userName.Equals("user") && password.Equals("pass"))
{
//Good to go
}
}
catch (Exception ex)
{
throw new FaultException("Authentication failed");
}
}
}
My Config on Service. I used basicHttpBinding with Custom User Authentication (I don't want to use wsHttpBinding which makes it mandatory to have certificate)
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="myBasicBinding">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
<transport clientCredentialType="None" proxyCredentialType="None"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service name="TestService.Service1" behaviorConfiguration="customBehavior">
<endpoint address="" binding="basicHttpBinding" bindingConfiguration="myBasicBinding" contract="TestService.IService1" />
<endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="customBehavior">
<!-- To avoid disclosing metadata information, set the values below to false before deployment -->
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false"/>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="TestService.CustomAuthenticator,TestService"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
Service is hosted on IIS 8.5 with Anonymous and Forms Authentications enabled. I took a Console application client, added service reference and below config
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IService1">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
<transport clientCredentialType="None" proxyCredentialType="None"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://techspider/Service/Service1.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1"
contract="ServiceReference1.IService1" name="BasicHttpBinding_IService1" />
</client>
</system.serviceModel>
But, when I call a method on the service, it seems to be accepting any user name / password, which makes me think that it is not going to the custom authentication class I implemented.
If I don't supply any UserName, it is throwing me an error which indicates my setup is correct.
Service1Client client = new Service1Client();
client.GetData(1);
The username is not provided. Specify username in ClientCredentials.
The below code even after supplying wrong credentials, was able to fetch me the output.
Service1Client client = new Service1Client();
client.ClientCredentials.UserName.UserName = "aaaa";
client.ClientCredentials.UserName.Password = "dddd";
client.GetData(1);
Can anyone suggest if I'm missing any Config on Server? How do I ensure my CustomAuthenticator gets executed with every method call. I searched a number of questions online but none of them resolved.
The feeling that each and every username password combination will be accepted is right. That's the case because you're not really validating the credentials.
if (userName.Equals("user") && password.Equals("pass"))
{
//Good to go
}
If these demo-credentials are submitted, you're good to go ;-) But what if this doesn't apply? If any other username password combination is submitted, then nothing happens! And that's exactly the reason why your service is accepting arbitrary credentials. In the catch-block you're throwing an informative FaultException, but it won't be triggered as long as there doesn't occur an exception (which isn't the case in this simple example).
If you take a look at the docs, there's a very important remark and a corresponding code sample:
Override the Validate method to specify how the username and password is validated. If the username and password do not pass validation, then throw a SecurityTokenValidationException.
So instead of doing nothing if the submitted credentials don't match you should throw a SecurityTokenValidationException to reject the login-attempt.
public class CustomAuthenticator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
try
{
if (userName.Equals("user") && password.Equals("pass"))
{
//Good to go
}
else
{
// add this line
throw new SecurityTokenException("Unknown Username or Password");
}
}
catch (Exception ex)
{
throw new FaultException("Authentication failed");
}
}
}
I've been trying to figure this out for a couple of days. I know that I'm close but can't quite figure out what I'm missing.
The service is in a website hosted on IIS
Basic authentication is enabled for the site, and all others including anonymous authentication is disabled.
I have created a self signed certificate and set up a https binding
I have overridden the Validate method of the UsernamePasswordValidator but when I attach a break point it isn't reached. So my problem is likely something to do with this.
When I attempt to access my service method at https://localhost/Services/JobSiteService.svc/rest/get I am continually prompted for username and password as i receive a 401 unauthorized error.
Can anyone see what I'm doing wrong?
Appropriate code below:
Web.Config
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding name="webHttpTransportSecurity">
<security mode="Transport">
<transport clientCredentialType="Basic" />
</security>
</binding>
</webHttpBinding>
</bindings>
<services>
<service name="Validity.WebService.JobSiteService" behaviorConfiguration="SecureRestBehavior">
<endpoint address="rest" binding="webHttpBinding" behaviorConfiguration="RESTBehavior" bindingConfiguration="webHttpTransportSecurity" contract="Validity.WebService.Contracts.IJobSiteService" />
<endpoint address="meta" binding="mexHttpsBinding" contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="SecureRestBehavior">
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Validity.WebService.IdentityValidator, Validity.WebService" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="RESTBehavior" >
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<protocolMapping>
<add binding="webHttpBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
Service Contract:
[ServiceContract]
public interface IJobSiteService
{
[OperationContract]
[WebInvoke(Method="GET", ResponseFormat=WebMessageFormat.Json)]
List<JobSite> Get();
}
Custom Validator (it's in the namespace "Validity.WebService" but the code formatting broke when I included it.):
public override void Validate(string userName, string password)
{
using (var context = new ValidityContext())
{
using (var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(context)))
{
var user = userManager.Find(userName, password);
if (user == null)
{
var msg = String.Format("Unknown Username {0} or incorrect password {1}", userName, password);
throw new FaultException(msg);//the client actually will receive MessageSecurityException. But if I throw MessageSecurityException, the runtime will give FaultException to client without clear message.
}
else
{
SessionOperationContext.Current.Items["CurrentUser"] = user;
}
}
}
}
Any help with this would be greatly appreciated.
Finally figured this out.
The solution was to disable basic authentication on the IIS server and override the CheckAccessCore method of the ServiceAuthorizationManager to validate the user.
I have a WCFservice that is a HTTPS REST API which exposes a few WebGet methods. I am using transport level security over HTTPS. This is all working fine. I implemented a custom UserNamePasswordValidator, overriding the Validate method as per MSDN documentation.
public override void Validate(string userName, string password)
{
WebFaultException rejectEx = new WebFaultException(HttpStatusCode.Unauthorized);
rejectEx.Data.Add("HttpStatusCode", rejectEx.StatusCode);
if (null == userName || null == password)
throw rejectEx;
if (userName == "someusername" && password == "somepassword") return;
throw rejectEx;
}
Accessing the service in a browser via HTTPS only (http is disabled), username and password are prompted for, leaving one out or entering one or both incorrectly results in the prompt coming back as I am throwing a 401.
The issue I have is that Validate is never called if both the username and password are left out when accessing the service. I need to be able to handle this and return an informative error, or even just a 401, to the browser/client.
Debugging shows that Validate is never fired when leaving out both the username and password. This means that in a browser you end up with a blank page and no errors.
How can I handle this situation and return a response to the client prompting for credentials again?
Edit to include config info:
<behaviors>
<serviceBehaviors>
<behavior name="RESTEndpointBehaviour">
<serviceMetadata httpsGetEnabled="true" httpGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="false" />
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="MyAPI.CustomAuthenticator, MyAPI"
/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="RESTEndpointBehaviour" name="MyAPI.RestService">
<endpoint binding="webHttpBinding" bindingConfiguration="BasicHttpSSLBinding"
contract="MyAPI.IRestService" />
<endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="https://localhost:443" />
</baseAddresses>
</host>
</service>
</services>
<bindings>
<webHttpBinding>
<binding name="BasicHttpSSLBinding">
<security mode="Transport">
<transport clientCredentialType="Basic"/>
</security>
</binding>
</webHttpBinding>
</bindings>
I ended up solving this puzzle by implementing a serviceAuthorization instead. As per the below blog article, with a few tweaks to match my application.
http://www.allenconway.net/2012/07/using-basic-authentication-in-rest.html
There is an web application, where in it has few entities including image entity.That image is already stored in DB, now i have to access it through webservice and display in XML. Tried converting that image entity to byte[] and as well as String base64. But did not work out. Please help me to resolve this issue. Thanks in advance.
Are you facing issue transmitting the image back?
I converted the image to base64 and returned as a string. It is working fine. What issue did you face there.
But this approach requires update on the maxReceivedMessageSize parameters in WCF service as well as the client.
Side effects : There are some issues with increasing
maxReceivedMessageSize. More memory foot print, might open up DDOS
attacks.
Service Contract
public class ImageService : IImageService
{
public string GetImageData(string path)
{
if (File.Exists(path))
{
return ConvertFile(path);
}
else
return string.Empty;
}
private string ConvertFile(string path)
{
using (Image image = Image.FromFile(path))
{
using (MemoryStream m = new MemoryStream())
{
image.Save(m, image.RawFormat);
byte[] imageBytes = m.ToArray();
// Convert byte[] to Base64 String
string base64String = Convert.ToBase64String(imageBytes);
return base64String;
}
}
}
}
Service Configuration
<system.serviceModel>
<bindings>
<basicHttpsBinding>
<binding name="basicHttp" allowCookies="true"
maxReceivedMessageSize="20000000"
maxBufferSize="20000000"
maxBufferPoolSize="20000000">
<readerQuotas maxDepth="32"
maxArrayLength="200000000"
maxStringContentLength="200000000"/>
</binding>
</basicHttpsBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the values below to false before deployment -->
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
Client Code
static void Main(string[] args)
{
ImageService.ImageServiceClient client = new ImageService.ImageServiceClient();
string data = client.GetData(#"C:\Users\v-sridve\Pictures\Travel_Error_New.PNG");
}
Client Configuration
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IService1" maxReceivedMessageSize="1000000" maxBufferSize="1000000" />
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:7541/Service1.svc" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IService1" contract="ImageService.IService1"
name="BasicHttpBinding_IService1" />
</client>
</system.serviceModel>
I have a client (could be C# or PHP) that needs to be able to request an OAuth 2.0 access token (JWT for compatibility) from my STS (Thinktecture Identity Server), and then send that token to a webhttp endpoint in a WCF Service. The service will validate the token in a message inspector, and either throw an error or continue to a service method. I am using Thinktecture Identity Model, and RestSharp to send rest requests.
So far here is what I have:
Client:
var client = new OAuth2Client(
new Uri("https://mysts/issue/oauth2/token"),
"client",
"secret");
var response = client.RequestAccessTokenUserName("username", "password", "http://localhost:51696/");
var token = response.AccessToken;
var restClient = new RestClient("https://127.0.0.1:444/");
var restRequest = new RestRequest(Method.POST);
restRequest.AddHeader("Authorization", token);
restRequest.AddObject(new Request());
And I get the access token from the sts just fine, I'm not sure if I'm inserting the token in the authorization header correctly or not.
Message inspector:
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
#region new code
string binding = OperationContext.Current.EndpointDispatcher.ChannelDispatcher.BindingName;
if (binding == "http://tempuri.org/:SecureRestBinding")
{
// Check to see if there is an Authorization in the header, otherwise throw a 401
if (WebOperationContext.Current.IncomingRequest.Headers["Authorization"] == null)
{
WebOperationContext.Current.OutgoingResponse.Format = WebMessageFormat.Json;
WebOperationContext.Current.OutgoingResponse.Headers.Add("WWW-Authenticate: Basic realm=\"myrealm\"");
throw new WebFaultException<string>("No username or password was provided", HttpStatusCode.Unauthorized);
}
else
{
//Code to validate oauth token?
}
}
This makes sure to only check REST messages, SOAP messages are handled via a security token handler. I need to fill in code to validate the oauth token, but I cant seem to find a good example anywhere.
web.config in case its relevant:
<system.serviceModel>
<services>
<service behaviorConfiguration="MyServiceBehavior" name="MyWCFWebRole.MyWcfService">
<endpoint address="/REST/"
behaviorConfiguration="MyRestBehavior"
binding="webHttpBinding" bindingConfiguration="RESTSSLBinding"
name="RestSSLEndpoint" bindingName="SecureRestBinding"
contract=MyWCFWebRole.MyWcfServic" />
<endpoint address="/SOAP/"
binding="basicHttpBinding"
bindingConfiguration="secureHttpBinding"
name="SecureHttpEndpoint"
bindingName="SecureHttpBinding"
contract="MyWCFWebRole.MyWcfServic" />
</service>
</services>
<bindings>
<webHttpBinding>
<binding name="RESTSSLBinding">
<security mode="Transport">
<transport clientCredentialType="None" />
</security>
</binding>
</webHttpBinding>
<basicHttpBinding>
<binding name="secureHttpBinding">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
</security>
</binding>
</basicHttpBinding>
<ws2007FederationHttpBinding>
<binding name="">
<security mode="Message">
<message>
<issuerMetadata address="mex_address" />
</message>
</security>
</binding>
</ws2007FederationHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="MyRestBehavior">
<webHttp helpEnabled="true" faultExceptionEnabled="true" />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="MyServiceBehavior">
<serviceMetadata httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceAuthorization principalPermissionMode="Always" />
<serviceCredentials useIdentityConfiguration="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment minFreeMemoryPercentageToActivateService="0" aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<protocolMapping>
<add scheme="http" binding="ws2007FederationHttpBinding" />
</protocolMapping>
</system.serviceModel>
<system.webServer>
<defaultDocument>
<files>
<clear />
<add value="MyService.svc" />
</files>
</defaultDocument>
<modules runAllManagedModulesForAllRequests="true" />
<!--
To browse web app root directory during debugging, set the value below to true.
Set to false before deployment to avoid disclosing web app folder information.
-->
<directoryBrowse enabled="false" />
</system.webServer>
Any help is appreciated. Using .net 4.5