Table of Contents
- Table of Contents
- Introduction
- Roadmap
- Security in WebAPI
- Authentication
- Authorization
- Maintaining Session
- Basic Authentication
- Pros and Cons of Basic Authentication
- Token Based Authorization
- WebAPI with Basic Authentication and Token Based Authorization
- Creating User Service
- UserServices
- Resolve dependency of UserService
- Implementing Basic Authentication
- Step 1: Create generic Authentication Filter
- Step 2: Create Basic Authentication Identity
- Step 3: Create a Custom Authentication Filter
- Step 4: Basic Authentication on Controller
- Running the application
- Design discrepancy
- Implementing Token based Authorization
- Set Database
- Set Business Services
- Setup WebAPI/Controller
- AuthenticateController:
- Setup Authorization Action Filter
- Mark Controllers with Authorization filter
- Maintaining Session using Token
- Running the application
- Test Authentication
- Test Authorization
- Conclusion
- References
- Other Series
Introduction
Security has always been a major concern for enterprise-level applications, especially when exposing our business through services. I have already explained a lot on WebAPI in my earlier articles of the series. I explained how to create a WebAPI, how to resolve dependencies to make it a loosely coupled design, defining custom routes and the use of attribute routing. This article will explain how to do security in a WebAPI. This article will explain how to make WebAPI secure using Basic Authentication and Token based authorization. I'll also explain how to leverage token-based authorization and Basic Authentication in WebAPI to maintain sessions in WebAPI. There is no standard way of achieving security in WebAPI. We can design our own security technique and structure that suits our application best.
Roadmap
The following is the roadmap I have setup to learn WebAPI step-by-step:
I'll intentionally use Visual Studio 2010 and .NET Framework 4.0 because there are a few implementations that are very hard to find in .NET Framework 4.0, but I'll make it easy by showing how to do it.
Security in WebAPISecurity in itself is a very complicated and tricky topic. I'll try to explain how to do it in WebAPI in my own way. When we plan to create an enterprise-level application, we especially want to take care of authentication and authorization. These are two techniques that, if used well, makes our application secure, in our case makes our WebAPI more secure.
Image credit:
pixabay.
AuthenticationAuthentication is all about the identity of the end user. It's about validating the identity of a user who is accessing our system, that he is authenticated enough to use our resources or not. Does that end user have valid credentials to log into our system? Credentials can be in the form of a user name and word. We'll use the Basic Authentication technique to understand how to do authentication in WebAPI.
AuthorizationAuthorization should be considered as a second step after authentication to do security. Authorization means what all the permissions are that the authenticated user must have to access web resources. Are they allowed to access/perform an action on that resource? This could be done by setting roles and permissions for an end-user who is authenticated, or can be done by providing a secure token, using which an end user can have access to other services or resources.
Maintaining SessionRESTful services work on a stateless protocol, in other words HTTP. We can maintain sessions in the Web API using token-based authorization techniques. An authenticated user will be allowed to access resources for a specific period of time and can re-instantiate the request with an increased session time delta to access other resource or the same resource. Websites using WebAPIs as RESTful services may need to implement login/logout for a user, to maintain sessions for the user, to provide roles and permissions to their user, all these features could be done using basic authentication and token-based authorization. I'll explain this step-by-step.
Basic AuthenticationBasic authentication is a mechanism, where an end user is authenticated using our service, in other words RESTful service, using plain credentials such as user name and word. An end user makes a request to the service for authentication with the user name and word embedded in the request header. The service receives the request and checks if the credentials are valid or not and returns the response accordingly, in case of invalid credentials, the service responds with a 401 error code, in other words unauthorized. The actual credentials by which the comparison is done may lie in the database, any config file like web.config or in the code itself.
Pros and Cons of Basic AuthenticationBasic authentication has its own pros and cons. It is advantageous for implementation, it is very easy to implement, it is supported by nearly all the modern browsers and has become an authentication standard in RESTful / Web APIs. It has the disadvantages of sending user credentials in plain text, sending user credentials inside a request header, in other words prone to hack. One must send credentials each time a service is called. No session is maintained and a user cannot logout once logged in using basic authentication. It is very prone to Cross-Site Request Forgery (CSRF).
Token Based AuthorizationThe authorization part comes just after authentication. Once authenticated, a service can send a token to an end user by which the user can access other resources. The token could be any encrypted key that only the server/service understands and when it fetches the token from the request made by the end user, it validates the token and authorizes the user into the system. The token generated could be stored in a database or an external file as well, in other words we need to persist the token for future references. The token can have its own lifetime and may expire accordingly. In that case the user will need to be authenticated again into the system.
WebAPI with Basic Authentication and Token Based AuthorizationCreating User ServiceJust open your WebAPI project or the WebAPI project that we discussed in the last part of learning WebAPI.
We have BusinessEntities, BusinessServices, DataModel, DependencyResolver and a WebAPI project as well. We already have a User table in the database, or you can create your own database with a table like User Table as shown below.
I am using a WebAPI database, I have attached the scripts for download.
UserServicesGo to BusinessServices project and add a new interface, IUserService, and a service named UserServices implementing that interface as in the following:
Just define one method named Authenticate in the interface.
- namespace BusinessServices
- {
- public interface IUserServices
- {
- int Authenticate(string userName, string word);
- }
- }
This method takes username and word as a parameter and returns the specific userId if the user is authenticated successfully.
Just implement this method in the UserServices class, just like we created services earlier in the series.
- using DataModel.UnitOfWork;
-
- namespace BusinessServices {
-
-
-
- public class UserServices: IUserServices {
- private readonly UnitOfWork _unitOfWork;
-
-
-
-
- public UserServices(UnitOfWork unitOfWork) {
- _unitOfWork = unitOfWork;
- }
-
-
-
-
-
-
-
- public int Authenticate(string userName, string word) {
- var user = _unitOfWork.UserRepository.Get(u = > u.UserName == userName && u.word == word);
- if (user != null && user.UserId > 0) {
- return user.UserId;
- }
- return 0;
- }
- }
- }
You can clearly see that Authenticate method just checks the user credentials from UserRepository and returns the values accordingly. The code is very self-explanatory.
Resolve dependency of UserServiceJust open the DependencyResolver class in the BusinessServices project itself and add its dependency type so that we get the UserServices dependency resolved at run time, so add,
- registerComponent.RegisterType<IUserServices, UserServices>();
line to SetUP method. Our class becomes:
- using System.ComponentModel.Composition;
- using DataModel;
- using DataModel.UnitOfWork;
- using Resolver;
-
- namespace BusinessServices {
- [Export(typeof(IComponent))]
- public class DependencyResolver: IComponent {
- public void SetUp(IRegisterComponent registerComponent) {
- registerComponent.RegisterType < IProductServices, ProductServices > ();
- registerComponent.RegisterType < IUserServices, UserServices > ();
- }
- }
- }
Implementing Basic AuthenticationStep 1: Create a generic Authentication FilterAdd a folder named Filters to the WebAPI project and add a class named
GenericAuthenticationFilter under that folder. Derive that class from
AuthorizationFilterAttribute, this is a class under System.Web.Http.Filters.
I have created the generic authentication filter that will be like:
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
- public class GenericAuthenticationFilter: AuthorizationFilterAttribute {
-
-
-
-
- public GenericAuthenticationFilter() {}
-
- private readonly bool _isActive = true;
-
-
-
-
-
- public GenericAuthenticationFilter(bool isActive) {
- _isActive = isActive;
- }
-
-
-
-
-
- public override void OnAuthorization(HttpActionContext filterContext) {
- if (!_isActive) return;
- var identity = FetchAuthHeader(filterContext);
- if (identity == null) {
- ChallengeAuthRequest(filterContext);
- return;
- }
- var genericPrincipal = new GenericPrincipal(identity, null);
- Thread.CurrentPrincipal = genericPrincipal;
- if (!OnAuthorizeUser(identity.Name, identity.word, filterContext)) {
- ChallengeAuthRequest(filterContext);
- return;
- }
- base.OnAuthorization(filterContext);
- }
-
-
-
-
-
-
-
-
- protected virtual bool OnAuthorizeUser(string user, string , HttpActionContext filterContext) {
- if (string.IsNullOrEmpty(user) || string.IsNullOrEmpty()) return false;
- return true;
- }
-
-
-
-
-
- protected virtual BasicAuthenticationIdentity FetchAuthHeader(HttpActionContext filterContext) {
- string authHeaderValue = null;
- var authRequest = filterContext.Request.Headers.Authorization;
- if (authRequest != null && !String.IsNullOrEmpty(authRequest.Scheme) && authRequest.Scheme == "Basic") authHeaderValue = authRequest.Parameter;
- if (string.IsNullOrEmpty(authHeaderValue)) return null;
- authHeaderValue = Encoding.Default.GetString(Convert.FromBase64String(authHeaderValue));
- var credentials = authHeaderValue.Split(':');
- return credentials.Length < 2 ? null : new BasicAuthenticationIdentity(credentials[0], credentials[1]);
- }
-
-
-
-
-
-
- private static void ChallengeAuthRequest(HttpActionContext filterContext) {
- var dnsHost = filterContext.Request.RequestUri.DnsSafeHost;
- filterContext.Response = filterContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
- filterContext.Response.Headers.Add("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", dnsHost));
- }
- }
Since this is an AuthorizationFilter derived class, we need to override its methods to add our custom logic. Here the “OnAuthorization” method is overridden to add a custom logic. Whenever we get an ActionContext on OnAuthorization, we'll check for its header, since we are pushing our service to use BasicAuthentication, the request headers should contain this information. I have used FetchAuthHeader to check the scheme, if it comes to be “Basic” and thereafter stores the credentials, in other words user name and word, in a form of an object of class BasicAuthenticationIdentity, therefore creating an identity out of valid credentials.
- protected virtual BasicAuthenticationIdentity FetchAuthHeader(HttpActionContext filterContext) {
- string authHeaderValue = null;
- var authRequest = filterContext.Request.Headers.Authorization;
- if (authRequest != null && !String.IsNullOrEmpty(authRequest.Scheme) && authRequest.Scheme == "Basic") authHeaderValue = authRequest.Parameter;
- if (string.IsNullOrEmpty(authHeaderValue)) return null;
- authHeaderValue = Encoding.Default.GetString(Convert.FromBase64String(authHeaderValue));
- var credentials = authHeaderValue.Split(':');
- return credentials.Length < 2 ? null : new BasicAuthenticationIdentity(credentials[0], credentials[1]);
- }
I am expecting values to be encrypted using Base64 string. You can use your own encryption mechanism as well.
Later on in the OnAuthorization method we create a genericPrincipal with the created identity and assign it to the current Thread principal as in the following:
- var genericPrincipal = new GenericPrincipal(identity, null);
- Thread.CurrentPrincipal = genericPrincipal;
- if (!OnAuthorizeUser(identity.Name, identity.word, filterContext))
- {
- ChallengeAuthRequest(filterContext);
- return;
- }
- base.OnAuthorization(filterContext);
Once done, a challenge to that request is added, where we add a response and tell the Basic realm:
filterContext.Response.Headers.Add("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", dnsHost));
in ChallengeAuthRequest method.
If no credentials is provided in the request, this generic authentication filter sets the generic authentication principal to the current thread principal.
Since we understand the drawback that in basic authentication credentials are ed in plain text, it would be good if our service uses SSL for communication or message ing.
We have an overridden constructor as well that allows the default behavior of the filter to be stopped by just ing in a parameter, in other words true or false.
- public GenericAuthenticationFilter(bool isActive)
- {
- _isActive = isActive;
- }
We can use OnAuthorizeUser for custom authorization purposes.
Step 2: Create a Basic Authentication IdentityBefore we proceed further, we also need the BasicIdentity class, that takes credentials and assigns them to the Generic Principal. So just add one more class named
BasicAuthenticationIdentity deriving from
GenericIdentity.
This class contains the three properties UserName, word and UserId. I intentionally added UserId because we'll need that in the future. So our class will be like:
- using System.Security.Principal;
-
- namespace WebAPI.Filters {
-
-
-
- public class BasicAuthenticationIdentity: GenericIdentity {
-
-
-
- public string word {
- get;
- set;
- }
-
-
-
- public string UserName {
- get;
- set;
- }
-
-
-
- public int UserId {
- get;
- set;
- }
-
-
-
-
-
-
- public BasicAuthenticationIdentity(string userName, string word): base(userName, "Basic") {
- word = word;
- UserName = userName;
- }
- }
- }
Step 3: Create a Custom Authentication FilterNow you are ready to use your own Custom Authentication filter. Just add one more class under that Filters project and call it
APIAuthenticationFilter, this class will derive from
GenericAuthenticationFilter, that we created in the first step.This class overrides the OnAuthorizeUser method to add custom logic for authenticating a request. It uses UserService that we created earlier to check the user.
- protected override bool OnAuthorizeUser(string username, string word, HttpActionContext actionContext) {
- var provider = actionContext.ControllerContext.Configuration.DependencyResolver.GetService(typeof(IUserServices)) as IUserServices;
- if (provider != null) {
- var userId = provider.Authenticate(username, word);
- if (userId > 0) {
- var basicAuthenticationIdentity = Thread.CurrentPrincipal.Identity as BasicAuthenticationIdentity;
- if (basicAuthenticationIdentity != null) basicAuthenticationIdentity.UserId = userId;
- return true;
- }
- }
- return false;
- }
Complete class
- using System.Threading;
- using System.Web.Http.Controllers;
- using BusinessServices;
-
- namespace WebAPI.Filters {
-
-
-
- public class APIAuthenticationFilter: GenericAuthenticationFilter {
-
-
-
- public APIAuthenticationFilter() {}
-
-
-
-
-
- public APIAuthenticationFilter(bool isActive): base(isActive) {}
-
-
-
-
-
-
-
-
- protected override bool OnAuthorizeUser(string username, string word, HttpActionContext actionContext) {
- var provider = actionContext.ControllerContext.Configuration.DependencyResolver.GetService(typeof(IUserServices)) as IUserServices;
- if (provider != null) {
- var userId = provider.Authenticate(username, word);
- if (userId > 0) {
- var basicAuthenticationIdentity = Thread.CurrentPrincipal.Identity as BasicAuthenticationIdentity;
- if (basicAuthenticationIdentity != null) basicAuthenticationIdentity.UserId = userId;
- return true;
- }
- }
- return false;
- }
- }
- }
Step 4: Basic Authentication on the ControllerSince we already have our products controller as in the following:
- public class ProductController: APIController {#region Private variable.
-
- private readonly IProductServices _productServices;
-
- #endregion
-
- #region Public Constructor
-
-
-
-
- public ProductController(IProductServices productServices) {
- _productServices = productServices;
- }
-
- #endregion
-
-
- [GET("allproducts")]
- [GET("all")]
- public HttpResponseMessage Get() {
- var products = _productServices.GetAllProducts();
- var productEntities = products as List < ProductEntity > ? ? products.ToList();
- if (productEntities.Any()) return Request.CreateResponse(HttpStatusCode.OK, productEntities);
- return Request.CreateErrorResponse(HttpStatusCode.NotFound, "Products not found");
- }
-
-
- [GET("productid/{id?}")]
- [GET("particularproduct/{id?}")]
- [GET("myproduct/{id:range(1, 3)}")]
- public HttpResponseMessage Get(int id) {
- var product = _productServices.GetProductById(id);
- if (product != null) return Request.CreateResponse(HttpStatusCode.OK, product);
- return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No product found for this id");
- }
-
-
- [POST("Create")]
- [POST("Register")]
- public int Post([FromBody] ProductEntity productEntity) {
- return _productServices.CreateProduct(productEntity);
- }
-
-
- [PUT("Update/productid/{id}")]
- [PUT("Modify/productid/{id}")]
- public bool Put(int id, [FromBody] ProductEntity productEntity) {
- if (id > 0) {
- return _productServices.UpdateProduct(id, productEntity);
- }
- return false;
- }
-
-
- [DELETE("remove/productid/{id}")]
- [DELETE("clear/productid/{id}")]
- [PUT("delete/productid/{id}")]
- public bool Delete(int id) {
- if (id > 0) return _productServices.DeleteProduct(id);
- return false;
- }
- }
There are three ways in which you can use this authentication filter.
Just apply this filer to ProductController. You can add this filter at the top of the controller, for all API requests to be validated as in the following:
- [APIAuthenticationFilter]
- [RoutePrefix("v1/Products/Product")]
- public class ProductController : APIController
You can also globally add this in Web API configuration file, so that that filter applies to all the controllers and all the actions associated with it.
GlobalConfiguration.Configuration.Filters.Add(new APIAuthenticationFilter());
You can also apply it to the Action level too if you wish to apply or not apply authentication to that action.
-
- [APIAuthenticationFilter(true)]
- [GET("allproducts")]
- [GET("all")]
- public HttpResponseMessage Get()
- {…………………
- }
-
-
- [APIAuthenticationFilter(false)]
- [GET("productid/{id?}")]
- [GET("particularproduct/{id?}")]
- [GET("myproduct/{id:range(1, 3)}")]
- public HttpResponseMessage Get(int id)
- {……………………..
- }
Running the applicationWe have already implemented Basic Authentication, just try to run the application to test if it is working.
Just run the application, we get:
We already have our test client added, but for new readers, just go to Manage Nuget Packages, by right-clicking WebAPI project and type WebAPITestClient into the searchbox in online packages as in the following:
You'll get “A simple Test Client for ASP.NET Web API”, just add it. You'll get a help controller in Areas -> HelpPage as shown below.
I have already provided the database scripts and data in my previous article, you can use that.
Append “/help” to the application URL and you'll get the test client.
GETPOSTPUTDELETEYou can test each service by clicking on it. Once you click on the service link, you'll be redirected to test the service page of that specific service. On that page there is a button Test API in the right bottom corner, just press that button to test your service.
Service to Get All the products.
When you click on the Send request, a dialog will be shown asking for Authentication. Just cancel that dialog and let the request go without credentials. You'll get a response of Unauthorized, in other words 401.
This means our authentication mechanism is working.
Just to double check it, let's send the request with credentials now. Just add a header too with the request. The header should be like:
Authorization: Basic YWtoaWw6YWtoaWw=Here “
YWtoaWw6YWtoaWw=” is my Base64 encoded user name and word, in other words akhil:akhil.
Click on Send and we get the response as desired.
Likewise you can test all the service endpoints.
This means our service is working with Basic Authentication.
Design discrepancyThis design, when running on SSL, is very good for implementing Basic Authentication. But there are a few scenarios in which, along with Basic Authentication, I would like to leverage authorization too and not even authorization but sessions too. When we talk about creating an enterprise application, it just do not limit things to securing our endpoints with authentication only.
In this design, I'll need to send a user name and word each time with every request. Assume I want to create an application where authentication only occurs only once as my login is done and after successfully authentication, in other words logging in, I must be able to use other services of that application. In other words I am then authorized to use those services. Our application should be robust enough that it restricts even authenticated users from using other services he is not authorized for. Yes, I am talking about Token-based authorization.
I'll expose only one endpoint for authentication and that will be my login service. So the client only knows about that login service that needs credentials to get logged into the system.
After the client successfully logs in I'll send a token that could be a GUID or an encrypted key by any xyz algorithm that I want when the user makes a request for any other services after login, should provide this token along with that request. And to maintain sessions, our token will have an expiry too, that will last for 15 minutes, that can be made configurable using a web.config file. After the session expires, the user will be logged out and will again need to use the login service with credentials to get a new token. Seems exciting to me, let's implement this.
Implementing Token based AuthorizationTo overcome the preceding scenarios, let's start developing and give our application the shape of a thick client enterprise architecture.
Set DatabaseLet's start with setting up a database. When we see our previously created database that we had set up in the
first part of the series, we have a token table. We require this token table for token persistence. Our token will persist in the database with an expiry time. If you are using your own database, you can create a token table as in the following:
Set Business ServicesJust navigate to BusinessServices and create one more Interface named ITokenServices for the token-based operations as in the following:
- using BusinessEntities;
-
- namespace BusinessServices {
- public interface ITokenServices {#region Interface member methods.
-
-
-
-
-
-
- TokenEntity GenerateToken(int userId);
-
-
-
-
-
-
- bool ValidateToken(string tokenId);
-
-
-
-
-
- bool Kill(string tokenId);
-
-
-
-
-
-
- bool DeleteByUserId(int userId);#endregion
- }
- }
We have four methods defined in this interface. Let's create a TokenServices class that implements ITokenServices and understand each method.
The GenerateToken method takes a userId as a parameter and generates a token, encapsulates that token in a token entity with a Token expiry time and returns it to the caller.
- public TokenEntity GenerateToken(int userId) {
- string token = Guid.NewGuid().ToString();
- DateTime issuedOn = DateTime.Now;
- DateTime expiredOn = DateTime.Now.AddSeconds(
- Convert.ToDouble(ConfigurationManager.AppSettings["AuthTokenExpiry"]));
- var tokendomain = new Token {
- UserId = userId,
- AuthToken = token,
- IssuedOn = issuedOn,
- ExpiresOn = expiredOn
- };
-
- _unitOfWork.TokenRepository.Insert(tokendomain);
- _unitOfWork.Save();
- var tokenModel = new TokenEntity() {
- UserId = userId,
- IssuedOn = issuedOn,
- ExpiresOn = expiredOn,
- AuthToken = token
- };
-
- return tokenModel;
- }
When generating a token, it names a database entry into the Token table.
The ValidateToken method just validates that the token associated with the request is valid or not, in other words it exists in the database within its expiry time limit.
- public bool ValidateToken(string tokenId) {
- var token = _unitOfWork.TokenRepository.Get(t = > t.AuthToken == tokenId && t.ExpiresOn > DateTime.Now);
- if (token != null && !(DateTime.Now > token.ExpiresOn)) {
- token.ExpiresOn = token.ExpiresOn.AddSeconds(
- Convert.ToDouble(ConfigurationManager.AppSettings["AuthTokenExpiry"]));
- _unitOfWork.TokenRepository.Update(token);
- _unitOfWork.Save();
- return true;
- }
- return false;
- }
It just takes the token Id supplied in the request.
Kill Token just kills the token, in other words removes the token from the database.
- public bool Kill(string tokenId) {
- _unitOfWork.TokenRepository.Delete(x = > x.AuthToken == tokenId);
- _unitOfWork.Save();
- var isNotDeleted = _unitOfWork.TokenRepository.GetMany(x = > x.AuthToken == tokenId).Any();
- if (isNotDeleted) {
- return false;
- }
- return true;
- }
The DeleteByUserId method deletes all the token entries from the database w.r.t specific userId associated with those tokens.
- public bool DeleteByUserId(int userId)
- {
- _unitOfWork.TokenRepository.Delete(x => x.UserId == userId);
- _unitOfWork.Save();
-
- var isNotDeleted = _unitOfWork.TokenRepository.GetMany(x => x.UserId == userId).Any();
- return !isNotDeleted;
- }
So with _unitOfWork and along with Constructor our class becomes:
- using System;
- using System.Configuration;
- using System.Linq;
- using BusinessEntities;
- using DataModel;
- using DataModel.UnitOfWork;
-
- namespace BusinessServices {
- public class TokenServices: ITokenServices {#region Private member variables.
- private readonly UnitOfWork _unitOfWork;#endregion
-
- #region Public constructor.
-
-
-
- public TokenServices(UnitOfWork unitOfWork) {
- _unitOfWork = unitOfWork;
- }#endregion
-
-
- #region Public member methods.
-
-
-
-
-
-
-
- public TokenEntity GenerateToken(int userId) {
- string token = Guid.NewGuid().ToString();
- DateTime issuedOn = DateTime.Now;
- DateTime expiredOn = DateTime.Now.AddSeconds(
- Convert.ToDouble(ConfigurationManager.AppSettings["AuthTokenExpiry"]));
- var tokendomain = new Token {
- UserId = userId,
- AuthToken = token,
- IssuedOn = issuedOn,
- ExpiresOn = expiredOn
- };
-
- _unitOfWork.TokenRepository.Insert(tokendomain);
- _unitOfWork.Save();
- var tokenModel = new TokenEntity() {
- UserId = userId,
- IssuedOn = issuedOn,
- ExpiresOn = expiredOn,
- AuthToken = token
- };
-
- return tokenModel;
- }
-
-
-
-
-
-
- public bool ValidateToken(string tokenId) {
- var token = _unitOfWork.TokenRepository.Get(t = > t.AuthToken == tokenId && t.ExpiresOn > DateTime.Now);
- if (token != null && !(DateTime.Now > token.ExpiresOn)) {
- token.ExpiresOn = token.ExpiresOn.AddSeconds(
- Convert.ToDouble(ConfigurationManager.AppSettings["AuthTokenExpiry"]));
- _unitOfWork.TokenRepository.Update(token);
- _unitOfWork.Save();
- return true;
- }
- return false;
- }
-
-
-
-
-
- public bool Kill(string tokenId) {
- _unitOfWork.TokenRepository.Delete(x = > x.AuthToken == tokenId);
- _unitOfWork.Save();
- var isNotDeleted = _unitOfWork.TokenRepository.GetMany(x = > x.AuthToken == tokenId).Any();
- if (isNotDeleted) {
- return false;
- }
- return true;
- }
-
-
-
-
-
-
- public bool DeleteByUserId(int userId) {
- _unitOfWork.TokenRepository.Delete(x = > x.UserId == userId);
- _unitOfWork.Save();
-
- var isNotDeleted = _unitOfWork.TokenRepository.GetMany(x = > x.UserId == userId).Any();
- return !isNotDeleted;
- }
-
- #endregion
- }
- }
Do not forget to resolve the dependency of this Token service in the DependencyResolver class. Add registerComponent.RegisterType<
ITokenServices, TokenServices>(); to the SetUp method of the DependencyResolver class in the BusinessServices project.
- [Export(typeof(IComponent))]
- public class DependencyResolver: IComponent {
- public void SetUp(IRegisterComponent registerComponent) {
- registerComponent.RegisterType < IProductServices, ProductServices > ();
- registerComponent.RegisterType < IUserServices, UserServices > ();
- registerComponent.RegisterType < ITokenServices, TokenServices > ();
-
- }
- }
Setup WebAPI/ControllerNow since we decided that we don't want authentication to be applied on each and every API exposed, I'll create a single Controller/API endpoint that takes authentication or a login request and uses a Token Service to generate a token and respond client/caller with a token that persists in the database with expiry details.
Add a new Controller under the Controllers folder in the WebAPI with the name Authenticate as in the following:
AuthenticateController
- using System.Configuration;
- using System.Net;
- using System.Net.Http;
- using System.Web.Http;
- using AttributeRouting.Web.Http;
- using BusinessServices;
- using WebAPI.Filters;
-
- namespace WebAPI.Controllers {
- [APIAuthenticationFilter]
- public class AuthenticateController: APIController {#region Private variable.
-
- private readonly ITokenServices _tokenServices;
-
- #endregion
-
- #region Public Constructor
-
-
-
-
- public AuthenticateController(ITokenServices tokenServices) {
- _tokenServices = tokenServices;
- }
-
- #endregion
-
-
-
-
-
- [POST("login")]
- [POST("authenticate")]
- [POST("get/token")]
- public HttpResponseMessage Authenticate() {
- if (System.Threading.Thread.CurrentPrincipal != null && System.Threading.Thread.CurrentPrincipal.Identity.IsAuthenticated) {
- var basicAuthenticationIdentity = System.Threading.Thread.CurrentPrincipal.Identity as BasicAuthenticationIdentity;
- if (basicAuthenticationIdentity != null) {
- var userId = basicAuthenticationIdentity.UserId;
- return GetAuthToken(userId);
- }
- }
- return null;
- }
-
-
-
-
-
-
- private HttpResponseMessage GetAuthToken(int userId) {
- var token = _tokenServices.GenerateToken(userId);
- var response = Request.CreateResponse(HttpStatusCode.OK, "Authorized");
- response.Headers.Add("Token", token.AuthToken);
- response.Headers.Add("TokenExpiry", ConfigurationManager.AppSettings["AuthTokenExpiry"]);
- response.Headers.Add("Access-Control-Expose-Headers", "Token,TokenExpiry");
- return response;
- }
- }
- }
The controller is decorated with our authentication filter as in the following:
- [APIAuthenticationFilter]
- public class AuthenticateController : APIController
So, each and every request coming through this controller will need to through this authentication filter that checks for a BasicAuthentication header and credentials. The Authentication filter sets the CurrentThread principal to the authenticated Identity.
There is a single Authenticate method / action in this controller. You can decorate it with multiple endpoints as was explained in the fourth part of this series.
- [POST("login")]
- [POST("authenticate")]
- [POST("get/token")]
The Authenticate method first checks for CurrentThreadPrincipal and if the user is authenticated or not, in other words the job is done by the authentication filter.
if (System.Threading.Thread.CurrentPrincipal!=null && System.Threading.Thread.CurrentPrincipal.Identity.IsAuthenticated)
When it finds that the user is authenticated, it generates an auth token using TokenServices and returns a user with Token and its expiry.
- response.Headers.Add("Token", token.AuthToken);
- response.Headers.Add("TokenExpiry", ConfigurationManager.AppSettings["AuthTokenExpiry"]);
- response.Headers.Add("Access-Control-Expose-Headers", "Token,TokenExpiry" );
- return response;
In our BasicAuthenticationIdentity class, I intentionally used the userId property so that we can use this property when we try to generate a token, that we are doing in this controller's Authenticate method.
- var basicAuthenticationIdentity = System.Threading.Thread.CurrentPrincipal.Identity as BasicAuthenticationIdentity;
- if (basicAuthenticationIdentity != null)
- {
- var userId = basicAuthenticationIdentity.UserId;
- return GetAuthToken(userId);
- }
Now when you run this application, you'll see the Authenticate API as well, just invoke this API with Basic Authentication and User credentials, you'll get the token with expiry, let's do this step-by-step.
- Run the application.
- Click on the first API link, in other words POST authenticate. You'll get the page to test the API.
- Press the TestAPI button in the right corner. In the test console, provide Header information with the Authorization set for Basic and the user credentials in Base64 format, like we did earlier. Click on Send.
- Now, since we provided valid credentials, we'll get a token from the Authenticate controller, with its expiry time.
In the database.
Here we get response 200, in other words our user is authenticated and logged into the system. The TokenExpiry is 900, in other words 15 minutes. Note that the time difference between IssuedOn and ExpiresOn is 15 minutes, this we did in the TokenServices class method GenerateToken, you can set the time as needed. The Token is 604653d8-eb21-495c-8efd-da50ef4e56d3. Now for 15 minutes we can use this token to call our other services. But before that, we should mark our other services to understand this token and respond accordingly. Keep the generated token saved so that we can use it further in calling other services that I am about to explain. So let's set up authorization on other services.
Set Up Authorization Action Filter
We already have our Authentication filter in place and we don't want to use it for authorization purposes. So we need to create a new Action Filter for authorization. This action filter will only recognize a Token coming in requests. It assumes that, requests are already authenticated using our login channel and now the user is authorized/not authorized to use other services like Products in our case, there could be n number of other services too that can use this authorization action filter. For the request to be authorized, we don't need to user credentials. Only a token (received from the Authenticate controller after successful validation) needs to be ed using the request.
Add a folder named ActionFilters to the WebAPI project. And add a class named AuthorizationRequiredAttribute.
Deriving from ActionFilterAttribute.
Override the OnActionExecuting method of ActionFilterAttribute, this is the way we define an action filter in an API project.
- using System.Linq;
- using System.Net;
- using System.Net.Http;
- using System.Web.Http.Controllers;
- using System.Web.Http.Filters;
- using BusinessServices;
-
- namespace WebAPI.ActionFilters {
- public class AuthorizationRequiredAttribute: ActionFilterAttribute {
- private const string Token = "Token";
-
- public override void OnActionExecuting(HttpActionContext filterContext) {
-
- var provider = filterContext.ControllerContext.Configuration.DependencyResolver.GetService(typeof(ITokenServices)) as ITokenServices;
-
- if (filterContext.Request.Headers.Contains(Token)) {
- var tokenValue = filterContext.Request.Headers.GetValues(Token).First();
-
-
- if (provider != null && !provider.ValidateToken(tokenValue)) {
- var responseMessage = new HttpResponseMessage(HttpStatusCode.Unauthorized) {
- ReasonPhrase = "Invalid Request"
- };
- filterContext.Response = responseMessage;
- }
- } else {
- filterContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
- }
-
- base.OnActionExecuting(filterContext);
-
- }
- }
- }
The overridden method checks for the “Token” attribute in the Header of every request. If the token is present, it calls the ValidateToken method from TokenServices to check if the token exists in the database. If the token is valid, our request is navigated to the actual controller and the action that we requested, else you'll get an error message saying unauthorized.
Mark Controllers with Authorization filterWe have our action filter ready. Now let's mark our controller ProductController with this attribute. Just open the Product controller class and at the top just decorate that class with this ActionFilter attribute as in the following:
- [AuthorizationRequired]
- [RoutePrefix("v1/Products/Product")]
- public class ProductController : APIController
- {
- }
We have marked our controller with the action filter that we created, now every request coming to the actions of this controller will need to be ed using this ActionFilter, that checks for the token in the request.
You can mark other controllers as well with the same attribute, or you can mark it at the action level as well. Assume you want certain actions to be available to all users irrespective of their authorization. Then you can just mark only those actions that require authorization and leave other actions as they are, like I explained in Step 4 of Implementing Basic Authentication.
Maintaining Session using TokenWe can certainly use these tokens to maintain session as well. The tokens are issues for 900 seconds, in other words 15 minutes. Now we want that the user should be able to continue to use this token if he is using other services as well for our application. Or assume there is a case where we only want the user to finish his work on the site within 15 minutes or within his session time before he makes a new request. So when validating the token in TokenServices, what I have done is, to increase the time of the token by 900 seconds whenever a valid request comes with a valid token.
-
-
-
-
-
- public bool ValidateToken(string tokenId) {
- var token = _unitOfWork.TokenRepository.Get(t = > t.AuthToken == tokenId && t.ExpiresOn > DateTime.Now);
- if (token != null && !(DateTime.Now > token.ExpiresOn)) {
- token.ExpiresOn = token.ExpiresOn.AddSeconds(
- Convert.ToDouble(ConfigurationManager.AppSettings["AuthTokenExpiry"]));
- _unitOfWork.TokenRepository.Update(token);
- _unitOfWork.Save();
- return true;
- }
- return false;
- }
In the preceding code for token validation, first we check if the requested token exists in the database and is not expired. We check expiry by comparing it with the current date and time. If it is a valid token then we just update the token into the database with a new ExpiresOn time that is adding 900 seconds.
- if (token != null && !(DateTime.Now > token.ExpiresOn)) {
- token.ExpiresOn = token.ExpiresOn.AddSeconds(
- Convert.ToDouble(ConfigurationManager.AppSettings["AuthTokenExpiry"]));
- _unitOfWork.TokenRepository.Update(token);
- _unitOfWork.Save();
- }
By doing this we can allow the end user or client to maintain session and continue using our services/application with a session timeout of 15 minutes. This approach can also be leveraged in multiple ways, like making various services with various session timeouts or many such conditions could be applied when we work on actual applications using APIs.
Running the applicationOur job is nearly done.
We just need to run the application and test if it is working correctly. If you have saved the token you generated earlier when testing authentication then you can use it to test authorization. I am just again running the entire cycle to test the application.
Test AuthenticationRepeat the tests we did earlier to get an Auth Token. Just invoke the Authenticate controller with valid credentials and Basic authorization header. I get:
And without providing an Authorization header as basic with credentials I get.
I just saved the token that I got in first request.
Now try to call ProductController actions.
Test AuthorizationRun the application to invoke Product Controller actions. Try to invoke them without providing any Token.
Invoke first service in the list,
Click send.
Here we get Unauthorized, in other words because our ProductController is marked with the authorization attribute, it checks for a Token. So here our request is invalid. Now try calling this action by providing the token that we saved.
Click on Send and we get.
That means we got the response and our token was valid. Now we see our Authentication and Authorization, both functionalities are working fine. You can test Sessions on your own.
Likewise you can test all actions. You can create other controllers and test the security and play around with sets of permutations and combinations.
ConclusionWe covered and learned a lot. In this article I tried to explain about how to build an API application with basic Authentication and Authorization. One can mould this concept to achieve the level of security needed. Like the token generation mechanism could be customized as per one's requirements. Two levels of security could be implemented where authentication and authorization is needed for every service. One can also implement authorization on actions based on roles.
Image credit:
http://www.greencountryfordofparsons.com I already stated that there is no specific way to do security, the more you understand the concept, the more secure you can make the system. The techniques used in this article or the design implemented in this article should be leveraged well if you use it with Secure Socket Layer (SSL), running the REST APIs on https. In my next article I'll try to explain some more beautiful implementations and concepts. Until then Happy Coding.
You can also download the complete source code with all packages from
Github.
References
Read more:
For more technical articles you can reach out to CodeTeddy
My other series of articles: