Introduction
In the modern era of development, we use web API for various purposes for sharing data, or for binding grid, drop-down list, and other controls, but if we do not secure this API, then other people who are going to access your web application or service can misuse it in some or the other way and also we are into the era of client-side framework (JavaScript, Angular js, react js, express js, common js, etc.) if you are using one of these client-side frameworks, then you are using web service or web API. It is true for getting or posting data to server and being on client side is less secure, you need to add extra efforts to secure it.
In this article, we are going to learn that extra part, the process of securing Web API begins with registering process. In this part, we are first going to register a user, after user registration, next user who is registered is going to login into application, after login into application, User needs to register a company which is going to use this service, after company registration, the next step we are going to get ClientID
and ClientSecert
keys.
After getting keys, next we are going to use these keys for authentication. The first request to access API must come with valid ClientID
and ClientSecert
. Next, it will validate keys and then it is going to provide Token in response, this token you need to use in every request to authenticate that you are a valid user and this Token expires in 30 minutes, but if you want to provide custom time according to your need, you can do it.
Also, this token is secured using AES 256 encryption algorithm.
Process
- Register User
- Login
- Register Company
- Get
ClientID
and ClientSecert
- Authenticate to get Token
- Use Token for Authenticating and accessing Data
Database Parts
In this part, we have created the database with name "MusicDB
" and it has five tables which we are going to use in the application.
- Register User: Stores user information
- Register Company: Stores Companies information
- Client Keys: Stores
ClientID
and ClientSecert
- TokensManager: Stores all token information
- Music Store: Stores all Music information which is accessed by Clients
Creating WEB API Application
In this part, we are going to create simple Web API application for creating that I have chosen "ASP.NET Web Application (.NET Framework)" template and named the project as "MusicAPIStore
" and next, we are going to choose a template as "Web API" to create a project.
After creating project, below is the complete View of the project.
Using Entity Framework Code First Approach to Connecting Application to Database
The first step we are going to add connection string of database in Web.config.
The second step we are going to add Models according to tables in "MusicDB
" database.
In the below snapshot, we have created Model of all Tables with the same name as Table name.
The third step creating a class with name DatabaseContext
which is going to inherit DbContext
class than inside this class we are going to add a DbSet
object.
Code Snippet of Databasecontext Class
using MusicAPIStore.Models;
using System.Data.Entity;
namespace MusicAPIStore.Context
{
public class DatabaseContext : DbContext
{
public DatabaseContext() : base("DefaultConnection")
{
}
public DbSet<RegisterUser> RegisterUser { get; set; }
public DbSet<RegisterCompany> RegisterCompany { get; set; }
public DbSet<TokensManager> TokensManager { get; set; }
public DbSet<ClientKeys> ClientKeys { get; set; }
public DbSet<MusicStore> MusicStore { get; set; }
}
}
After completing with adding DatabaseContext
class, next we are going to add folder with name Repository to Project.
Adding Repository Folder to Application
In this application, we are going to use repository pattern.
For storing interfaces and concrete class, we have to add Repository folder to the application.
We have completed adding Repository folder to the application. Next, we are going to add RegisterUser
, Model
, Interface
, Concrete class, Controller, and Views.
Register User
RegisterUser
Model - Adding
IRegisterUser
Interface - Adding
RegisterUserConcrete
RegisterUserConcrete
will inherit IRegisterUser
- Adding Controller
- Adding View
Let’s see RegisterUser
Model.
1. RegisterUser Model
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MusicAPIStore.Models
{
[Table("RegisterUser")]
public class RegisterUser
{
[Key]
public int UserID { get; set; }
[Required(ErrorMessage = "Required Username")]
[StringLength(30, MinimumLength = 2,
ErrorMessage = "Username Must be Minimum 2 Charaters")]
public string Username { get; set; }
[DataType(DataType.Password)]
[Required(ErrorMessage = "Required Password")]
[MaxLength(30,ErrorMessage = "Password cannot be Greater than 30 Charaters")]
[StringLength(31, MinimumLength = 7 ,
ErrorMessage ="Password Must be Minimum 7 Charaters")]
public string Password { get; set; }
public DateTime CreateOn { get; set; }
[Required(ErrorMessage = "Required EmailID")]
[RegularExpression(@"[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}",
ErrorMessage = "Please enter Valid Email ID")]
public string EmailID { get; set; }
}
}
After adding RegisterUser
Model, next we are going to add Interface IRegisterUser
in which we are going to declare methods which we require.
2. IRegisterUser Interface
We are going to add IRegisterUser
interface in Repository folder which we have created.
This interface contains four methods:
Add
(Inserting User Data into database) ValidateRegisteredUser
(Validating User already exists in database or not returns Boolean value)
ValidateUsername
(Validating Username
already existed in database or not returns Boolean value)
GetLoggedUserID
(Gets UserID
by Username
and Password
)
Code Snippet
using MusicAPIStore.Models;
namespace MusicAPIStore.Repository
{
public interface IRegisterUser
{
void Add(RegisterUser registeruser);
bool ValidateRegisteredUser(RegisterUser registeruser);
bool ValidateUsername(RegisterUser registeruser);
int GetLoggedUserID(RegisterUser registeruser);
}
}
After adding IRegisterUser
Interface, next we are going to add RegisterUserConcrete
Class in Repository folder.
3. RegisterUserConcrete
We are going to add RegisterUserConcrete
Class in Repository folder which we have created.
After adding RegisterUserConcrete
class, next we are going to inherit it from IRegisterUser
Interface and implement all methods inside of IRegisterUser
Interface.
Code Snippet
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MusicAPIStore.Models;
using MusicAPIStore.Context;
namespace MusicAPIStore.Repository
{
public class RegisterUserConcrete : IRegisterUser
{
DatabaseContext _context;
public RegisterUserConcrete()
{
_context = new DatabaseContext();
}
public void Add(RegisterUser registeruser)
{
_context.RegisterUser.Add(registeruser);
_context.SaveChanges();
}
public int GetLoggedUserID(RegisterUser registeruser)
{
var usercount = (from User in _context.RegisterUser
where User.Username == registeruser.Username &&
User.Password == registeruser.Password
select User.UserID).FirstOrDefault();
return usercount;
}
public bool ValidateRegisteredUser(RegisterUser registeruser)
{
var usercount = (from User in _context.RegisterUser
where User.Username == registeruser.Username &&
User.Password == registeruser.Password
select User).Count();
if (usercount > 0)
{
return true;
}
else
{
return false;
}
}
public bool ValidateUsername(RegisterUser registeruser)
{
var usercount = (from User in _context.RegisterUser
where User.Username == registeruser.Username
select User).Count();
if (usercount > 0)
{
return true;
}
else
{
return false;
}
}
}
}
After adding RegisterUserConcrete
class, next we are going to Add RegisterUserController
.
4. Adding RegisterUser Controller
For adding controller, just right on Controllers folder, inside that select Add a Inside that select Controller after selecting Controller a new dialog with name "Add Scaffold" will Pop up for choosing type of controller to add in that we are just going to select "MVC5Controller - Empty" and click on Add button. After that, a new dialog with name "Add Controller" will pop up asking for Controller name. Here, we are going to name controller as RegisterUserController
and click on Add button.
Note: Next, we are going to initialize an object in Constructor of RegisterUser
Controller.
Code Snippet
using MusicAPIStore.AES256Encryption;
using MusicAPIStore.Models;
using MusicAPIStore.Repository;
using System;
using System.Web.Mvc;
namespace MusicAPIStore.Controllers
{
public class RegisterUserController : Controller
{
IRegisterUser repository;
public RegisterUserController()
{
repository = new RegisterUserConcrete();
}
public ActionResult Create()
{
return View(new RegisterUser());
}
[HttpPost]
public ActionResult Create(RegisterUser RegisterUser)
{
try
{
if (!ModelState.IsValid)
{
return View("Create", RegisterUser);
}
if (repository.ValidateUsername(RegisterUser))
{
ModelState.AddModelError("", "User is Already Registered");
return View("Create", RegisterUser);
}
RegisterUser.CreateOn = DateTime.Now;
RegisterUser.Password = EncryptionLibrary.EncryptText(RegisterUser.Password);
repository.Add(RegisterUser);
TempData["UserMessage"] = "User Registered Successfully";
ModelState.Clear();
return View("Create", new RegisterUser());
}
catch
{
return View();
}
}
}
}
In this Controller, we have added two Action methods of Create one for handling [HttpGet]
request and other for handling [HttpPost]
request. In [HttpPost]
request, we are going to first Validate
Is Username
already exists or not. If not, then we are going to Create User, next we are also taking Password as input which we cannot store in database as clear text. We need to store it in encrypted format. For doing that, we are going to use AES 256 algorithm.
5. RegisterUser View
After Registering a User, below are details which get stored in RegisterUser
Table.
6. RegisterUser Table
After completing with Registration part, next we are going to create a Login page.
2. Login
In this part, we are going to add Login Controller with Login and Logout Action method in it.
Here, we do not need to add a new interface or concrete class because we have already created a RegisterUserConcrete
method which has a method which is required by Login Controller.
Code Snippet
using MusicAPIStore.AES256Encryption;
using MusicAPIStore.Models;
using MusicAPIStore.Repository;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MusicAPIStore.Controllers
{
public class LoginController : Controller
{
IRegisterUser _IRegisterUser;
public LoginController()
{
_IRegisterUser = new RegisterUserConcrete();
}
public ActionResult Login()
{
return View(new RegisterUser());
}
[HttpPost]
public ActionResult Login(RegisterUser RegisterUser)
{
try
{
if (string.IsNullOrEmpty(RegisterUser.Username) &&
(string.IsNullOrEmpty(RegisterUser.Password)))
{
ModelState.AddModelError("", "Enter Username and Password");
}
else if (string.IsNullOrEmpty(RegisterUser.Username))
{
ModelState.AddModelError("", "Enter Username");
}
else if (string.IsNullOrEmpty(RegisterUser.Password))
{
ModelState.AddModelError("", "Enter Password");
}
else
{
RegisterUser.Password =
EncryptionLibrary.EncryptText(RegisterUser.Password);
if (_IRegisterUser.ValidateRegisteredUser(RegisterUser))
{
var UserID = _IRegisterUser.GetLoggedUserID(RegisterUser);
Session["UserID"] = UserID;
return RedirectToAction("Create", "RegisterCompany");
}
else
{
ModelState.AddModelError("", "User is Already Registered");
return View("Create", RegisterUser);
}
}
return View("Login", RegisterUser);
}
catch
{
return View();
}
}
public ActionResult Logout()
{
Session.Abandon();
return RedirectToAction("Login", "Login");
}
}
}
After completing with login, next we going to create Register Company Interface and Concrete in Repository folder and after that, we are going add RegisterCompanyController
and Action method along with View.
Note: We are using AES 256 algorithm with salt for encryption and decryption.
3. Register Company
In this part, we are going to register a company and to this company, we are going to generate ClientID
and ClientSecret
.
Let’s start with adding an interface with name IRegisterCompany
which will contain all methods which need to be implemented by Concrete
class.
Code Snippet of IRegisterCompany
using MusicAPIStore.Models;
using System.Collections.Generic;
namespace MusicAPIStore.Repository
{
public interface IRegisterCompany
{
IEnumerable<RegisterCompany> ListofCompanies(int UserID);
void Add(RegisterCompany entity);
void Delete(RegisterCompany entity);
void Update(RegisterCompany entity);
RegisterCompany FindCompanyByUserId(int UserID);
bool ValidateCompanyName(RegisterCompany registercompany);
bool CheckIsCompanyRegistered(int UserID);
}
}
We have declared all methods which are required by Register Company Controller. Next, we are going to add Concrete
class with name RegisterCompanyConcrete
which is going to implement the IRegisterCompany
interface.
Code Snippet
using System;
using System.Collections.Generic;
using System.Linq;
using MusicAPIStore.Models;
using MusicAPIStore.Context;
namespace MusicAPIStore.Repository
{
public class RegisterCompanyConcrete : IRegisterCompany
{
DatabaseContext _context;
public RegisterCompanyConcrete()
{
_context = new DatabaseContext();
}
public IEnumerable<RegisterCompany> ListofCompanies(int UserID)
{
try
{
var CompanyList = (from companies in _context.RegisterCompany
where companies.UserID == UserID
select companies).ToList();
return CompanyList;
}
catch (Exception)
{
throw;
}
}
public void Add(RegisterCompany entity)
{
try
{
_context.RegisterCompany.Add(entity);
_context.SaveChanges();
}
catch (Exception)
{
throw;
}
}
public void Delete(RegisterCompany entity)
{
try
{
var itemToRemove = _context.RegisterCompany.SingleOrDefault
(x => x.CompanyID == entity.CompanyID);
_context.RegisterCompany.Remove(itemToRemove);
_context.SaveChanges();
}
catch (Exception)
{
throw;
}
}
public RegisterCompany FindCompanyByUserId(int UserID)
{
try
{
var Company =
_context.RegisterCompany.SingleOrDefault(x => x.UserID == UserID);
return Company;
}
catch (Exception)
{
throw;
}
}
public bool ValidateCompanyName(RegisterCompany registercompany)
{
try
{
var result = (from company in _context.RegisterCompany
where company.Name == registercompany.Name &&
company.EmailID == registercompany.EmailID
select company).Count();
if (result > 0)
{
return true;
}
else
{
return false;
}
}
catch (Exception)
{
throw;
}
}
public bool CheckIsCompanyRegistered(int UserID)
{
try
{
var companyExists = _context.RegisterCompany.Any(x => x.UserID == UserID);
if (companyExists)
{
return true;
}
else
{
return false;
}
}
catch (Exception)
{
throw;
}
}
}
}
After we are done with implementing all methods of interface, next we are going to Add RegisterCompany
Controller.
Adding RegisterCompany Controller
In this part, we are going to add RegisterCompany
Controller with Create and index Action
method in it.
In index Action method, we are going to get list of Companies and in creating [HttpGet]
Action method, we are going to get a list of companies on the basis of UserID
, and in creating [HttpPost]
Action method, we are going to save data of company in database and before that, we are going to check whether the company name already exists or not. If company name exists, then we are going to show error message "Company is Already Registered".
For adding Controller
, follow the same step which we have used for adding RegisterUser
Controller. After adding controller, we have just manually added a constructor and three Action methods in it as shown below.
Code Snippet
using MusicAPIStore.Context;
using MusicAPIStore.Models;
using MusicAPIStore.Repository;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MusicAPIStore.Filters;
namespace MusicAPIStore.Controllers
{
[ValidateSessionAttribute]
public class RegisterCompanyController : Controller
{
IRegisterCompany _IRegister;
public RegisterCompanyController()
{
_IRegister = new RegisterCompanyConcrete();
}
public ActionResult Index()
{
var RegisterList = _IRegister.ListofCompanies(Convert.ToInt32(Session["UserID"]));
return View(RegisterList);
}
public ActionResult Create()
{
var Company = _IRegister.CheckIsCompanyRegistered
(Convert.ToInt32(Session["UserID"]));
if (Company)
{
return RedirectToAction("Index");
}
return View();
}
[HttpPost]
public ActionResult Create(RegisterCompany RegisterCompany)
{
try
{
if (!ModelState.IsValid)
{
return View("Create", RegisterCompany);
}
if (_IRegister.ValidateCompanyName(RegisterCompany))
{
ModelState.AddModelError("", "Company is Already Registered");
return View("Create", RegisterCompany);
}
RegisterCompany.UserID = Convert.ToInt32(Session["UserID"]);
RegisterCompany.CreateOn = DateTime.Now;
_IRegister.Add(RegisterCompany);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
}
}
After completing with adding Controller and its Action method, next we are going to add View to Create and index Action method.
Adding Index and Company View
In this part, we are going to add Views. For adding View, just right click inside Action method, then choose Add View from Menu list a new dialog will pop up with name "Add View". Next, we do not require to provide name to view it is set to default which is Action method name in Template choose template to depend on which view you want to create (Create
, Index
) and long with that choose Model (RegisterCompany
). Click on Add Button.
After adding View, just save the application and run, then the first step is to login into the application as you log in, you will see RegisterCompany
View, just register your Company.
After creating a company, you will able to see Index View as shown in the below snapshot with company details which you have filled.
Now we have Registered Company next step is to Get Application ClientID and Client Secret.
Generate ClientID and Client Secret Keys
In this part, we are going to Generate Unique ClientID and Client Secret keys for each company which is registered.
We generate these keys using RNGCryptoServiceProvider
algorithm.
Code Snippet of Key Generator Class
public static class KeyGenerator
{
public static string GetUniqueKey(int maxSize = 15)
{
char[] chars = new char[62];
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
byte[] data = new byte[1];
using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider())
{
crypto.GetNonZeroBytes(data);
Data = new byte[maxSize];
crypto.GetNonZeroBytes(data);
}
StringBuilder result = new StringBuilder(maxSize);
foreach (byte b in data)
{
result.Append(chars[b % (chars.Length)]);
}
return result.ToString();
}
}
After generating ClientID and Client Secret keys, we are going to insert these Keys with UserID
in ClientKey
table and display to User for using it.
And we have also provided ReGenerate Keys button on GenerateKeys
View such that if the user wants, he can generate a new pair of Keys he can do by clicking on this button again, these values are updated to the database according to UserID
.
Let’s begin with having a look at Model ClientKey
first.
After adding ClientKey
Model in Model folder, next we are going to move forward by adding Interface with name IClientKeys
and in this interface, we are going to declare five methods. The methods names inside interface are self-explanatory.
Adding Interface IClientKeys
using MusicAPIStore.Models;
namespace MusicAPIStore.Repository
{
public interface IClientKeys
{
bool IsUniqueKeyAlreadyGenerate(int UserID);
void GenerateUniqueKey(out string ClientID, out string ClientSecert);
int SaveClientIDandClientSecert(ClientKey ClientKeys);
int UpdateClientIDandClientSecert(ClientKey ClientKeys);
ClientKey GetGenerateUniqueKeyByUserID(int UserID);
}
}
After adding Interface and declaring methods inside it, next we are going to add Concrete class which is going to inherit this IClientKeys
interface.
Adding Class ClientKeysConcrete
In this part, we are going to add Concrete
class with name ClientKeysConcrete
which is going to inherit IClientKeys
interface and implement all methods in it.
using System.Linq;
using MusicAPIStore.Models;
using MusicAPIStore.Context;
using MusicAPIStore.AES256Encryption;
using System.Data.Entity;
namespace MusicAPIStore.Repository
{
public class ClientKeysConcrete : IClientKeys
{
DatabaseContext _context;
public ClientKeysConcrete()
{
_context = new DatabaseContext();
}
public void GenerateUniqueKey(out string ClientID, out string ClientSecert)
{
ClientID = EncryptionLibrary.KeyGenerator.GetUniqueKey();
ClientSecert = EncryptionLibrary.KeyGenerator.GetUniqueKey();
}
public bool IsUniqueKeyAlreadyGenerate(int UserID)
{
bool keyExists = _context.ClientKeys.Any
(clientkeys => clientkeys.UserID.Equals(UserID));
if (keyExists)
{
return true;
}
else
{
return false;
}
}
public int SaveClientIDandClientSecert(ClientKey ClientKeys)
{
_context.ClientKeys.Add(ClientKeys);
return _context.SaveChanges();
}
public ClientKey GetGenerateUniqueKeyByUserID(int UserID)
{
var clientkey = (from ckey in _context.ClientKeys
where ckey.UserID == UserID
select ckey).FirstOrDefault();
return clientkey;
}
public int UpdateClientIDandClientSecert(ClientKey ClientKeys)
{
_context.Entry(ClientKeys).State = EntityState.Modified;
_context.SaveChanges();
return _context.SaveChanges();
}
}
}
If you had view on ClientKeysConcrete
class, there is "GenerateUniqueKey
" method which generates unique ClientID and Client Secret keys and returns, next we are going to have look at the method "IsUniqueKeyAlreadyGenerate
". This method checks whether ClientID and Client Secret keys is already generated for this user or not, next we are going to have look on method "SaveClientIDandClientSecert
" as this method name says it is going to save ClientID and Client Secret keys details in database.
If a user logs out from the portal and if he visits the portal again to see his ClientID and Client Secret keys for getting those keys from the database, we have created "GetGenerateUniqueKeyByUserID
" method.
The last method which we are going to see is "UpdateClientIDandClientSecert
". This method is used to update ClientID and Client Secret keys when User clicks on ReGenerate Keys button on View which we are going to add in the meanwhile.
Adding ApplicationKeys Controller
In this part, we are going to add ApplicationKeys
Controller with two GenerateKeys
Action methods in it.
One Action method handles [HttpGet]
part and another is going to handle [HttpPost]
part.
In [HttpGet] GenerateKeys
Action method, we are going the first check is Keys Already Generate or not. If not, then we are going to Generate new Keys and save that keys in Database and Display to Users.
The [HttpPost]
GenerateKeys
Action method is called when User clicks on "ReGenerate Keys" button and this we are going to Generate new Keys and save those keys in database and display to users.
For adding Controller, follow the same step which we have used for adding RegisterUser
Controller. After adding controller, we have just manually added a constructor and two Action methods in it as shown below.
Code Snippet of ApplicationKeys Controller
using MusicAPIStore.Filters;
using MusicAPIStore.Models;
using MusicAPIStore.Repository;
using System;
using System.Web.Mvc;
namespace MusicAPIStore.Controllers
{
[ValidateSessionAttribute]
public class ApplicationKeysController : Controller
{
IClientKeys _IClientKeys;
IRegisterCompany _IRegisterCompany;
public ApplicationKeysController()
{
_IClientKeys = new ClientKeysConcrete();
_IRegisterCompany = new RegisterCompanyConcrete();
}
[HttpGet]
public ActionResult GenerateKeys()
{
try
{
ClientKey clientkeys = new ClientKey();
var keyExists = _IClientKeys.IsUniqueKeyAlreadyGenerate
(Convert.ToInt32(Session["UserID"]));
if (keyExists)
{
clientkeys = _IClientKeys.GetGenerateUniqueKeyByUserID
(Convert.ToInt32(Session["UserID"]));
}
else
{
string clientID=string.Empty;
string clientSecert = string.Empty;
int companyId = 0;
var company = _IRegisterCompany.FindCompanyByUserId
(Convert.ToInt32(Session["UserID"]));
companyId = company.CompanyID;
_IClientKeys.GenerateUniqueKey(out clientID, out clientSecert);
clientkeys.ClientKeyID = 0;
clientkeys.CompanyID = companyId;
clientkeys.CreateOn = DateTime.Now;
clientkeys.ClientID = clientID;
clientkeys.ClientSecret = clientSecert;
clientkeys.UserID = Convert.ToInt32(Session["UserID"]);
_IClientKeys.SaveClientIDandClientSecert(clientkeys);
}
return View(clientkeys);
}
catch (Exception)
{
throw;
}
}
[HttpPost]
public ActionResult GenerateKeys(ClientKey clientkeys)
{
try
{
string clientID = string.Empty;
string clientSecert = string.Empty;
_IClientKeys.GenerateUniqueKey(out clientID, out clientSecert);
var company = _IRegisterCompany.FindCompanyByUserId
(Convert.ToInt32(Session["UserID"]));
clientkeys.CompanyID = company.CompanyID;
clientkeys.CreateOn = DateTime.Now;
clientkeys.ClientID = clientID;
clientkeys.ClientSecret = clientSecert;
clientkeys.UserID = Convert.ToInt32(Session["UserID"]);
_IClientKeys.UpdateClientIDandClientSecert(clientkeys);
return RedirectToAction("GenerateKeys");
}
catch (Exception ex)
{
return View();
}
}
}
}
After completing with adding Controller and its Action method, next we are going to add View to GenerateKeys
Action method.
Adding GenerateKeys View
In this part, we are going to add Views for adding View. Just right click inside Action method, then choose Add View from Menu list, a new dialog will pop up with name "Add View". Next, we do not require to provide name to view. It is set to default which is Action method name. In Template, choose template to depend on upon which view you want to create (Create) and along with that, choose Model (ClientKey). Click on Add Button.
After adding View, just save the application and run, then the first step is to login to the application. As you log in, you will see RegisterCompany Index View.
On the Master page, we have added a link to show GenerateKeys View.
Now on clicking on Application secret link, it will show GenerateKeys View.
Now we have completed generating ClientID and Client Secret. Next, we are going to add Authenticate Controller for authenticating ClientID and Client Secret and return Token key in Response.
Authentication Mechanism
In this process, we have provided ClientID and Client Secret to Client and now we need to develop authenticating mechanism where user will send this ClientID and Client Secret to Server, then we are going to validate these keys with database and after that, we are going to return token to User in response if keys are valid then only, else we are going to return Error Message.
Let’s start with adding interface with name IAuthenticate
and declaring methods in it.
Adding IAuthenticate Interface
using MusicAPIStore.Models;
using System;
namespace MusicAPIStore.Repository
{
public interface IAuthenticate
{
ClientKey GetClientKeysDetailsbyCLientIDandClientSecert
(string clientID , string clientSecert);
bool ValidateKeys(ClientKey ClientKeys);
bool IsTokenAlreadyExists(int CompanyID);
int DeleteGenerateToken(int CompanyID);
int InsertToken(TokensManager token);
string GenerateToken(ClientKey ClientKeys, DateTime IssuedOn);
}
}
We have declared five methods in the IAuthenticate
interface:
GetClientKeysDetailsbyCLientIDandClientSecert
This method takes ClientID and Client Secret as input and gets data from the database on the basis of it.
ValidateKeys
This method takes ClientKey Model as input in which it checks ClientID and Client Secret passed by users is valid or not.
IsTokenAlreadyExists
This method takes CompanyID
as input and checks whether Token is already generated for it.
DeleteGenerateToken
This method takes CompanyID
as input and deletes token which is already generated on the basis of the CompanyID
parameter.
InsertToken
This method takes TokensManager
model as input parameter for saving Token
values in database.
GenerateToken
This method generates and returns Token
.
Adding Authenticate Concrete Class
In this part, we are going to add Concrete
class with name AuthenticateConcrete
which is going to inherit IAuthenticate
interface and implement all methods in it.
using System;
using System.Linq;
using MusicAPIStore.Models;
using MusicAPIStore.Context;
using MusicAPIStore.AES256Encryption;
using static MusicAPIStore.AES256Encryption.EncryptionLibrary;
namespace MusicAPIStore.Repository
{
public class AuthenticateConcrete : IAuthenticate
{
DatabaseContext _context;
public AuthenticateConcrete()
{
_context = new DatabaseContext();
}
public ClientKey GetClientKeysDetailsbyCLientIDandClientSecert
(string clientID, string clientSecert)
{
try
{
var result = (from clientkeys in _context.ClientKeys
where clientkeys.ClientID == clientID &&
clientkeys.ClientSecret == clientSecert
select clientkeys).FirstOrDefault();
return result;
}
catch (Exception)
{
throw;
}
}
public bool ValidateKeys(ClientKey ClientKeys)
{
try
{
var result = (from clientkeys in _context.ClientKeys
where clientkeys.ClientID == ClientKeys.ClientID &&
clientkeys.ClientSecret == ClientKeys.ClientSecret
select clientkeys).Count();
if (result > 0)
{
return true;
}
else
{
return false;
}
}
catch (Exception)
{
throw;
}
}
public bool IsTokenAlreadyExists(int CompanyID)
{
try
{
var result = (from token in _context.TokensManager
where token.CompanyID == CompanyID
select token).Count();
if (result > 0)
{
return true;
}
else
{
return false;
}
}
catch (Exception)
{
throw;
}
}
public int DeleteGenerateToken(int CompanyID)
{
try
{
var token = _context.TokensManager.SingleOrDefault
(x => x.CompanyID == CompanyID);
_context.TokensManager.Remove(token);
return _context.SaveChanges();
}
catch (Exception)
{
throw;
}
}
public string GenerateToken(ClientKey ClientKeys, DateTime IssuedOn)
{
try
{
string randomnumber =
string.Join(":", new string[]
{ Convert.ToString(ClientKeys.UserID),
KeyGenerator.GetUniqueKey(),
Convert.ToString(ClientKeys.CompanyID),
Convert.ToString(IssuedOn.Ticks),
ClientKeys.ClientID
});
return EncryptionLibrary.EncryptText(randomnumber);
}
catch (Exception)
{
throw;
}
}
public int InsertToken(TokensManager token)
{
try
{
_context.TokensManager.Add(token);
return _context.SaveChanges();
}
catch (Exception)
{
throw;
}
}
}
}
Snapshot after Adding Interface IAuthenticate and AuthenticateConcrete
After completing with adding Interface IAuthenticate
and AuthenticateConcrete
, next, we are going to add ApiController
with name Authenticate
.
Adding Authenticate Controller
In this part, we are going to add ApiController
with name Authenticate
and it will have a single Action
method in it with name Authenticate
which takes ClientKey Model as input from [FromBody
].
using MusicAPIStore.Models;
using MusicAPIStore.Repository;
using System;
using System.Configuration;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace MusicAPIStore.Controllers
{
public class AuthenticateController : ApiController
{
IAuthenticate _IAuthenticate;
public AuthenticateController()
{
_IAuthenticate = new AuthenticateConcrete();
}
public HttpResponseMessage Authenticate([FromBody]ClientKey ClientKeys)
{
if (string.IsNullOrEmpty(ClientKeys.ClientID) &&
string.IsNullOrEmpty(ClientKeys.ClientSecret))
{
var message = new HttpResponseMessage(HttpStatusCode.NotAcceptable);
message.Content = new StringContent("Not Valid Request");
return message;
}
else
{
if (_IAuthenticate.ValidateKeys(ClientKeys))
{
var clientkeys =
_IAuthenticate.GetClientKeysDetailsbyCLientIDandClientSecert
(ClientKeys.ClientID, ClientKeys.ClientSecret);
if (clientkeys == null)
{
var message = new HttpResponseMessage(HttpStatusCode.NotFound);
message.Content = new StringContent("InValid Keys");
return message;
}
else
{
if (_IAuthenticate.IsTokenAlreadyExists(clientkeys.CompanyID))
{
_IAuthenticate.DeleteGenerateToken(clientkeys.CompanyID);
return GenerateandSaveToken(clientkeys);
}
else
{
return GenerateandSaveToken(clientkeys);
}
}
}
else
{
var message = new HttpResponseMessage(HttpStatusCode.NotFound);
message.Content = new StringContent("InValid Keys");
return new HttpResponseMessage
{ StatusCode = HttpStatusCode.NotAcceptable };
}
}
}
[NonAction]
private HttpResponseMessage GenerateandSaveToken(ClientKey clientkeys)
{
var IssuedOn = DateTime.Now;
var newToken = _IAuthenticate.GenerateToken(clientkeys, IssuedOn);
TokensManager token = new TokensManager();
token.TokenID = 0;
token.TokenKey = newToken;
token.CompanyID = clientkeys.CompanyID;
token.IssuedOn = IssuedOn;
token.ExpiresOn = DateTime.Now.AddMinutes(Convert.ToInt32
(ConfigurationManager.AppSettings["TokenExpiry"]));
token.CreatedOn = DateTime.Now;
var result = _IAuthenticate.InsertToken(token);
if (result == 1)
{
HttpResponseMessage response = new HttpResponseMessage();
response = Request.CreateResponse(HttpStatusCode.OK, "Authorized");
response.Headers.Add("Token", newToken);
response.Headers.Add("TokenExpiry",
ConfigurationManager.AppSettings["TokenExpiry"]);
response.Headers.Add("Access-Control-Expose-Headers", "Token,TokenExpiry");
return response;
}
else
{
var message = new HttpResponseMessage(HttpStatusCode.NotAcceptable);
message.Content = new StringContent("Error in Creating Token");
return message;
}
}
}
}
Let’s understand the code:
public HttpResponseMessage Authenticate([FromBody]ClientKey ClientKeys)
{
if (string.IsNullOrEmpty(ClientKeys.ClientID) &&
string.IsNullOrEmpty(ClientKeys.ClientSecret))
{
var message = new HttpResponseMessage(HttpStatusCode.NotAcceptable);
message.Content = new StringContent("Not Valid Request");
return message;
}
else
{
if (_IAuthenticate.ValidateKeys(ClientKeys))
{
var clientkeys =
_IAuthenticate.GetClientKeysDetailsbyCLientIDandClientSecert
(ClientKeys.ClientID, ClientKeys.ClientSecret);
if (clientkeys == null)
{
var message = new HttpResponseMessage(HttpStatusCode.NotFound);
message.Content = new StringContent("InValid Keys");
return message;
}
else
{
if (_IAuthenticate.IsTokenAlreadyExists(clientkeys.CompanyID))
{
_IAuthenticate.DeleteGenerateToken(clientkeys.CompanyID);
return GenerateandSaveToken(clientkeys);
}
else
{
return GenerateandSaveToken(clientkeys);
}
}
}
else
{
var message = new HttpResponseMessage(HttpStatusCode.NotFound);
message.Content = new StringContent("InValid Keys");
return new HttpResponseMessage
{ StatusCode = HttpStatusCode.NotAcceptable };
}
}
}
The first step in this part is that the Authenticate
method takes ClientKey
model as input and from this model, we are going to use only two parameters, ClientID and Client Secret. Next, we are first going to check this is parameters null
or not. If it is Null
, we are going to send HttpResponseMessage as
"Not Valid Request
" in response.
If ClientID and Client Secret are not null
, then we are going to send ClientID and Client Secret to ValidateKeys
method to check whether these Values passed already exist in the database or not.
If ClientID and Client Secret value exist in database, then we pass both values to "GetClientKeysDetailsbyCLientIDandClientSecert
" method to get all ClientKey
details. Here again, we check whether Keys are Valid or not, if not, then we are going to send HttpResponseMessage as
"InValid Keys
" in response.
If ClientID and Client Secret are Valid, then we get ClientKey
Details from the database and from this Model, we pass CompanyID
to "IsTokenAlreadyExists
" method to check whether Token
already exists in the database or not.
If Token
already exists in database, then we are going to delete the old token and generate New token and insert new token in the database and we are also going to send Token in response to Client who has sent the request.
If Token does not exist in database, then we are going to generate token and insert new token in the database and we are also going to send Token in response to Client who has sent the request.
Now we have understood how to process work. Let’s try a real time example.
First to call Web API api/Authenticate
method, we are going to use Postman web debugger.
For Downloading Postman Chrome App
Installing the Postman Chrome App
https://www.getpostman.com/docs/introduction
After installing Postman Chrome App, now you can open Postman Chrome App. Below is a snapshot of Postman Chrome App.
In the next step, we are going have a look at ClientID and ClientSecret because we need to send these Keys to Get Token from Server.
Getting Keys
Next, we are going to set keys (ClientID
and ClientSecret
) in a FromBody
request to Post.
Setting Values for Post Request in POSTMAN
- Choose Post Request From Dropdownlist
- Set URL: http://localhost:4247/api/Authenticate
- We are going to send this Keys
FromBody
of Request. - It will be raw data with Content Type as
application/json
- Set '
ClientID
' and 'ClientSecret
'
{
'ClientID':'pGU6RJ8ELcVRZmN',
'ClientSecret':'tiIfdZ3vh5IwGVm'
}
- Click on Send Button to Send Request.
Response of Request
In Response
, we get Token
and TokenExpiry
.
Token: XbCsogSJXKLSq2TBUs0QZrbClRpiuXZFrfjKy0WRtEdPQYpA87Pav9KozmmKoNMd1W3Q8Hg8hoaGYKDtyTH2Rg==
TokenExpiry: 30
Token is only valid for 30 Minutes.
After getting a response, let’s have a view of TokenManager
table to see what fields got inserted in it.
In the next step, we are going to add AuthorizeAttribute
with name APIAuthorizeAttribute
.
Adding AuthorizeAttribute (APIAuthorizeAttribute)
In this part, we are going to create an AuthorizeAttribute
with name "APIAuthorizeAttribute
" in Filter folder.
For adding this AuthorizeAttribute
, first we are going to a class with name "APIAuthorizeAttribute
" and this class is going to inherit a class "AuthorizeAttribute
" and implement all methods inside it.
In this "APIAuthorizeAttribute
", we are going to validate Token which is sent from the client.
- The first step we are going to receive Token from Client Header.
- After that, we are going to check whether this token is
Null
or not. - Next, we are going to decrypt this Token.
- After decrypting this Token, we get string values as output.
- Next, we are going to split (‘:’)
string
values which we have received. - After splitting, we get (
UserID
, Random Key, CompanyID, Ticks, ClientID
) values. - Next, we are going to pass (
UserID
, CompanyID
, ClientID
) to database to check whether this parameter which we have received is valid. - After that, we are going to check Token Expiration.
- If something is throwing error in this step, we are going to return a
false
value. - And if we have valid values and token is not expired, then it will return
true
.
Authorize Attribute Snapshot with Explanation
After having a view on Snapshot, you will get a detailed idea about how it works. Next, we are going to see the complete code snippet of APIAuthorizeAttribute
.
APIAuthorizeAttribute Code Snippet
using MusicAPIStore.AES256Encryption;
using MusicAPIStore.Context;
using System;
using System.Linq;
using System.Web.Http;
using System.Web.Http.Controllers;
namespace MusicAPIStore.Filters
{
public class APIAuthorizeAttribute : AuthorizeAttribute
{
private DatabaseContext db = new DatabaseContext();
public override void OnAuthorization(HttpActionContext filterContext)
{
if (Authorize(filterContext))
{
return;
}
HandleUnauthorizedRequest(filterContext);
}
protected override void HandleUnauthorizedRequest(HttpActionContext filterContext)
{
base.HandleUnauthorizedRequest(filterContext);
}
private bool Authorize(HttpActionContext actionContext)
{
try
{
var encodedString = actionContext.Request.Headers.GetValues("Token").First();
bool validFlag = false;
if (!string.IsNullOrEmpty(encodedString))
{
var key = EncryptionLibrary.DecryptText(encodedString);
string[] parts = key.Split(new char[] { ':' });
var UserID = Convert.ToInt32(parts[0]);
var RandomKey = parts[1];
var CompanyID = Convert.ToInt32(parts[2]);
long ticks = long.Parse(parts[3]);
DateTime IssuedOn = new DateTime(ticks);
var ClientID = parts[4];
var registerModel = (from register in db.ClientKeys
where register.CompanyID == CompanyID
&& register.UserID == UserID
&& register.ClientID == ClientID
select register).FirstOrDefault();
if (registerModel != null)
{
var ExpiresOn = (from token in db.TokensManager
where token.CompanyID == CompanyID
select token.ExpiresOn).FirstOrDefault();
if ((DateTime.Now > ExpiresOn))
{
validFlag = false;
}
else
{
validFlag = true;
}
}
else
{
validFlag = false;
}
}
return validFlag;
}
catch (Exception)
{
return false;
}
}
}
}
After completing with adding APIAuthorizeAttribute
, next we need to apply this attribute to the controller.
But we have not added the controller (ApiController
) on which we need to apply this attribute, let’s add ApiController
with name "LatestMusic
".
Adding ApiController LatestMusic
In this part, we are going to add ApiController
with name "LatestMusic
" and it will have a single Action method in it with name GetMusicStore
which will return a list of latest music from the database.
LatestMusic Controller Code Snippet
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using MusicAPIStore.Context;
using MusicAPIStore.Models;
using MusicAPIStore.Filters;
namespace MusicAPIStore.Controllers
{
[APIAuthorizeAttribute]
public class LatestMusicController : ApiController
{
private DatabaseContext db = new DatabaseContext();
public List<MusicStore> GetMusicStore()
{
try
{
var listofSongs = db.MusicStore.ToList();
return listofSongs;
}
catch (System.Exception)
{
throw;
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
}
In the above code snippet, if you have a close look, then you can see that we have applied Attribute
at the controller level.
Now, whenever anyone is going to access this controller, he needs to have a valid token. Only after that, he can access LatestMusic API.
Accessing LatestMusic API Controller
For accessing LatestMusic API, we need to send a valid token in the header.
Let’s start from authenticating process first after authenticating, we are going to receive a valid token in response and this token again we are going to send to access LatestMusic API.
- Authenticating process to get token:
- Sending token to access
LatestMusic
API and getting Top 10 Music hit list in response.
Response in Details
Passing Invalid Token to Test Response
Validating Token after Session Expiration
After sending request when the token is expired, it will show an error message as shown below:
Table where token expiration date and time is stored:
Conclusion
In this article, we have learned how to secure WEB API using token based authentication in a step by step manner and in detailed manner such that junior developer can also understand it very easily. Now, you can secure your most client based application using this process, and also server based application.
Thank you for reading. I hope you liked my article.
History
- 20th April, 2017: Initial version