151

I need to develop an iPhone client that consumes JSON data from somewhere. I chose Web API from MS because it seemed easy enough but when it comes to authenticating users, things get quite frustrating.

I am amazed how I've not been able to find a clear example of how to authenticate a user right from the login screen down to using the Authorize attribute over my ApiController methods after several hours of Googling.

This is not a question but a request for an example of how to do this exactly. I have looked at the following pages:

Even though these explain how to handle unauthorized requests, these do not demonstrate clearly something like a LoginController or something like that to ask for user credentials and validate them.

Anyone willing to write a nice simple example or point me in the right direction, please?

starball
  • 20,030
  • 7
  • 43
  • 238
Luis Aguilar
  • 4,331
  • 6
  • 36
  • 55
  • 1
    I have answered kind of the same question on this: http://stackoverflow.com/questions/11775594/how-to-secure-an-aspnet-mvc-web-api/11782361#11782361 – cuongle Aug 03 '12 at 09:04
  • For Web Api with asp.net, you can just use the cookie and formsauthentication module as you would with an mvc app (if you want to). So in your web api code you can then check the principal to see if the user is logged-in, for example (same as before). – Elliot Mar 28 '13 at 15:07
  • Also look at my answer for http://stackoverflow.com/questions/11775594/how-to-secure-an-asp-net-web-api/16642284#16642284 – Varun Chatterji May 20 '13 at 03:53
  • I strongly recommend many folks read http://www.asp.net/web-api/overview/security/individual-accounts-in-web-api article. – Youngjae Apr 28 '14 at 12:43

3 Answers3

176

I am amazed how I've not been able to find a clear example of how to authenticate a user right from the login screen down to using the Authorize attribute over my ApiController methods after several hours of Googling.

That's because you are getting confused about these two concepts:

  • Authentication is the mechanism whereby systems may securely identify their users. Authentication systems provide an answers to the questions:

    • Who is the user?
    • Is the user really who he/she represents himself to be?
  • Authorization is the mechanism by which a system determines what level of access a particular authenticated user should have to secured resources controlled by the system. For example, a database management system might be designed so as to provide certain specified individuals with the ability to retrieve information from a database but not the ability to change data stored in the datbase, while giving other individuals the ability to change data. Authorization systems provide answers to the questions:

    • Is user X authorized to access resource R?
    • Is user X authorized to perform operation P?
    • Is user X authorized to perform operation P on resource R?

The Authorize attribute in MVC is used to apply access rules, for example:

 [System.Web.Http.Authorize(Roles = "Admin, Super User")]
 public ActionResult AdministratorsOnly()
 {
     return View();
 }

The above rule will allow only users in the Admin and Super User roles to access the method

These rules can also be set in the web.config file, using the location element. Example:

  <location path="Home/AdministratorsOnly">
    <system.web>
      <authorization>
        <allow roles="Administrators"/>
        <deny users="*"/>
      </authorization>
    </system.web>
  </location>

However, before those authorization rules are executed, you have to be authenticated to the current web site.

Even though these explain how to handle unauthorized requests, these do not demonstrate clearly something like a LoginController or something like that to ask for user credentials and validate them.

From here, we could split the problem in two:

  • Authenticate users when consuming the Web API services within the same Web application

    This would be the simplest approach, because you would rely on the Authentication in ASP.Net

    This is a simple example:

    Web.config

      <authentication mode="Forms">
        <forms
          protection="All"
          slidingExpiration="true"
          loginUrl="account/login"
          cookieless="UseCookies"
          enableCrossAppRedirects="false"
          name="cookieName"
        />
      </authentication>
    

    Users will be redirected to the account/login route, there you would render custom controls to ask for user credentials and then you would set the authentication cookie using:

          if (ModelState.IsValid)
          {
              if (Membership.ValidateUser(model.UserName, model.Password))
              {
                  FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
                  return RedirectToAction("Index", "Home");
              }
              else
              {
                  ModelState.AddModelError("", "The user name or password provided is incorrect.");
              }
          }
    
          // If we got this far, something failed, redisplay form
          return View(model);
    
  • Cross - platform authentication

    This case would be when you are only exposing Web API services within the Web application therefore, you would have another client consuming the services, the client could be another Web application or any .Net application (Win Forms, WPF, console, Windows service, etc)

    For example assume that you will be consuming the Web API service from another web application on the same network domain (within an intranet), in this case you could rely on the Windows authentication provided by ASP.Net.

      <authentication mode="Windows" />
    

    If your services are exposed on the Internet, then you would need to pass the authenticated tokens to each Web API service.

    For more info, take a loot to the following articles:

starball
  • 20,030
  • 7
  • 43
  • 238
Jupaol
  • 21,107
  • 8
  • 68
  • 100
  • 3
    Wow! That's what I call an answer. So, to conclude. I am planning in doing the following: 1. Create an account controller with a Login method that receives the user name and password over HTTPS and returns the login result and the token. 2. The client stores the token and sends it as a header (no HTTPS anymore) in the request which is validated by the web server. Is that a good approach? Then my final doubt is how to control token tampering and expiration. Is this possible? – Luis Aguilar Jul 31 '12 at 14:39
  • In general, yes it's a good approach however, since you do not want to use HTTPS in subsequent calls (I think for performance reasons). About tampering, one way would be for example to include in the token the IP address of the caller user when authenticated, and validate it in the `Authenticate` event. About expiration, you could use the same approach, when authenticated add the current date to the token and validate it again in the `Authenticate` event. Basically this technique replicates the _cookies_ behavior used by ASP.Net to implement authentication. – Jupaol Jul 31 '12 at 18:14
  • @LuisAguilar and Jupaol: This is a great answer! But a stupid question: Why is it safer to send an encrypted token via http (not https) than sending username and passwork directly? If someone intercepts the request and catches the token he can authenticate as *me* like he could with username and password. So, he could do the same damage. The only difference I see is that a token might expire, so the damage phase is limited to a day or a few. Is there any other benefit? – Slauma Aug 02 '12 at 14:11
  • Well, not really. The only true way that I know to avoid session hijacking is by using SSL. I think the same happens with ASP.NET session cookies. They can be stolen on their way to the server. I've tried to come up with a solution but none seems to totally solve this issue. Googling a bit I can see everyone just end up using SSL. Is there any way that you know to solve this? It would be great help. – Luis Aguilar Aug 02 '12 at 14:30
  • Something I was thinking was to include the original IP address that requested the login token the first time and compare it with the `Request.RequestUri` that supposedly is a trustable source to get the actual requester IP address. – Luis Aguilar Aug 02 '12 at 14:36
  • 1
    @LuisAguilar: I don't know a way to make it safe without SSL. The IP solution sounds good. But what about users with non-static IP addresses? Would you force them to login again if the IP has changed? – Slauma Aug 02 '12 at 15:01
  • Well. I've thought about that as well. Dynamic IP addresses tend to change. But not that often. I think is a fairly close alternative to SSL. – Luis Aguilar Aug 02 '12 at 15:13
  • 6
    @Jupaol I think I speak for many Web API developers, I cannot use Forms Authentication because I do not have a website and clients are not using a browser, nor can I use Integrated authentication because users can be anywhere in the world on any device (hence the Web API), so what do I use? – markmnl Jul 24 '14 at 00:58
  • Tokens are acquired with username and password, but they are not username and password. One couldn't change the password with a token, nor could one generate new tokens with a token. – Chris McCall Sep 19 '14 at 18:48
  • 1
    @Luis Aguilar I think with smartphone your IP address can change all the time. – Bastien Vandamme Oct 22 '14 at 06:54
  • 22
    I don't understand why this answer get so many upvote. It's not about ASP.NET Web API but about ASP.NET MVC. – Bastien Vandamme Oct 22 '14 at 06:59
  • 3
    I'd like to reiterate B413's comment and point out that this question specifically asks for Web API – Julien Jan 23 '15 at 18:19
  • 6
    Is this the most up-voted 'wrong' answer on SO? The answer doesn't actually talk about web api which is very different from a mvc web application! Like @B413 I am totally shocked! – dragonfly02 Jun 06 '16 at 22:10
16

If you want to authenticate against a user name and password and without an authorization cookie, the MVC4 Authorize attribute won't work out of the box. However, you can add the following helper method to your controller to accept basic authentication headers. Call it from the beginning of your controller's methods.

void EnsureAuthenticated(string role)
{
    string[] parts = UTF8Encoding.UTF8.GetString(Convert.FromBase64String(Request.Headers.Authorization.Parameter)).Split(':');
    if (parts.Length != 2 || !Membership.ValidateUser(parts[0], parts[1]))
        throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "No account with that username and password"));
    if (role != null && !Roles.IsUserInRole(parts[0], role))
        throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "An administrator account is required"));
}

From the client side, this helper creates a HttpClient with the authentication header in place:

static HttpClient CreateBasicAuthenticationHttpClient(string userName, string password)
{
    var client = new HttpClient();
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes(userName + ':' + password)));
    return client;
}
Edward Brey
  • 40,302
  • 20
  • 199
  • 253
  • Just wanted to comment that I was looking for a simply way to use the industry standard to pass credentials in the header. This example showed the basics from both the server and the client side and was all I needed. – da_jokker Mar 23 '17 at 16:14
9

I am working on a MVC5/Web API project and needed to be able to get authorization for the Web Api methods. When my index view is first loaded I make a call to the 'token' Web API method which I believe is created automatically.

The client side code (CoffeeScript) to get the token is:

getAuthenticationToken = (username, password) ->
    dataToSend = "username=" + username + "&password=" + password
    dataToSend += "&grant_type=password"
    $.post("/token", dataToSend).success saveAccessToken

If successful the following is called, which saves the authentication token locally:

saveAccessToken = (response) ->
    window.authenticationToken = response.access_token

Then if I need to make an Ajax call to a Web API method that has the [Authorize] tag I simply add the following header to my Ajax call:

{ "Authorization": "Bearer " + window.authenticationToken }
ProfNimrod
  • 4,142
  • 2
  • 35
  • 54
  • Where from does `response.access_token` come from. Are u setting it from c# code..? – shashwat Feb 20 '14 at 10:50
  • The 'response' object is returned by the 'token' method. – ProfNimrod Feb 20 '14 at 14:30
  • I haven't looked into roles. This approach just gives you an access-token so you can call WebApi methods decorated with the [Authorize] tag. Presuambly, when you call any of those methods you could check for roles. http://stackoverflow.com/questions/19689570/mvc-5-check-user-role might help. – ProfNimrod Feb 20 '14 at 14:34
  • And where in this solution do you actually authenticate your user? – Craig Brett Apr 25 '17 at 13:05
  • The /token endpoint is created automatically for any new Web API project. The code behind this is where the user is authenticated. It's a bit more complicated if you've added a Web API controller to an existing MVC project. – ProfNimrod May 29 '17 at 16:08