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:
<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:
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:
public class ApplicationUser : IdentityUser
{
}
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.
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> ()));
manager.UserValidator = new UserValidator<ApplicationUser> (manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
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.
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
};
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
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:
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:
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.
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)
{
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.
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:
@model Levelnis.Learning.CallingWebApiFromMvc.Web.Models.RegisterViewModel
@{
ViewBag.Title = "Register";
}
<h2>Register</h2>
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.
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:
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:
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:
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
.
public abstract class ClientBase
{
protected readonly IApiClient ApiClient;
protected ClientBase(IApiClient apiClient)
{
ApiClient = apiClient;
}
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.
public class AccountController : BaseController
{
private readonly ILoginClient loginClient;
public AccountController()
{
var apiClient = new ApiClient(HttpClientInstance.Instance);
loginClient = new LoginClient(apiClient);
}
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.
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:
@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
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
:
public class AccountController : BaseController
{
private readonly ILoginClient loginClient;
private readonly ITokenContainer tokenContainer;
public AccountController()
{
tokenContainer = new TokenContainer();
var apiClient = new ApiClient(HttpClientInstance.Instance);
loginClient = new LoginClient(apiClient);
}
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);
}
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
:
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.
public interface IApiClient
{
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;
}
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.
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:
{
"grant_type": "password",
"username": "dave.test@levelnis.co.uk",
"password": "Password1!"
}
Here's the new method:
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
:
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:
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.
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.