This is the eight part of Building ASP.Net Web API RESTful Service Series. The topics we’ll cover are:
- Building the Database Model using Entity Framework Code First – Part 1.
- Applying the Repository Pattern for the Data Access Layer – Part 2.
- Getting started with ASP.Net Web API – Part 3.
- Implement Model Factory, Dependency Injection and Configuring Formatters – Part 4.
- Implement HTTP actions POST, PUT, and DELETE In Web API – Part 5.
- Implement Resources Association – Part 6.
- Implement Resources Pagination – Part 7.
- Securing Web API – Part 8. (This Post).
- Preparing Web API for Versioning – Part 9.
- Different techniques to Implement Versioning – Part 10.
- Caching resources using CacheCow and ETag – Part 11.
Update (2014-March-5) Two new posts which cover ASP.Net Web API 2 new features:
Update (2014-April-16) New multi part series tutorial which covers building OData Service using ASP.Net Web API.
Securing Web API
In this post we’ll talk about securing our eLearning API, till this moment all requests sent from client to the API are done over HTTP protocol (http://) and the communication is not encrypted, but in this post we’ll implement authentication feature in “StudentsController” so we’ll be sending Username and Password for authenticating students. It is well known that transmitting confidential information should be done using secure HTTP (https://).
Enforce HTTPS for Web API
We can enforce HTTPS on the entire Web API by configuring this on IIS level, but in some scenarios you might enforce HTTPS on certain methods where we transmit confidential information and use HTTP for other methods.
In order to implement this we need to use Web API filters; basically filters will allow us to execute some code in the pipeline before the execution of code in controller methods. This new filter will be responsible to examine if the URI scheme is secured, and if it is not secure, the filter will reject the call and send response back to the client informing him that request should be done over HTTPS.
We’ll add a new filter which derives from “AuthorizationFilterAttribute”, this filter contains an overridden method called “OnAuthorization” where we can inject a new response in case the request is not done over HTTPS.
Let’s add new folder named “Filters” to project root then add new class named “ForceHttpsAttribute” which derives from “System.Web.Http.Filters.AuthorizationFilterAttribute”
The code for the filter class will be as the below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public class ForceHttpsAttribute : AuthorizationFilterAttribute { public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext) { var request = actionContext.Request; if (request.RequestUri.Scheme != Uri.UriSchemeHttps) { var html = "<p>Https is required</p>"; if (request.Method.Method == "GET") { actionContext.Response = request.CreateResponse(HttpStatusCode.Found); actionContext.Response.Content = new StringContent(html, Encoding.UTF8, "text/html"); UriBuilder httpsNewUri = new UriBuilder(request.RequestUri); httpsNewUri.Scheme = Uri.UriSchemeHttps; httpsNewUri.Port = 443; actionContext.Response.Headers.Location = httpsNewUri.Uri; } else { actionContext.Response = request.CreateResponse(HttpStatusCode.NotFound); actionContext.Response.Content = new StringContent(html, Encoding.UTF8, "text/html"); } } } } |
By looking at the code above we are using “actionContext” parameter to get the request and response object from it. What we are doing here is examining the URI scheme of the request, so if it is not secure (http://) we need to return small html message in the response body informing client to send the request again over https.
As well we are differentiating between the GET method, and other methods (POST, PUT, DELETE); because in case the client initiated a GET request to existing resource over http we need to construct the same request again using https scheme and use 443 SSL port then inject this secure URI in response location header. By doing this the client (browser) will initiate automatically another GET request using https scheme .
In case of non GET requests, we will return 404 status code (Not Found) and small html message informing client to send the request again over https.
Now if we want to enforce this filter over the entire Web API we need to add this filter globally in “WebAPIConfig” class as the code below:
1 2 3 4 |
public static void Register(HttpConfiguration config) { config.Filters.Add(new ForceHttpsAttribute()); } |
But if we want to enforce HTTPS for certain methods or certain controllers we can add this filter attribute “ForceHttps” as the below:
1 2 3 4 5 6 7 8 9 10 11 |
//Enforce HTTPS on the entire controller [Learning.Web.Filters.ForceHttps()] public class CoursesController : BaseApiController { //Enforce HTTPS on POST method only [Learning.Web.Filters.ForceHttps()] public HttpResponseMessage Post([FromBody] CourseModel courseModel) { } } |
Authenticate Users using Basic Authentication
Until this moment all methods in our API are public, and any user on the internet is able to request any resource, but in real life scenario this is not correct, specific data should be accessed by specific people, so we need to authenticate requests on certain resources. A good candidates in our sample API for clients authentications are:
- Clients who make a GET request to the URI “http://{your_port}/api/students/{userName}“. This means that if a client issue a request to GET detailed information about student with username “TaiseerJoudeh”, then he needs to provide the username and password for this resource in the request to authenticate him self. As well we won’t allow authenticated user “TaiseerJoudeh” to GET detailed information for another resource because he is not the resource owner and he shouldn’t be able to see his detailed information such as Email, Birth date, classes enrolled in, etc…
- Clients who make a POST request the URI “http://{your_port}/api/courses/2/students/{userName}“. This method is used to enroll specific student in specific class, it makes sense to authenticate requests here because if student with username “KhaledHassan” wants to enroll in course with Id 2, then he needs to authenticate himself by providing username and password. Otherwise anyone will be able to enroll any student in any class.
In our scenario we’ll use the Basic Authentication to authenticate users requesting the above two resources, to do so we need to add new Web API filter which will be responsible to read authorization data from request header, check if the authentication type is “basic”, validate credentials sent in the authorization headers, and finally authenticate user if all is good. Otherwise it will return a response with status code 401 (Unauthorized) and it won’t return the resource.
Before digging into the code, let’s talk a little bit about basic authentication.
What is Basic Authentication?
It provides a mean to authenticate the sender of the request before actually processing the HTTP request. This will protect the server against Denial of service attacks (DoS). The way it works that the client who is initiating the HTTP request provides a username and password which is base64-encoded and placed in the HTTP header as string on the form “username:password”, the recipient of the message (server) is expected to first authenticate the credentials and process the request further only when the authentication is successful.
As the username and password are only base64-encoded and to avoid passwords are exposed to others, Basic Authentication should be always used over SSL connection (HTTPS).
To apply this in our API let’s add new class named “LearningAuthorizeAttribute” which derives from “System.Web.Http.Filters.AuthorizationFilterAttribute”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
public class LearningAuthorizeAttribute : AuthorizationFilterAttribute { [Inject] public LearningRepository TheRepository { get; set; } public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext) { //Case that user is authenticated using forms authentication //so no need to check header for basic authentication. if (Thread.CurrentPrincipal.Identity.IsAuthenticated) { return; } var authHeader = actionContext.Request.Headers.Authorization; if (authHeader != null) { if (authHeader.Scheme.Equals("basic", StringComparison.OrdinalIgnoreCase) && !String.IsNullOrWhiteSpace(authHeader.Parameter)) { var credArray = GetCredentials(authHeader); var userName = credArray[0]; var password = credArray[1]; if (IsResourceOwner(userName, actionContext)) { //You can use Websecurity or asp.net memebrship provider to login, for //for he sake of keeping example simple, we used out own login functionality if (TheRepository.LoginStudent(userName, password)) { var currentPrincipal = new GenericPrincipal(new GenericIdentity(userName), null); Thread.CurrentPrincipal = currentPrincipal; return; } } } } HandleUnauthorizedRequest(actionContext); } private string[] GetCredentials(System.Net.Http.Headers.AuthenticationHeaderValue authHeader) { //Base 64 encoded string var rawCred = authHeader.Parameter; var encoding = Encoding.GetEncoding("iso-8859-1"); var cred = encoding.GetString(Convert.FromBase64String(rawCred)); var credArray = cred.Split(':'); return credArray; } private bool IsResourceOwner(string userName, System.Web.Http.Controllers.HttpActionContext actionContext) { var routeData = actionContext.Request.GetRouteData(); var resourceUserName = routeData.Values["userName"] as string; if (resourceUserName == userName) { return true; } return false; } private void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext) { actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized); actionContext.Response.Headers.Add("WWW-Authenticate", "Basic Scheme='eLearning' location='http://localhost:8323/account/login'"); } } |
In the code above we’ve overridden method “OnAuthorization” and implemented the below:
- Getting the authorization data from request headers
- Making sure that authorization header scheme is set to “basic” authentication and contains base64-encoded string.
- Converting the base64-encoded string to a string on the form “username:password” and get the username and password.
- Validating that username sent in authentication header is the same for username in URI to ensure that resource owner only able to view his details.
- Validating the credentials against our database.
- If the credentials are correct we set the identity for current principal, so in sub subsequent requests the user already authenticated.
- If the credentials are incorrect the server sends an HTTP response with a 401 status code (Unauthorized), and a WWW-Authenticate header. Web clients (browser) handle this response by requesting a user ID and password from client.
Now to apply basic authentication on the two methods we mentioned earlier, all we need to do is to add this attribute “LearningAuthorizeAttribute” to those methods as the code below:
1 2 3 4 5 6 7 8 |
public class StudentsController : BaseApiController { [LearningAuthorizeAttribute] public HttpResponseMessage Get(string userName) { } } |
1 2 3 4 5 6 7 8 |
public class EnrollmentsController : BaseApiController { [LearningAuthorizeAttribute] public HttpResponseMessage Post(int courseId, [FromUri]string userName, [FromBody]Enrollment enrollment) { } } |
Now we need to test authentication by sending GET request to URI: “http://localhost:{your_port}/api/students/TaiseerJoudeh” using two different clients, Firefox and Fiddler.
Testing user Firefox:
We’ll issue get request using the browser and the response returned will be 401 because we didn’t provide username and password and “Authentication Required” prompt will be displayed asking for the username and password. By providing the the correct credentials we’ll receive status code 200 along with complete JSON object graph which contains all specific data for for this user. For any sub subsequent request for the same resource the code will not check credentials because we have created principal for this user and he is already authenticated.
Testing using Fiddler:
By using fiddler we need to create the base64-encoded string which contains the “username:password” and send it along with the authorization header, to generate this string we will use http://www.base64encode.org/ as the image below:
Note that this is not encryption at all, so as we stated earlier, using Basic Authentication should be done over SSL only.
Now we will use the encoded string to pass it in authorization header “Authorization: Basic VGFpc2VlckpvdWRlaDpZRUFSVkZGTw==” the request will be as the image below:
The response code will be 200 OK and will receive the specific data for this authenticated user.
In the next post we’ll talk about versioning Web API and how we can implement different techniques to implement versioning.
Reblogged this on Sutoprise Avenue, A SutoCom Source.
Reblogged this on Sutoprise Avenue, A SutoCom Source.
How would you implement roles with this Authorization filter?
How would you add Role support from this point?
The problem I having using Postman to send the request over is the header contains the username and password on the first request.
let’s say it’s invalid and sends back unathorized.
popup opens on browser. type in new username/password
on the server the request header has the same username/password as the first request.
Hello Ray, this should not happen, put a break point on ur code and trace the code. If you send new username/password with each request they should be updated always. You can fork the code on github and you can compare it with ur local copy.
I found this issue initially because I was debugging it. I think it’s just the way postman works and is overriding it. With postman it allows me to add the request headers for basic auth in there so it must send it again ignoring the popup request on the browser.
I am creating a Web API 2 service, but would like to authenticate once passback a token and then on subsequent requests use the token. Token will be like a guid or facebook access token passed over and I can then look up the user with this string/id.
Is there a standard practice for this or does every request always have to send this over?
By the way, Thanks for the fast response!! I wasn’t expecting anything or maybe in several days I might get something. Kudos to you. Great Blog!! Very concise well written and easy to understand.
The only thing I had to look up was this part:
actionContext.Response.Headers.Add(“WWW-Authenticate”,
“Basic Scheme=’eLearning’ location=’http://localhost:8323/account/login'”);
Wasn’t sure what to put in there. But looks like it’s never used and you can put anything or nothing there. Probabably the key value pair Basic Scheme and location would be used on the client side to redirect them. When I googled it, it looks like people were using the key/value of Realm for the Response of WWW-Authenticate, not sure what that is, but I’ll Google into it more later.
Thanks Again!
Hello again,
If you want to send a token, then I believe you need to send it with each request, if you want to keep the URL pretty and do not want to send it as query string, you can use custom header like (X-AuthToken) with each request, and you reject this request if this header is not available.
Thanks for your nice words and good luck in your new service. Let me know if you need further help 🙂
HI Taiseer,
Nice and well explained article. Thanks for this.
I have couple of questions where I could not realized yet.
Lets say,
We have the web api (as in above) that expose GET,POST,PUT, DELETE endpoints for the Student entity. This end points
will be consumed by an android app. Android app will use HTTP request.
Then android app is making a first request to the GET end point with valid encrypted username and password in the header. Since with the valid credentials in the header, the web api returns the status code 200 along with complete JSON object graph.
THIS IS OK and I UNDERSTOOD.
Below are the points that I could not understand. Please help me out.
Let’s say,
The android app is making the second request to POST endpoint for the Student controller. At this request
1) Do I need to encrypt the username and password again and attache to the header and send it?
2) Do the LearningAuthorizeAttribute filter will re-authenticate the user with encrypted username and password in the
HTTP header?
3) If it is re-authenticating over and over for each requests, How do we skip re-authentication for already logged in users?
Please help me out to understand how we can identify user who already logged in…
Thank You
Pubudu
Hello Pubudu,
Glad you like it, below my answers to your questions
1. As long you are using basic authentication, then yes you need to send the username/pasword along with each request, if you are building mobile application then mo recommendation is to use Auth token which you send with each request and you can validate this token on the server (expiry, username has access to resource etc…)
2. If you authenticated X user then you can do any HTTP operation on it without re-authentication because we are setting the Thread.CurrentPrincipal for valid users.
3. My recommendation to use Auth token if you are building an app, check this article which i find basic and easy to explain the concept of tokens http://www.codeproject.com/Articles/630986/Cross-Platform-Authentication-With-ASP-NET-Web-API
HI Taiseer,
Big big thanks to you. I’m really sending my warm regards.. Because this was where I got so uncleared. Thanks..
I have another point which is still unclear for me. Please help me out on this as well.
In the article, it has mentioned
“Common use case of public-key cryptography/x.509 certificates involve sharing your public keys with the collaborating systems which could send data encrypted using your public key, which could be decrypted only using your private key. In our case, we don’t need to and shouldn’t share the public key with any other party as we are the issuer and consumer of the token. So, both keys needs to be protected religiously to prevent forgery of the token.”
To be honest I don’t understand how this certificates works with the Public Key and Private Key. Can you please tell me how this is going to happen in following scenario.
Send the first request with username and password over https.
Then generates the token at the server using the certificate.
Creates the response with encrypted token attached to the header.
Sends the data with token and data with the response.
So,
How would the certificate or public key or private key get involved in the above scenario?
If u can create me a how the request and response be would be really grate..
Please help me out…
Thank You
Pubudu
One more thing ..
Once we set the Thread.CurrentPrincipal for all incoming request with different valid users from different regions (from different IP’s),
When the Thread.CurrentPrincipal will get expired like expiring the session…
Because HTTP is stateless …
Please provide your valuable answer..
Thank You
Pubudu
Hi, I’ve followed your instructions on the BasicAuthorizeAttribute, the only problem is that even with the [Inject] attribute, the property is staying null.
When I use the same property (with the [Inject] attribute) in a Controller, it’s injecting just fine.
Is there something I miss? Is additional configuration needs to be done in order for dependency injection into Filters will happen?
Took me a while, I’ve found the NinjectWebApiFilterProvider,
It sovled it,
Thanks!
I was typing a reply for you 🙂 You are right we’ve to add support for Web API filters injection by adding the code below:
GlobalConfiguration.Configuration.Services.Add(typeof(IFilterProvider), new NinjectWebApiFilterProvider(kernel));
Good luck!
Thank you Taiseer,
I’ll try to implement it with Unity as well, since it’s much more supported and maintained.
Hi Taiseer,
Clear and useful blog, well done. May I suggest a future article regarding token authentication in your own style? It seems a lot of people are looking for this (including me). I know this article: http://www.codeproject.com/Articles/630986/Cross-Platform-Authentication-With-ASP-NET-Web-API tries to explain it, but a simpler example from you might be clearer.
One question on this particular article. This code:
actionContext.Response.Headers.Add(“WWW-Authenticate”, “Basic Scheme=’eLearning’ location=’http://localhost:8323/account/login'”);
references http and not httpS. You rightly said throughout your article that basic authentication should always happen over SLL, so why not use https in the response header?
All the best.
Taiseer,
I’m working through the Authentication portion and have some questions.
You state when the login prompt via the web page occurs use the proper credentials… what credentials. you don’t set those in the Learning.Data-> LearningDataSeeder.cs file. I hardcoded the password in that cs file to “1234” to get past this part.
However when I enter the credentials, TaiseerJoudeh and 1234 it fails… I tried the base64 approach as well and received the following error within Fiddler,
HTTP/1.1 500 Internal Server Error
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?YzpcdXNlcnNcam9uLm1jY2Fpblxkb2N1bWVudHNcdmlzdWFsIHN0dWRpbyAyMDEyXFByb2plY3RzXGVMZWFybmluZ1xMZWFybmluZy5XZWJcYXBpXHN0dWRlbnRzXFRhaXNlZXJKb3VkZWg=?=
X-Powered-By: ASP.NET
Date: Mon, 08 Sep 2014 19:40:03 GMT
Content-Length: 646
{“Message”:”An error has occurred.”,”ExceptionMessage”:”Object reference not set to an instance of an object.”,”ExceptionType”:”System.NullReferenceException”,”StackTrace”:” at Learning.Web.Filters.LearningAuthorizeAttribute.OnAuthorization(HttpActionContext actionContext) in c:\\Users\\jon.mccain\\Documents\\Visual Studio 2012\\Projects\\eLearning\\Learning.Web\\Filters\\LearningAuthorizeAttribute.cs:line 43\r\n at System.Web.Http.Filters.AuthorizationFilterAttribute.System.Web.Http.Filters.IAuthorizationFilter.ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func`1 continuation)”}
Which is the same error I received in the browser approach.
I’ve replaced my code with yours and rebuilt the solution.
Would you be able to guide me in what I’ve done incorrectly?
Thanks,
Jon
See this StackOverflow post on proper response codes if https isn’t used: http://stackoverflow.com/q/2554778/145173
Thanks Edward for pointing me there, will check this definitelyz
Hi good tutorial
questions?
why in your code put the user authenticate in Thread.CurrentPrincipal = currentPrincipal and not in actionContext.RequestContext.Principal what it´s diferent?
how can avoid with each request validate the credential against the database like session
thanks
sorry for my inglish i don´t speak
Please check this post for the correct way to do authentication.
Hello,
First of all nice Blog! (good topic)
I have a question , if I want to use your example code as authentication for my server side, that give service to android application , did I need to change something ? (not working with browser ), may you know a better approach to that task.
Second how I can examine my code with Chrome browser ? (how I can change my url ??)
Thanks,
MAT
Hello Taiseer ,
Very useful blog and article.
When I try yo run your filter code , I receive run time error in IsResourceOwner function.
The routeData.Values[“userName”] not recognize the userName key (the dictionary is empty) .
So what I do wrong?
Thanks
HAB
Hey, thanks a lot for sharing. Maybe my question is a bit awkward, But, one question is here. Why you use the ISO-8859-1 technique for the base64 encoding?