In my last post I showed that once you secured the service using a SSL certificate, you could now view a security context when debugging. This is important because now we need to populate that context so we can determine if we want to allow the client to be authenticated to the service, and then check to see if they are authorized for whichever method or operation they have requested.
Once again we can fall back to our knowledge of the web in general for this configuration. Basic Authentication is nothing new to RESTful or even WCF services in general. It is a 401 HTTP challenge/response mechanism to prompt the client for credentials. As we also know, 'Basic' authentication can get a black-eye because it is just a base64 encoded non-encrypted string that is not natively secure, unless used in conjunction with a SSL certificate to secure the transport of this sensitive information.
From my last post we configured a simple REST service using a security mode of 'Transport' with a SSL certificate, and we now need to configure the clientCredentialType attribute. If we add a <transport> element within our existing <security> parent element, we can select Basic as our clientCredentialType. Notice there are several options for this attribute and you can read about all of them here: HttpClientCredentialType Enumeration. You might be wondering about 'Digest' as the security mode, but it is not actually that much more secure than 'Basic' and requires the hosting server to be joined to a domain. As for the others like Windows and NTLM they are good in intranet or extranet hosted scenarios. The 'None' option is the default option, but the whole point of this conversation is about securing our service, so we don't want to use that. The 'Certificate' option will be the focus of my next post on another mainstream way to secure our internet facing RESTful service. Our focus continues to be on using Basic authentication as displayed below:
<bindings> <webHttpBinding> <binding name="webHttpTransportSecurity"> <security mode="Transport"> <transport clientCredentialType="Basic"></transport> </security> </binding> </webHttpBinding> </bindings>
Our next step is to configure the service to point to a custom user name and password validator method that we will create shortly. Within our <serviceBehaviors> element we can configure the <servicecredentials> element and dictate that we want to use a Custom 'userNamePasswordValidationMode' value. We need to do this so we can intercept the credentials provided by the client via the request message header.
<serviceBehaviors> <behavior name="SecureRESTSvcTestBehavior"> <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment --> <serviceMetadata httpGetEnabled="false" 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="RESTfulSecuritySH.CustomUserNameValidator, RESTfulSecuritySH" /> </serviceCredentials> </behavior> </serviceBehaviors>
Notice above that I have already provided a name for the class which will intercept and validate these credentials: 'CustomUserNameValidator'. The overridden 'Validate' method in this class will allow us to check if the user accessing our service is going to be authenticated. In the call stack, this method we are going to create will be called prior to the method being requested, so if authentication fails it will happen prior to accessing anything else inside the service. This code snippet that will follow is a close derivative to one that came from the MSDN (here: http://msdn.microsoft.com/en-us/library/aa702565.aspx).
public class CustomUserNameValidator : UserNamePasswordValidator { // This method validates users. It allows in two users, user1 and user2 // This code is for illustration purposes only and // must not be used in a production environment because it is not secure. public override void Validate(string userName, string password) { if (null == userName || null == password) { throw new ArgumentNullException("You must provide both the username and password to access this service"); } if (!(userName == "user1" && password == "test") && !(userName == "user2" && password == "test")) { // This throws an informative fault to the client. throw new FaultException("Unknown Username or Incorrect Password"); // When you do not want to throw an informative fault to the client, // throw the following exception. // throw new SecurityTokenException("Unknown Username or Incorrect Password"); } } }
Looking at the code above we see that we are able to inspect the username and password values to authenticate a user to the service. At this point you are seeing that this is preforming service level authentication and is more coarse grained than some of the method level authorization we will see in a minute. The point of this code is to validate if the client making the call has access to your service.
It should go without saying that you would not use the simplistic implementation from the code above. More than likely, you would probably make a call to a database to validate if the user's credentials are valid as opposed to hardcoding the logic. If the credentials are validated, control will pass on to the originally requested method. The 'Validate' method is void so there is nothing to set or return once authorized. It's a 'no news is good news' type of functionality, where exceptions should be raised only when there is an authentication issue.
This custom method of authenticating users is different as opposed to those of you that have overridden the 'CheckAccessCore' when using a defined 'serviceAuthorizationManagerType' that returns a bool indicating if the user is authorized. Since we are validating the username and password, configuring a value for the 'customUserNamePasswordValidatorType' is exactly what we need.
At this point test out what we have done, by starting your service (e.g. WCF Test Client) and make a call to the 'Customer' method as we built in my last post. This time we will be prompted for credentials by the browser.
Upon entering the correct credentials (username = "user1", password = "test") we get the returned JSON results expected. All of this authentication happened securely because our RESTful service is secured with a SSL certificate. Also note these credentials can be assigned programmatically in whatever language you are using. The beauty of REST services is they are platform and language agnostic and rely on the standards of the web and HTTP. If you happen to be a .NET client calling the service, then you would add the credentials to the request header as shown below:
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(@"https://DevMachine1234:8099/MyRESTServices/Customer/1"); //Add a header to the request that contains our credentials //DO NOT HARDCODE IN PRODUCTION!! Pull credentials real-time from database or other store. string svcCredentials = Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes("user1"+ ":" + "test")); req.Headers.Add("Authorization", "Basic " + svcCredentials); //Just some example code to parse the JSON response using the JavaScriptSerializer using (WebResponse svcResponse = (HttpWebResponse)req.GetResponse()) { using (StreamReader sr = new StreamReader(svcResponse.GetResponseStream())) { JavaScriptSerializer js = new JavaScriptSerializer(); string jsonTxt = sr.ReadToEnd(); } }
Now try entering incorrect credentials (in code or in a browser) and make the same REST call. This time as expected and exception is thrown, the client receives a HTTP 403 Forbidden, and we are not permitted to view the results or access the service.
The next and final step here is to take the provided client context a step further with authorization at the method level. The reason for doing this is to offer fine grained security at the method level. For example if you are hosting a RESTful weather service with 10 methods, but only 7 of the methods are served up to everyone and the remaining 3 are for paid subscribers only. In this case we need to preform authorization at the method level.
Remember from my last post I mentioned the importance of the 'ServiceSecurityContext' object. Here is where it will come into play. This is populated with the client's context after being validated by our custom 'Validate' method. Within the ServiceSecurityContext instance the 'PrimaryIdentity' is populated with an instance of System.Principal.Identity.GenericIdentity that contains the properties we need to determine if this user is authorized to the requested method.
The code below will examine this instance and determine if the call can proceed:
//NOTE: This code is within the actual method call (e.g. GetCustomer CLR method) //Get current SecurityContext to inspect below for authorizing ServiceSecurityContext securityCtx; securityCtx = OperationContext.Current.ServiceSecurityContext; //This code is a bit primitive and ideally you would call off to another method here that would //perform the logic and probably just return a bool value as in commented out line below: //if (CheckIfAuthorized(securityCtx) != true) if ((securityCtx.PrimaryIdentity.IsAuthenticated != true) || (securityCtx.PrimaryIdentity.Name != "user1")) { throw new UnauthorizedAccessException("You are permitted to call this method. Access Denied."); }
If you use the "user1" account you can see that we are indeed both authenticated to the service and authorized to call this method. However, now try and log back into the service with the "user2" account. This account is authenticated to make calls to the site, but not authorized to call this method. Once again you would not hardcode this logic, but rather be calling out to a security file or database to determine the authorization for this user and returning a bool more than likely. This provides that method level fine grained security that many services require.
So to wrap this up, you can implement a well know HTTP authentication method in 'Basic' authentication to secure your RESTful services. We can then take the context of the authenticated client call a step further and implement fine grained authorization at a method level to limit access to methods when needed. By using a well know security protocol that has been secured with SSL over HTTPS, you will broaden your services use and popularity using well know security practices.
First of all I’d like to say Allen that your blog is pretty good and have some useful information regarding DOT NET Development Services. I am searching Dedicated .NET Developers for my new project because it is a vast and big projects and I also need some information which I got from this blog. Thanks for sharing this wonderful post.
ReplyDeleteNice tut. I would love to see this but self hosted in a winform. I tried those instruction and ended up with a blank page. I could enter the user/pass and it would auth. ok but once "logged" the service was blank.
ReplyDeleteAre you serious. This thing spanned 4 difference posts. I hope MS has a better (simpler) solution for securing REST services.
ReplyDeleteWell a few points to make here. 1st off, authentication techniques and HTTP are web protocols and have nothing to do with Microsoft. These posts were in regards to WCF hosted RESTful services. And 'yes', there are several steps in getting the authentication up and running in order to be considered 'secure'. I think though there is an answer to your needs which is the new Web API built as a part of ASP.NET MVC. It streamlines the process of hosting and securing REST based services quite a bit. Have a look: http://www.asp.net/web-api
ReplyDeleteGreat blog post(s)! Exactly what I needed for my new project which has to support requests from several different programming languages and environments. I've never known that WCF REST services are so straight-forward to use, but thanks to you, my service runs per SSL and has a fine security concept - even usable from a simple web browser.
ReplyDeleteHello Allen,
ReplyDeleteThank you for your wonderful series on securing WCF Data Services. It was very helpful to get my app secured in a couple of hours.
This doesn't seem to work when hosting with IIS 7.
ReplyDeleteActually all of my work was done against IIS 7 and worked well. Is there a specific issue you are having?
ReplyDeleteAllen,
ReplyDeleteI am getting following error:
The authentication schemes configured on the host ('Anonymous') do not allow those configured on the binding 'WebHttpBinding' ('Basic'). Please ensure that the SecurityMode is set to Transport or TransportCredentialOnly. Additionally, this may be resolved by changing the authentication schemes for this application through the IIS management tool, through the ServiceHost.Authentication.AuthenticationSchemes property, in the application configuration file at the element, by updating the ClientCredentialType property on the binding, or by adjusting the AuthenticationScheme property on the HttpTransportBindingElement.
Note that SecurityMode is set to Transport as mentioned by you. My WCF REST service is meant to be called from cross-domain jquery client. My IIS application currently has anonymous and forms authentication enabled.
Here is my web.config.
Please advise.
This works find in self hosted environment. soon as I move it to iis7. Username and password never reaches UserNamePasswordValidator class.
ReplyDeleteAllen,
ReplyDeleteExcellent post. I was trying to do the same thing but always Basic authentication calls and checks against the windows account and not overriding the custom usernamevalidator class. using iis 7, is there any specific change i have to do in iis. But when i tried to do serviceAuthorizationManagerType, then it is working fine.
Pls let me know
im always getting The remote server returned an error: (500) Internal Server Error.
ReplyDeleteI am getting the same thing as several users above regarding code never reaching UserNamePasswordValidator. I monkeyed with IIS thinking that would make a difference but no dice. Anyone figure this out?
ReplyDeleteMy issue below was address in his blog post:
ReplyDeletehttp://allen-conway-dotnet.blogspot.com/2012/07/using-basic-authentication-in-rest.html
Hi,
ReplyDeleteI am having the same problem as several users above regarding code never reaching UserNamePasswordValidator.
I am using IIS 7.5.
me too having the same problem with usernamepasswordvalidator
ReplyDeleteI love you Allen. Your posts are excellent and help where MS documentation is lacking.
ReplyDeleteHi Allen,
ReplyDeleteIf I had a phone app client, and only that phone app was allowed to call my RESTful service,could I simply use a GUID as a parameter so that the service can check for the GUID and if correct then grant access to the caller?
Yes you could use a GUID and check for it in the endpoint to make sure the call is authorized. I though would probably go the route of using a more common standard like using Basic Authentication or passing a Bearer token in the header. This post is a tad older so I would probably opt for a WebAPI RESTful service as opposed to a WCF service. If using WebAPI, you can then override the SendAsync method in a custom handler to inspect the credentials in the header and only proceed if the credentials match what you expect. Now with any information passing, GUID, Basic Authentication, Bearer token, etc. you must use HTTPS to secure that passing of data otherwise it could be compromised as it would pass over the wire in plain text for anyone to see.
ReplyDeleteThanks Allen.
ReplyDeleteIs WebAPI better than WCF? Why else would it be a better option than WCF?
Its WebAPI easier to configure than WCF? WCF is always a nightmare to get the config settings working.
Do you have any posts on how to create a WebAPI RESTful service?
Great tutorial. Did you ever do the post dealing with the Certificate clientCredentialType?
ReplyDeleteGetting below error . if trying to access service url by HttpWebRequest:-
ReplyDeleteThe remote server returned an error: (401) Unauthorized.
Thank you, that was just an awesome post!!!
ReplyDelete