Click here to Skip to main content
15,889,877 members
Articles / Web Development / ASP.NET

How Can You Use Web API to Authenticate Users of Your MVC Application?

Rate me:
Please Sign up or sign in to vote.
4.65/5 (11 votes)
18 Jan 2016CPOL11 min read 75K   39   10
How to use web API to authenticate usrs of your MVC application?

This is part 4 of a 4-part series on calling WebAPI from MVC code. Check out the other parts here:

We’re all familiar with forms authentication these days. It’s one of the primary methods of limiting unauthorized access to your website. You know the drill. The user sticks his/her username and password into a form. The server verifies the details and issues a cookie. But what happens if we throw Web API into the mix? What do we do if we need to restrict access to a Web API layer? Well, we could try to pass the forms authentication cookie across to the API somehow. Hang on a minute, though! New requirement just in: we also need to access the API from a mobile app. How do we provide a consistent experience?

Use Token-based Authentication

The standard way to authenticate via Web API is to use token-based authentication. We pass the username and password across in the request. If authentication is successful, the server passes a token back in the response. We then include that token in later requests. If the token is not present, the server issues a 401 unauthorized response.

So how could we implement this? Well, we could roll our own security library. There’s a lot of work involved in that though. For this example, we’re going to use the OWIN security library, which is available on Nuget. It’s also included when you create a new Web API project. More on that in a moment.

If you want to follow along, you can grab the Visual Studio solution from the previous article. If you'd rather just download the finished code, you can get it from the link below.

View the original article.

First Up, Let's Update the Api Project

We'll look at the changes we need to make on the API side first. Once they're in place, we'll switch to the Web project and update the client side of things.

Step 1: We Need a Database

Before we go any further, we need to set up a database. We'll use SQL Server Express for this example. If you don't have it, you can download it from here: SQL Server Express. Once it's installed, create a database. Let's call it CallingWebApiFromMvc. So far so good.

We also need a connection string in the Api project. We won't get far without that! Pop this into the Web.config in the Api project:

C#
<connectionStrings>
  <add name="ApiFromMvcConnection" connectionString="Data Source=(local);
Initial Catalog=CallingWebApiFromMvc;Integrated Security=True" 
providerName="System.Data.SqlClient" />
</connectionStrings>

The Identity framework creates the membership tables that we'll need for managing users automatically. There's no need to worry about creating them beforehand.

Step 2: Add Required Nuget Packages

Next up, we'll add the Nuget packages we'll need for OWIN and Windows Identity. Fire up the package manager console and switch the default project to Api. Enter the following commands:

C#
Install-Package Microsoft.AspNet.WebApi.Owin
Install-Package Microsoft.Owin.Host.SystemWeb
Install-Package Microsoft.AspNet.Identity.EntityFramework
Install-Package Microsoft.AspNet.Identity.Owin

These packages will allow us to run an OWIN server within our application and use EntityFramework to save our users to SQL Server.

Step 3: Add Identity Classes for Managing Users

We're using Entity Framework atop Windows Identity for managing the database side of things. First up, we need some classes to handle that. Add an Identity folder into the Api project so we can namespace our classes. Then add the following:

C#
public class ApplicationUser : IdentityUser 
{
}
C#
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext() : base("ApiFromMvcConnection") {}

    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }
}

Note the ApiFromMvcConnection parameter we pass to the base constructor should match the name of our connection string in Web.config.

C#
public class ApplicationUserManager : UserManager<ApplicationUser>
{
    public ApplicationUserManager(IUserStore<ApplicationUser> store) : base(store)
    {
    }

    public static ApplicationUserManager Create
    (IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
    {
        var manager = new ApplicationUserManager
        (new UserStore<ApplicationUser> (context.Get<ApplicationDbContext> ()));
        // Configure validation logic for usernames
        manager.UserValidator = new UserValidator<ApplicationUser> (manager)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = true
        };
        // Configure validation logic for passwords
        manager.PasswordValidator = new PasswordValidator
        {
            RequiredLength = 6,
            RequireNonLetterOrDigit = true,
            RequireDigit = true,
            RequireLowercase = true,
            RequireUppercase = true,
        };
        var dataProtectionProvider = options.DataProtectionProvider;
        if (dataProtectionProvider != null)
        {
            manager.UserTokenProvider = new DataProtectorTokenProvider
            <ApplicationUser> (dataProtectionProvider.Create("ASP.NET Identity"));
        }

        return manager;
    }
}

Step 4: Add OWIN Startup Class

In order for our application to run as an OWIN server, we need to initialize it when the application starts. We can do this via a Startup class. We'll decorate this class with the OwinStartup attribute, so it fires when the application starts. This also means we can get rid of Global.asax and move the Application_Start code into our new Startup class.

C#
using Microsoft.Owin;

[assembly: OwinStartup(typeof(Levelnis.Learning.CallingWebApiFromMvc.Api.Startup))]
namespace Levelnis.Learning.CallingWebApiFromMvc.Api
{
    using System;
    using System.Web.Http;
    using Identity;
    using Microsoft.Owin.Security.OAuth;
    using Owin;
    using Providers;

    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            GlobalConfiguration.Configure(WebApiConfig.Register);
            app.CreatePerOwinContext(ApplicationDbContext.Create);
            app.CreatePerOwinContext<ApplicationUserManager> (ApplicationUserManager.Create);

            var oAuthOptions = new OAuthAuthorizationServerOptions
            {
                TokenEndpointPath = new PathString("/api/token"),
                Provider = new ApplicationOAuthProvider(),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
                AllowInsecureHttp = true
            };
            // Enable the application to use bearer tokens to authenticate users
            app.UseOAuthBearerTokens(oAuthOptions);
        }
    }
}

We're building up our OWIN server when the application starts. We configure the token endpoint here and set our own custom provider, which we use to authenticate our users. In our case, we're using the ApplicationOAuthProvider class. Let's have a look at that now:

Step 5: Add OAuth Provider

C#
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
    public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        context.Validated();
        return Task.FromResult<object> (null);
    }

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        var userManager = context.OwinContext.GetUserManager<ApplicationUserManager> ();
        var user = await userManager.FindAsync(context.UserName, context.Password);
        if (user == null)
        {
            context.SetError("invalid_grant", "The user name or password is incorrect.");
            return;
        }

        var oAuthIdentity = await user.GenerateUserIdentityAsync
        (userManager, OAuthDefaults.AuthenticationType);
        var cookiesIdentity = await user.GenerateUserIdentityAsync
        (userManager, CookieAuthenticationDefaults.AuthenticationType);
        var properties = CreateProperties(user.UserName);
        var ticket = new AuthenticationTicket(oAuthIdentity, properties);
        context.Validated(ticket);
        context.Request.Context.Authentication.SignIn(cookiesIdentity);
    }

    private static AuthenticationProperties CreateProperties(string userName)
    {
        var data = new Dictionary<string, string>
        {
            { "userName", userName }
        };
        return new AuthenticationProperties(data);
    }
}

We're interested in 2 methods here. The first, ValidateClientAuthentication, just validates the client. We have a single client, so can return success. It's an asynchronous method signature but there's no async call happening. Because of this, we can leave out the async modifier, but we have to return a Task ourselves. We've added a method to the ApplicationUser called GenerateUserIdentityAsync, which looks like this:

C#
public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> 
    GenerateUserIdentityAsync(UserManager<ApplicationUser> manager, string authenticationType)
    {
        var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
        return userIdentity;
    }
}

Step 6: Register a New User - API Side

So we've got all the Identity classes in place for managing users. Let's have a look at the RegisterController, which will save new users into our database. It accepts a RegisterApiModel, which is straightforward:

C#
public class RegisterApiModel
{
    [Required]
    [EmailAddress]
    public string Email { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = 
    "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    public string Password { get; set; }

    [Required]
    [Display(Name = "Confirm Password")]
    [Compare("Password", ErrorMessage = 
    "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }
}

The controller itself just returns a 200 OK response if registration is successful. If validation fails, it returns a 401 Bad Request response.

C#
public class RegisterController : ApiController
{
    private ApplicationUserManager UserManager
    {
        get
        {
            return Request.GetOwinContext().GetUserManager<ApplicationUserManager> ();
        }
    }

    public IHttpActionResult Post(RegisterApiModel model)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        var user = new ApplicationUser
        {
            Email = model.Email,
            UserName = model.Email,
            EmailConfirmed = true
        };

        var result = UserManager.Create(user, model.Password);
        return result.Succeeded ? Ok() : GetErrorResult(result);
    }

    private IHttpActionResult GetErrorResult(IdentityResult result)
    {
        if (result == null)
        {
            return InternalServerError();
        }

        if (result.Errors != null)
        {
            foreach (var error in result.Errors)
            {
                ModelState.AddModelError("", error);
            }
        }

        if (ModelState.IsValid)
        {
            // No ModelState errors are available to send, so just return an empty BadRequest.
            return BadRequest();
        }

        return BadRequest(ModelState);
    }
}

View the original article.

Quick Recap

We've got through a lot, let's have a quick look at what we've done so far.

  • We created a database to hold our user details and added a connection string to the Api config
  • We added the Nuget packages we needed for OWIN and Windows Identity
  • We added the Identity classes for managing users
  • We added the OWIN Startup class and composed our OWIN server within it
  • We added an OAuth provider for verifying the username and password
  • We added the controller and ApiModel for registering a new user

Next Up, Let's Update the Web Project

The changes are now in place on the Api side. Let's update the Web project to call the Register and Login endpoints and get some authentication happening.

Step 1: Create the Register View and ViewModel

Our first job is to add a RegisterViewModel to the Models folder in the Web project. The ViewModel doesn't need validation because we're handling validation on the Api side.

C#
public class RegisterViewModel
{
    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }

    [DataType(DataType.Password)]
    public string Password { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm Password")]
    public string ConfirmPassword { get; set; }
}

Now create an Account folder inside the Views folder and add the following Register.cshtml view:

C#
@model Levelnis.Learning.CallingWebApiFromMvc.Web.Models.RegisterViewModel
@{
    ViewBag.Title = "Register";
}

<h2>Register</h2>
@using (Html.BeginForm("Register", "Account", 
FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()
    <h4>Create a new account.</h4>
    <hr />
    @Html.ValidationSummary("", new { @class = "text-danger" })
    <div class="form-group">
        @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.Password, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="Register" />
        </div>
    </div>
}

Step 2: Create an AccountController and Register action

Now we've got the view in place, let's create a Controller action to invoke it.

C#
public class AccountController : Controller
{
    public ActionResult Register()
    {
        var model = new RegisterViewModel();
        return View(model);
    }
}

Step 3: Create a LoginClient to Pass Registration Details to the Api

We'll add a LoginClient in a moment with a Register method. To keep things consistent, we'll return an ApiResponse back from this method. We've called the LoginClient method Register, so let's call it RegisterResponse. Add this class to the ApiInfrastructure/Responses folder:

C#
public class RegisterResponse : ApiResponse {}

The Register method will accept a ViewModel and convert it into an ApiModel to pass across. If you refer to Step 6 above, you'll recall the RegisterApiModel on the Api side. We need a model on the client side with matching properties. Let's add a RegisterApiModel to the ApiInfrastructure/ApiModels folder:

C#
public class RegisterApiModel : ApiModel
{
    public string Email { get; set; }
    public string Password { get; set; }
    public string ConfirmPassword { get; set; }
}

Notice that we haven't decorated these properties with validation attributes. We're leaving the validation to the Api.

Now for the LoginClient itself. The client inherits from ClientBase, so we need to make a couple of tweaks there to expose some of those internals to our new class. Add the class and its interface to ApiInfrastructure/Client:

C#
public interface ILoginClient
{
    Task<RegisterResponse> Register(RegisterViewModel viewModel);
}

public class LoginClient : ClientBase, ILoginClient
{
    private const string RegisterUri = "api/register";

    public LoginClient(IApiClient apiClient) : base(apiClient)
    {
    }

    public async Task<RegisterResponse> Register(RegisterViewModel viewModel)
    {
        var apiModel = new RegisterApiModel
        {
            ConfirmPassword = viewModel.ConfirmPassword,
            Email = viewModel.Email,
            Password = viewModel.Password
        };
        var response = await ApiClient.PostJsonEncodedContent(RegisterUri, apiModel);
        var registerResponse = await CreateJsonResponse<RegisterResponse> (response);
        return registerResponse;
    }
}

We just need to make a couple of changes to ClientBase. Mark the private IApiClient field and CreateJsonResponse method as protected.

C#
public abstract class ClientBase
{
    protected readonly IApiClient ApiClient;

    protected ClientBase(IApiClient apiClient)
    {
        ApiClient = apiClient;
    }

    // Other methods removed for brevity

    protected static async Task<tresponse> 
    CreateJsonResponse<tresponse>(HttpResponseMessage response) where TResponse : ApiResponse, new()
    {
        var clientResponse = new TResponse
        {
            StatusIsSuccessful = response.IsSuccessStatusCode,
            ErrorState = response.IsSuccessStatusCode ? 
            null : await DecodeContent<errorstateresponse>(response),
            ResponseCode = response.StatusCode
        };
        if (response.Content != null)
        {
            clientResponse.ResponseResult = await response.Content.ReadAsStringAsync();
        }

        return clientResponse;
    }
}

Step 4: Add a Register POST Action to the AccountController and Call the LoginClient

Let's go back to the AccountController and handle the POST action. We need to inject our ILoginClient interface so we can post the registration data across to the Api.

C#
public class AccountController : BaseController
{
    private readonly ILoginClient loginClient;

    /// <summary>
    /// Default parameterless constructor. 
    /// Delete this if you are using a DI container.
    /// </summary>
    public AccountController()
    {
        var apiClient = new ApiClient(HttpClientInstance.Instance);
        loginClient = new LoginClient(apiClient);
    }

    /// <summary>
    /// Default constructor with dependency.
    /// Delete this if you aren't planning on using a DI container.
    /// </summary>
    /// <param name="loginClient">The login client.</param>
    public AccountController(ILoginClient loginClient)
    {
        this.loginClient = loginClient;
    }

    public ActionResult Register()
    {
        var model = new RegisterViewModel();
        return View(model);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Register(RegisterViewModel model)
    {
        var response = await loginClient.Register(model);
        if (response.StatusIsSuccessful)
        {
            return RedirectToAction("Index", "Home");
        }

        AddResponseErrorsToModelState(response);
        return View(model);
    }
}

We need to mark our controller action as async because we're calling async methods all the way down to the Api boundary. We also make the controller inherit from the BaseController that we saw in the previous article to add the response errors to model state.

Step 5: Create the Login View and ViewModel

We've now sorted out registration. Let's add a LoginViewModel to the Models folder in the Web project.

C#
public class LoginViewModel
{
    public string Email { get; set; } 
    public string Password { get; set; }
    [Display(Name = "Remember Me")]
    public bool RememberMe { get; set; }
}

We also need a view. Add Login.cshtml to the Views/Account folder:

HTML
@model Levelnis.Learning.CallingWebApiFromMvc.Web.Models.LoginViewModel
@{
    ViewBag.Title = "Login";
}

<h2>Login</h2>
<div class="row">
    <div class="col-md-8">
        <section id="loginForm">
            @using (Html.BeginForm("Login", "Account", 
            FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
            {
                @Html.AntiForgeryToken()
                <h4>Use a local account to log in.</h4>
                <hr />
                @Html.ValidationSummary(true, "", new { @class = "text-danger" })
                <div class="form-group">
                    @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
                    <div class="col-md-10">
                        @Html.TextBoxFor(m => m.Email, 
                        new { @class = "form-control" })
                        @Html.ValidationMessageFor(m => m.Email, 
                        "", new { @class = "text-danger" })
                    </div>
                </div>
                <div class="form-group">
                    @Html.LabelFor(m => m.Password, 
                    new { @class = "col-md-2 control-label" })
                    <div class="col-md-10">
                        @Html.PasswordFor(m => m.Password, new { @class = "form-control" })
                        @Html.ValidationMessageFor(m => m.Password, 
                        "", new { @class = "text-danger" })
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-md-offset-2 col-md-10">
                        <div class="checkbox">
                            @Html.CheckBoxFor(m => m.RememberMe)
                            @Html.LabelFor(m => m.RememberMe)
                        </div>
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-md-offset-2 col-md-10">
                        <input type="submit" 
                        value="Log in" class="btn btn-default" />
                    </div>
                </div>
            }
        </section>
    </div>
</div>

Step 6: Add the Login Action to the Account Controller

C#
public class AccountController : BaseController
{
    public ActionResult Login()
    {
        var model = new LoginViewModel();
        return View(model);
    }
}

Step 7: Add the Controller POST Action

Now we have a way to capture the username and password. Let’s post that back to the API layer. We'll be making changes to our old friend, the ApiClient. We'll also be adding the LoginClient and a TokenContainer. The TokenContainer gives us a place to store the token after we log in. To start, let's look at the post action in the AccountController:

C#
public class AccountController : BaseController
{
    private readonly ILoginClient loginClient;
    private readonly ITokenContainer tokenContainer;

    /// <summary>
    /// Default parameterless constructor. 
    /// Delete this if you are using a DI container.
    /// </summary>
    public AccountController()
    {
        tokenContainer = new TokenContainer();
        var apiClient = new ApiClient(HttpClientInstance.Instance);
        loginClient = new LoginClient(apiClient);
    }

    /// <summary>
    /// Default constructor with dependency.
    /// Delete this if you aren't planning on using a DI container.
    /// </summary>
    /// <param name="loginClient">The login client.</param>
    /// <param name="tokenContainer">The token container.</param>
    public AccountController(ILoginClient loginClient, ITokenContainer tokenContainer)
    {
        this.loginClient = loginClient;
        this.tokenContainer = tokenContainer;
    }

    public ActionResult Login()
    {
        var model = new LoginViewModel();
        return View(model);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Login(LoginViewModel model)
    {
        var loginSuccess = await PerformLoginActions(model.Email, model.Password);
        if (loginSuccess)
        {
            return RedirectToAction("Index", "Home");
        }

        ModelState.Clear();
        ModelState.AddModelError("", "The username or password is incorrect");
        return View(model);
    }

    // Register methods go here, removed for brevity

    private async Task<bool> PerformLoginActions(string email, string password)
    {
        var response = await loginClient.Login(email, password);
        if (response.StatusIsSuccessful)
        {
            tokenContainer.ApiToken = response.Data;
        }
        else
        {
            AddResponseErrorsToModelState(response);
        }

        return response.StatusIsSuccessful;
    }
}

All we do here is make an async call to the LoginClient with the username and password. If we're successful, we store the token in the TokenContainer. If not, we add an error to ModelState.

Step 8: Add the Token Container

We'll need somewhere to store the ApiToken when we send it back from the token endpoint. Here's our TokenContainer:

C#
namespace Levelnis.Learning.CallingWebApiFromMvc.ApiHelper
{
    public interface ITokenContainer
    {
        object ApiToken { get; set; }
    }
}

namespace Levelnis.Learning.CallingWebApiFromMvc.Web.ApiInfrastructure
{
    using System.Web;
    using ApiHelper;

    public class TokenContainer : ITokenContainer
    {
        private const string ApiTokenKey = "ApiToken";

        public object ApiToken
        {
            get { return Current.Session != null ? Current.Session[ApiTokenKey] : null; }
            set { if (Current.Session != null) Current.Session[ApiTokenKey] = value; }
        }

        private static HttpContextBase Current
        {
            get { return new HttpContextWrapper(HttpContext.Current); }
        }
    }
}

As you can see from the namespaces, the ITokenContainer interface lives in the ApiHelper project. The TokenContainer implementation lives in the Web project. I did this in case you had another front-end client needing to use the Api. That client would need its own way to handle the tokens.

It's not the best idea to add the token to Session in case your application ends up running in a web farm. This is because the session data won't be shared across servers. It might be better to use a cookie instead, but I didn't manage to get that working. If anybody does, please let me know!

Step 9: Add a New Post Method to the ApiClient

To send data to the token endpoint, we need to create a new method in the ApiClient. The main reason is because we need to pass a property called grant_type across. The existing PostJsonEncodedContent doesn't quite do what we need. It'll be easier to form-encode the content and post it across.

C#
public interface IApiClient
{
    // Existing methods removed for brevity

    Task<HttpResponseMessage> PostFormEncodedContent
    (string requestUri, params KeyValuePair<string, string>[] values);
}

public class ApiClient : IApiClient
{
    private readonly HttpClient httpClient;

    public ApiClient(HttpClient httpClient)
    {
        this.httpClient = httpClient;
    }

    // Existing methods removed for brevity

    public async Task<HttpResponseMessage> PostFormEncodedContent(string requestUri, params KeyValuePair<string, string>[] values)
    {
        using (var content = new FormUrlEncodedContent(values))
        {
            var response = await httpClient.PostAsync(requestUri, content);
            return response;
        }
    }
}

Step 10: Add a Login Method to the LoginClient

The last thing to do to hook all of this up is to add a Login method to the LoginClient. It returns a TokenResponse, which will contain the token returned by the Api call.

C#
namespace Levelnis.Learning.CallingWebApiFromMvc.Web.ApiInfrastructure.Responses
{
    using ApiHelper.Response;

    public class TokenResponse : ApiResponse<string>
    {
    }
}

The client itself calls our new ApiClient.PostFormEncodedContent method. It passes a request payload that looks like this:

C#
{
    "grant_type": "password",
    "username": "dave.test@levelnis.co.uk",
    "password": "Password1!"
}

Here's the new method:

C#
public interface ILoginClient
{
    Task<TokenResponse> Login(string email, string password);
}

public class LoginClient : ClientBase, ILoginClient
{
    private const string TokenUri = "api/token";

    public LoginClient(IApiClient apiClient) : base(apiClient)
    {
    }

    public async Task<TokenResponse> Login(string email, string password)
    {
        var response = await ApiClient.PostFormEncodedContent
        (TokenUri, "grant_type".AsPair("password"),
            "username".AsPair(email), "password".AsPair(password));
        var tokenResponse = await CreateJsonResponse<TokenResponse> (response);
        if (!response.IsSuccessStatusCode)
        {
            var errorContent = await DecodeContent<dynamic> (response);
            tokenResponse.ErrorState = new ErrorStateResponse
            {
                ModelState = new Dictionary<string, string[]>
                {
                    {errorContent["error"], 
                    new string[] {errorContent["error_description"]}}
                }
            };
            return tokenResponse;
        }

        var tokenData = await DecodeContent<dynamic> (response);
        tokenResponse.Data = tokenData["access_token"];
        return tokenResponse;
    }
}

We now need to access DecodeContent from ClientBase, so make that protected as well.

Step 11: Pass the token across when accessing secure endpoints

We need to pass the token across to the Api if we're accessing secure endpoints. We'll change the ApiClient to pass the token across in the authorization header for each request. That way, any endpoints with marked with the Authorize attribute will return a 401 Bad Request if we don't include the token. Here's the complete ApiClient:

C#
namespace Levelnis.Learning.CallingWebApiFromMvc.ApiHelper.Client
{
    using System.Collections.Generic;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Threading.Tasks;
    using Model;

    public class ApiClient : IApiClient
    {
        private readonly HttpClient httpClient;
        private readonly ITokenContainer tokenContainer;

        public ApiClient(HttpClient httpClient, ITokenContainer tokenContainer)
        {
            this.httpClient = httpClient;
            this.tokenContainer = tokenContainer;
        }

        public async Task<HttpResponseMessage> 
        GetFormEncodedContent(string requestUri, params KeyValuePair<string, string>[] values)
        {
            AddToken();
            using (var content = new FormUrlEncodedContent(values))
            {
                var query = await content.ReadAsStringAsync();
                var requestUriWithQuery = string.Concat(requestUri, "?", query);
                var response = await httpClient.GetAsync(requestUriWithQuery);
                return response;
            }
        }

        public async Task<HttpResponseMessage> 
        PostFormEncodedContent(string requestUri, params KeyValuePair<string, string>[] values)
        {
            using (var content = new FormUrlEncodedContent(values))
            {
                var response = await httpClient.PostAsync(requestUri, content);
                return response;
            }
        }

        public async Task<HttpResponseMessage> 
        PostJsonEncodedContent<T> (string requestUri, T content) where T : ApiModel
        {
            httpClient.DefaultRequestHeaders.Accept.Clear();
            httpClient.DefaultRequestHeaders.Accept.Add
            (new MediaTypeWithQualityHeaderValue("application/json"));
            AddToken();
            var response = await httpClient.PostAsJsonAsync(requestUri, content);
            return response;
        }

        private void AddToken()
        {
            if (tokenContainer.ApiToken != null)
            {
                httpClient.DefaultRequestHeaders.Authorization = 
                new AuthenticationHeaderValue("Bearer", tokenContainer.ApiToken.ToString());
            }
        }
    }
}

Step 12: Control Access via an Authentication Attribute

This is the final step. Almost there! We've got the token on the client now. We need to check it whenever we access secure pages on the client. We need to create a named route in RouteConfig for the login route:

C#
public static class RouteConfig
{
    public const string LoginRouteName = "LogIn";

    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(LoginRouteName, "log-in", 
        new {controller = "Account", Action = "LogIn"});

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", 
            action = "Index", id = UrlParameter.Optional }
        );
    }
}

Notice that we add the route name as a constant. We'll refer to that value in the attribute. This just saves us having to use magic strings everywhere. Next up is the attribute itself.

C#
namespace Levelnis.Learning.CallingWebApiFromMvc.Web.Attributes
{
    using System.Web.Mvc;
    using ApiHelper;
    using ApiInfrastructure;

    public class AuthenticationAttribute : ActionFilterAttribute
    {
        private readonly ITokenContainer tokenContainer;

        public AuthenticationAttribute()
        {
            tokenContainer = new TokenContainer();
        }

        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            if (tokenContainer.ApiToken == null)
            {
                filterContext.HttpContext.Response.RedirectToRoute(RouteConfig.LoginRouteName);
            }
        }
    }
}

All we do here is take in the TokenContainer and check whether the token is present. If not, we redirect to the login route. Now that we have an attribute, let's test it. Add it to the ProductController to restrict access to those action methods. Now if you fire up the application and click the Create Product button, you get bumped to the login screen.

Wrapping Up

We've covered an awful lot in this article. Thanks for staying with me! Let's recap the Web changes:

  • We created a register form to pass the username and password details across to the Api when registering
  • We created a LoginClient to translate the view data into Api data before sending it across to the Api
  • We created a login form to pass the username and password details across to the Api when logging in
  • We created a TokenContainer to store the ApiToken we get back from the token endpoint
  • We added a new POST method to the ApiClient to handle data that we couldn't deserialize from JSON automatically
  • We added a Login method to the LoginClient for sending the login data across to the Api
  • We added the token to the Authorization header in all requests to the Api
  • We created an AuthenticationAttribute to control access to secure areas of the client application

View the original article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead Levelnis Ltd
United Kingdom United Kingdom
Follow along my journey as I create a newsletter about launching websites. Just message me with "I'm in" and I'll add you

Comments and Discussions

 
QuestionCould you please share the source project Pin
Member 150938738-Mar-21 20:06
Member 150938738-Mar-21 20:06 
AnswerRe: Could you please share the source project Pin
OriginalGriff8-Mar-21 20:07
mveOriginalGriff8-Mar-21 20:07 
QuestionSourceCode Please Pin
Member 1491998516-Feb-21 3:57
Member 1491998516-Feb-21 3:57 
Questionrelease build error Pin
k3nn200328-Jul-20 21:45
k3nn200328-Jul-20 21:45 
QuestionHow to use cookies.... Pin
k3nn200328-Jul-20 21:10
k3nn200328-Jul-20 21:10 
QuestionHow Can You Use Web API to Authenticate Users of Your MVC Application Pin
Love Hammad26-Apr-20 19:51
Love Hammad26-Apr-20 19:51 
QuestionLogout Option Pin
Love Hammad9-Jan-19 18:40
Love Hammad9-Jan-19 18:40 
QuestionHow can I implement the Rolemanagement Pin
Member 987585028-Feb-18 6:29
Member 987585028-Feb-18 6:29 
PraiseExcellent Article Pin
sumit kumar parakh29-Dec-17 3:59
sumit kumar parakh29-Dec-17 3:59 
QuestionOAuth 1.0a? Pin
Lechuss27-Feb-17 5:15
Lechuss27-Feb-17 5:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.