"
ASP.NET (snapshot 2017) Microsoft documentation and samples

Secure a Web API with Individual Accounts and Local Login in ASP.NET Web API 2.2

by Mike Wasson

Download Sample App

This topic shows how to secure a web API using OAuth2 to authenticate against a membership database.

Software versions used in the tutorial

In Visual Studio 2013, the Web API project template gives you three options for authentication:

For more details about these options, see Creating ASP.NET Web Projects in Visual Studio 2013.

Individual accounts provide two ways for a user to log in:

This article looks at the local login scenario. For both local and social login, Web API uses OAuth2 to authenticate requests. However, the credential flows are different for local and social login.

In this article, I’ll demonstrate a simple app that lets the user log in and send authenticated AJAX calls to a web API. You can download the sample code here. The readme describes how to create the sample from scratch in Visual Studio.

The sample app uses Knockout.js for data-binding and jQuery for sending AJAX requests. I’ll be focusing on the AJAX calls, so you don’t need to know Knockout.js for this article.

Along the way, I’ll describe:

First, we need to define some OAuth2 terminology.

An application can act as both authorization server and resource server. The Web API project template follows this pattern.

Local Login Credential Flow

For local login, Web API uses the resource owner password flow defined in OAuth2.

  1. The user enters a name and password into the client.
  2. The client sends these credentials to the authorization server.
  3. The authorization server authenticates the credentials and returns an access token.
  4. To access a protected resource, the client includes the access token in the Authorization header of the HTTP request.

When you select Individual accounts in the Web API project template, the project includes an authorization server that validates user credentials and issues tokens. The following diagram shows the same credential flow in terms of Web API components.

In this scenario, Web API controllers act as resource servers. An authentication filter validates access tokens, and the [Authorize] attribute is used to protect a resource. When a controller or action has the [Authorize] attribute, all requests to that controller or action must be authenticated. Otherwise, authorization is denied, and Web API returns a 401 (Unauthorized) error.

The authorization server and the authentication filter both call into an OWIN middleware component that handles the details of OAuth2. I’ll describe the design in more detail later in this tutorial.

Sending an Unauthorized Request

To get started, run the app and click the Call API button. When the request completes, you should see an error message in the Result box. That’s because the request does not contain an access token, so the request is unauthorized.

The Call API button sends an AJAX request to ~/api/values, which invokes a Web API controller action. Here is the section of JavaScript code that sends the AJAX request. In the sample app, all of the JavaScript app code is located in the Scripts.js file.

[!code-javascriptMain]

   1:  // If we already have a bearer token, set the Authorization header.
   2:  var token = sessionStorage.getItem(tokenKey);
   3:  var headers = {};
   4:  if (token) {
   5:      headers.Authorization = 'Bearer ' + token;
   6:  }
   7:   
   8:  $.ajax({
   9:      type: 'GET',
  10:      url: 'api/values/1',
  11:      headers: headers
  12:  }).done(function (data) {
  13:      self.result(data);
  14:  }).fail(showError);

Until the user logs in, there is no bearer token, and therefore no Authorization header in the request. This causes the request to return a 401 error.

Here is the HTTP request. (I used Fiddler to capture the HTTP traffic.)

[!code-consoleMain]

   1:  GET https://localhost:44305/api/values HTTP/1.1
   2:  Host: localhost:44305
   3:  User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
   4:  Accept: */*
   5:  Accept-Language: en-US,en;q=0.5
   6:  X-Requested-With: XMLHttpRequest
   7:  Referer: https://localhost:44305/

HTTP response:

[!code-consoleMain]

   1:  HTTP/1.1 401 Unauthorized
   2:  Content-Type: application/json; charset=utf-8
   3:  Server: Microsoft-IIS/8.0
   4:  WWW-Authenticate: Bearer
   5:  Date: Tue, 30 Sep 2014 21:54:43 GMT
   6:  Content-Length: 61
   7:   
   8:  {"Message":"Authorization has been denied for this request."}

Notice that the response includes a Www-Authenticate header with the challenge set to Bearer. That indicates the server expects a bearer token.

Register a User

In the Register section of the app, enter an email and password, and click the Register button.

You don’t need to use a valid email address for this sample, but a real app would confirm the address. (See Create a secure ASP.NET MVC 5 web app with log in, email confirmation and password reset.) For the password, use something like “Password1!”, with an upper case letter, lower case letter, number, and non-alpha-numeric character. To keep the app simple, I left out client-side validation, so if there is a problem with the password format, you’ll get a 400 (Bad Request) error.

The Register button sends a POST request to ~/api/Account/Register/. The request body is a JSON object that holds the name and password. Here is the JavaScript code that sends the request:

[!code-javascriptMain]

   1:  var data = {
   2:      Email: self.registerEmail(),
   3:      Password: self.registerPassword(),
   4:      ConfirmPassword: self.registerPassword2()
   5:  };
   6:   
   7:  $.ajax({
   8:      type: 'POST',
   9:      url: '/api/Account/Register',
  10:      contentType: 'application/json; charset=utf-8',
  11:      data: JSON.stringify(data)
  12:  }).done(function (data) {
  13:      self.result("Done!");
  14:  }).fail(showError);

HTTP request:

[!code-consoleMain]

   1:  POST https://localhost:44305/api/Account/Register HTTP/1.1
   2:  Host: localhost:44305
   3:  User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
   4:  Accept: */*
   5:  Content-Type: application/json; charset=utf-8
   6:  X-Requested-With: XMLHttpRequest
   7:  Referer: https://localhost:44305/
   8:  Content-Length: 84
   9:   
  10:  {"Email":"alice@example.com","Password":"Password1!","ConfirmPassword":"Password1!"}

HTTP response:

[!code-consoleMain]

   1:  HTTP/1.1 200 OK
   2:  Server: Microsoft-IIS/8.0
   3:  Date: Wed, 01 Oct 2014 00:57:58 GMT
   4:  Content-Length: 0

This request is handled by the AccountController class. Internally, AccountController uses ASP.NET Identity to manage the membership database.

If you run the app locally from Visual Studio, user accounts are stored in LocalDB, in the AspNetUsers table. To view the tables in Visual Studio, click the View menu, select Server Explorer, then expand Data Connections.

Get an Access Token

So far we have not done any OAuth, but now we’ll see the OAuth authorization server in action, when we request an access token. In the Log In area of the sample app, enter the email and password and click Log In.

The Log In button sends a request to the token endpoint. The body of the request contains the following form-url-encoded data:

Here is the JavaScript code that sends the AJAX request:

[!code-javascriptMain]

   1:  var loginData = {
   2:      grant_type: 'password',
   3:      username: self.loginEmail(),
   4:      password: self.loginPassword()
   5:  };
   6:   
   7:  $.ajax({
   8:      type: 'POST',
   9:      url: '/Token',
  10:      data: loginData
  11:  }).done(function (data) {
  12:      self.user(data.userName);
  13:      // Cache the access token in session storage.
  14:      sessionStorage.setItem(tokenKey, data.access_token);
  15:  }).fail(showError);

If the request succeeds, the authorization server returns an access token in the response body. Notice that we store the token in session storage, to use later when sending requests to the API. Unlike some forms of authentication (such as cookie-based authentication), the browser will not automatically include the access token in subsequent requests. The application must do so explicitly. That’s a good thing, because it limits CSRF vulnerabilities.

HTTP request:

[!code-consoleMain]

   1:  POST https://localhost:44305/Token HTTP/1.1
   2:  Host: localhost:44305
   3:  User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
   4:  Accept: */*
   5:  Content-Type: application/x-www-form-urlencoded; charset=UTF-8
   6:  X-Requested-With: XMLHttpRequest
   7:  Referer: https://localhost:44305/
   8:  Content-Length: 68
   9:   
  10:  grant_type=password&username=alice%40example.com&password=Password1!

You can see that the request contains the user’s credentials. You must use HTTPS to provide transport layer security.

HTTP response:

[!code-consoleMain]

   1:  HTTP/1.1 200 OK
   2:  Content-Length: 669
   3:  Content-Type: application/json;charset=UTF-8
   4:  Server: Microsoft-IIS/8.0
   5:  Date: Wed, 01 Oct 2014 01:22:36 GMT
   6:   
   7:  {
   8:    "access_token":"imSXTs2OqSrGWzsFQhIXziFCO3rF...",
   9:    "token_type":"bearer",
  10:    "expires_in":1209599,
  11:    "userName":"alice@example.com",
  12:    ".issued":"Wed, 01 Oct 2014 01:22:33 GMT",
  13:    ".expires":"Wed, 15 Oct 2014 01:22:33 GMT"
  14:  }

For readability, I indented the JSON and truncated the access token, which is a quite long.

The access_token, token_type, and expires_in properties are defined by the OAuth2 spec. The other properties (userName, .issued, and .expires) are just for informational purposes. You can find the code that adds those additional properties in the TokenEndpoint method, in the /Providers/ApplicationOAuthProvider.cs file.

Send an Authenticated Request

Now that we have a bearer token, we can make an authenticated request to the API. This is done by setting the Authorization header in the request. Click the Call API button again to see this.

HTTP request:

[!code-consoleMain]

   1:  GET https://localhost:44305/api/values/1 HTTP/1.1
   2:  Host: localhost:44305
   3:  User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
   4:  Accept: */*
   5:  Authorization: Bearer imSXTs2OqSrGWzsFQhIXziFCO3rF...
   6:  X-Requested-With: XMLHttpRequest

HTTP response:

[!code-consoleMain]

   1:  HTTP/1.1 200 OK
   2:  Content-Type: application/json; charset=utf-8
   3:  Server: Microsoft-IIS/8.0
   4:  Date: Wed, 01 Oct 2014 01:41:29 GMT
   5:  Content-Length: 27
   6:   
   7:  "Hello, alice@example.com."

Log Out

Because the browser does not cache the credentials or the access token, logging out is simply a matter of “forgetting” the token, by removing it from session storage:

[!code-javascriptMain]

   1:  self.logout = function () {
   2:      sessionStorage.removeItem(tokenKey)
   3:  }

Understanding the Individual Accounts Project Template

When you select Individual Accounts in the ASP.NET Web Application project template, the project includes:

Here are the main application classes that implement these features:

Configuring the Authorization Server

In StartupAuth.cs, the following code configures the OAuth2 authorization server.

[!code-csharpMain]

   1:  PublicClientId = "self";
   2:  OAuthOptions = new OAuthAuthorizationServerOptions
   3:  {
   4:      TokenEndpointPath = new PathString("/Token"),
   5:      Provider = new ApplicationOAuthProvider(PublicClientId),
   6:      AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
   7:      AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
   8:      // Note: Remove the following line before you deploy to production:
   9:      AllowInsecureHttp = true
  10:  };
  11:   
  12:  // Enable the application to use bearer tokens to authenticate users
  13:  app.UseOAuthBearerTokens(OAuthOptions);

The TokenEndpointPath property is the URL path to the authorization server endpoint. That’s the URL that app uses to get the bearer tokens.

The Provider property specifies a provider that plugs into the OWIN middleware, and processes events raised by the middleware.

Here is the basic flow when the app wants to get a token:

  1. To get an access token, the app sends a request to ~/Token.
  2. The OAuth middleware calls GrantResourceOwnerCredentials on the provider.
  3. The provider calls the ApplicationUserManager to validate the credentials and create a claims identity.
  4. If that succeeds, the provider creates an authentication ticket, which is used to generate the token.

The OAuth middleware doesn’t know anything about the user accounts. The provider communicates between the middleware and ASP.NET Identity. For more information about implementing the authorization server, see OWIN OAuth 2.0 Authorization Server.

Configuring Web API to use Bearer Tokens

In the WebApiConfig.Register method, the following code sets up authentication for the Web API pipeline:

[!code-csharpMain]

   1:  config.SuppressDefaultHostAuthentication();
   2:  config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

The HostAuthenticationFilter class enables authentication using bearer tokens.

The SuppressDefaultHostAuthentication method tells Web API to ignore any authentication that happens before the request reaches the Web API pipeline, either by IIS or by OWIN middleware. That way, we can restrict Web API to authenticate only using bearer tokens.

[!NOTE] In particular, the MVC portion of your app might use forms authentication, which stores credentials in a cookie. Cookie-based authentication requires the use of anti-forgery tokens, to prevent CSRF attacks. That’s a problem for web APIs, because there is no convenient way for the web API to send the anti-forgery token to the client. (For more background on this issue, see Preventing CSRF Attacks in Web API.) Calling SuppressDefaultHostAuthentication ensures that Web API is not vulnerable to CSRF attacks from credentials stored in cookies.

When the client requests a protected resource, here is what happens in the Web API pipeline:

  1. The HostAuthentication filter calls the OAuth middleware to validate the token.
  2. The middleware converts the token into a claims identity.
  3. At this point, the request is authenticated but not authorized.
  4. The authorization filter examines the claims identity. If the claims authorize the user for that resource, the request is authorized. By default, the [Authorize] attribute will authorize any request that is authenticated. However, you can authorize by role or by other claims. For more information, see Authentication and Authorization in Web API.
  5. If the previous steps are successful, the controller returns the protected resource. Otherwise, the client receives a 401 (Unauthorized) error.

Additional Resources



Comments ( )
Link to this page: //www.vb-net.com/AspNet-DocAndSamples-2017/aspnet/web-api/overview/security/individual-accounts-in-web-api.htm
< THANKS ME>