In one of our previous article, we have explained about how to create login and registration using ASP.NET MVC with database, now in this article, I have explained how we can authenticate user based on token using Web API and C#.

Now a days, Web API is widely used because using it, it becomes easy to build HTTP services that reach a broad range of clients, including browsers, mobile devices, and traditional desktop applications.

How does token based authentication works?

The general concept behind a token-based authentication system is simple. Allow users to enter their username and password in order to obtain a token which allows them to fetch a specific resource - without using their username and password. Once their token has been obtained, the user can offer the token - which offers access to a specific resource for a time period - to the remote site.

In other words: add one level of indirection for authentication -- instead of having to authenticate with username and password for each protected resource, the user authenticates that way once (within a session of limited duration), obtains a time-limited token in return, and uses that token for further authentication during the session. Source

Advantages of Token based authentication

Cross-domain / CORS: cookies + CORS don't play well across different domains. A token-based approach allows you to make AJAX calls to any server, on any domain because you use an HTTP header to transmit the user information.

Stateless (a.k.a. Server side scalability): there is no need to keep a session store, the token is a self-contained entity that conveys all the user information. The rest of the state lives in cookies or local storage on the client side.

CDN: you can serve all the assets of your app from a CDN (e.g. javascript, HTML, images, etc.), and your server side is just the API.

Decoupling: you are not tied to any particular authentication scheme. The token might be generated anywhere, hence your API can be called from anywhere with a single way of authenticating those calls.

Mobile ready: when you start working on a native platform (iOS, Android, Windows 8, etc.) cookies are not ideal when consuming a token-based approach simplifies this a lot.

CSRF: since you are not relying on cookies, you don't need to protect against cross site requests (e.g. it would not be possible to sib your site, generate a POST request and re-use the existing authentication cookie because there will be none).

Performance: we are not presenting any hard perf benchmarks here, but a network roundtrip (e.g. finding a session on database) is likely to take more time than calculating an HMACSHA256 to validate a token and parsing its contents.

Step by step procedure to create token based authentication in Web API and C#

Step 1: Open your Visual Studio and Create a new project, by selecting File-> New -> Project -> Select "Web" (Left panel) and Select "ASP.NET web-application" (Right-pane), name it and click "OK"

token-based-authentication-c-sharp-visual-studio-min.png

Once you are done, you will see a screen to select template, you can select "Empty" template with Checking "MVC" and "Web API" checkboxes, to generate the required folders.

/token-based-authentication-c-sharp-visual-studio-select-template-min.png

Step 2: Once Visual Studio creates the project with MVC/Web API file references, we would have to add Nuget packages for following

  • Microsoft.Owin.Host.SystemWeb
  • Microsoft.Owin.Security.OAuth
  • Microsoft.Owin.Cors

To install the above dll's in our project, you can go to "Tools"->"Nuget Package Manager" -> Select "Manage Nuget package for Solution.." -> Select "Browse" tab and search for "Microsoft.Owin.Host.SystemWeb", once you find it, select and Click "Install" , as shown in the image below

install-package-for-token-based-auth.png

Repeat the same procedure, to install "Microsoft.Owin.Security.OAuth" and "Microsoft.Owin.Cors"

Step 3:  Once we have installed all of the above package, we will need to create a class  Startup.cs inside 'App_Start' folder, so right click on it and "Add"-> "Class".

Name it StartUp.cs and add the below code

using Microsoft.Owin;
using Microsoft.Owin.Security.OAuth;
using Owin;
using System;
using TokenBasedAuthentication.Providers;

[assembly: OwinStartup(typeof(TokenBasedAuthentication.App_Start.Startup))]
namespace TokenBasedAuthentication.App_Start
{
    public class Startup
    {
        public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }

        public void Configuration(IAppBuilder app)
        {
           OAuthOptions = new OAuthAuthorizationServerOptions
            {
                TokenEndpointPath = new PathString("/token"),
                Provider = new OAuthCustomeTokenProvider(), // We will create
                AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(20),
                AllowInsecureHttp = true,
                RefreshTokenProvider = new OAuthCustomRefreshTokenProvider() // We will create
            };

            app.UseOAuthAuthorizationServer(OAuthOptions);
            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
            
        }
    }
}

After using above code, you will get error related to OAuthCustomeTokenProvider and OAuthCustomRefreshTokenProvider because we need to write these two methods.

So, create a new folder "Providers" inside your project and create a new class "OAuthCustomeTokenProvider.cs" inside it, and use the code below:

using Microsoft.Owin.Security;
using Microsoft.Owin.Security.OAuth;
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using TokenBasedAuthentication.App_Start;
using TokenBasedAuthentication.Models;

namespace TokenBasedAuthentication.Providers
{
    public class OAuthCustomeTokenProvider : OAuthAuthorizationServerProvider
    {

        public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            return Task.Factory.StartNew(() =>
            {
                var userName = context.UserName;
                var password = context.Password;
                var userService = new UserService();
                var user = userService.Validate(userName, password);
                if (user != null)
                {
                    var claims = new List<Claim>()
                {
                    new Claim(ClaimTypes.Sid, Convert.ToString(user.Id)),
                    new Claim(ClaimTypes.Name, user.Name),
                    new Claim(ClaimTypes.Email, user.Email)
                };
                    foreach (var role in user.Roles)
                        claims.Add(new Claim(ClaimTypes.Role, role));

                    var data = new Dictionary<string, string>
                {
                    { "userName", user.Name },
                    { "roles", string.Join(",", user.Roles)}
                };
                    var properties = new AuthenticationProperties(data);

                    ClaimsIdentity oAuthIdentity = new ClaimsIdentity(claims,
                        Startup.OAuthOptions.AuthenticationType);

                    var ticket = new AuthenticationTicket(oAuthIdentity, properties);
                    context.Validated(ticket);
                }
                else
                {
                    context.SetError("invalid_grant", "Either email or password is incorrect");
                }

            });
        }

        public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            if (context.ClientId == null)
                context.Validated();

            return Task.FromResult<object>(null);
        }


        public override Task TokenEndpoint(OAuthTokenEndpointContext context)
        {
            foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
            {
                context.AdditionalResponseParameters.Add(property.Key, property.Value);
            }
            return Task.FromResult<object>(null);
        }

    }
}

In the above code we are using "OAuthAuthorizationServerProvider", and creating Code to validate user, so you would be getting error for "UserService" which we will create in next step.

Let's create two more classes "UserService.cs" and "User.cs" inside "Models" folder of your project.

User.cs

using System;

namespace TokenBasedAuthentication.Models
{
    public class User
    {
        public int Id { get; set; }
        public String Name { get; set; }
        public String Email { get; set; }
        public String Password { get; set; }
        public string[] Roles { get; set; }
    }
}

UserService.cs

using System;
using System.Collections.Generic;
using System.Linq;

namespace TokenBasedAuthentication.Models
{
    public interface IUserService
    {
        User Validate(string email, string password);
        List<User> GetUserList();
        User GetUserById(int id);
        List<User> SearchByName(string name);
    }

    // UserService concrete class
    public class UserService : IUserService
    {

        private List<User> userList = new List<User>();
        public UserService()
        {
            for (var i = 1; i <= 10; i++)
            {
                userList.Add(new User
                {
                    Id = i,
                    Name = $"Username {i}",
                    Password = $"password{i}",
                    Email = $"user{i}@gmail.com",
                    Roles = new string[] { i % 2 == 0 ? "Admin" : "User" }
                });
            }
        }

        public User Validate(string email, string password)
            => userList.FirstOrDefault(x => x.Email == email && x.Password == password);

        public List<User> GetUserList() => userList;

        public User GetUserById(int id)
            => userList.FirstOrDefault(x => x.Id == id);

        public List<User> SearchByName(string name)
            => userList.Where(x => x.Name.Contains(name)).ToList();
    }
}

UserService.cs is creating list of dummy User data and inherting IUserService Interface, which requires methods like Validate to check if user exists, GetUserById and SearchByName, if you have basic understanding of Linq, you might understand GetUserById is searching user based on Id provided while SearchBYName method searches user in list by name value.

We are using above UserService class for testing purpose, because I suppose, you can create it and authenticate user from database easily.

Inside "OAuthCustomeTokenProvider" we still need to override "GrantRefreshToken", so you can add the below code inside it

 public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
        {
            var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
             newIdentity.AddClaim(new Claim("newClaim", "refreshToken"));

            var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
            context.Validated(newTicket);

            return Task.FromResult<object>(null);
        }

For security purposes, access tokens may be valid for a short amount of time. Once access token expire, client applications can use a refresh token to "refresh" the access token.

That is, a refresh token is a credential artifact that lets a client application get new access tokens without having to ask the user to log in again.

We are almost done, and we need to create just one more class "OAuthCustomRefreshTokenProvider.cs" inside "Providers" folder, so right click on "Provdiers" Folder and add new class, and use the code below

    public class OAuthCustomRefreshTokenProvider: IAuthenticationTokenProvider
    {
        // Add a static variable
        private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();

        // We will add code here
    }

Since we inherited from IAuthenticationTokenProvider interface so we need to implement following methods in this class. We will use only CreateAsync and ReceiveAsync but still we need to implement Create and Receive synchronous methods, so we will throw error from them.

Here are the methods of aboev used interface

public interface IAuthenticationTokenProvider
{
    void Create(AuthenticationTokenCreateContext context);
    Task CreateAsync(AuthenticationTokenCreateContext context);
    void Receive(AuthenticationTokenReceiveContext context);
    Task ReceiveAsync(AuthenticationTokenReceiveContext context);
}

So, after adding the required methods, our complete class will look like this

using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Infrastructure;
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;


namespace TokenBasedAuthentication.Providers
{
    public class OAuthCustomRefreshTokenProvider : IAuthenticationTokenProvider
    {
        // Add a static variable
        private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();

        public async Task CreateAsync(AuthenticationTokenCreateContext context)
        {
            var guid = Guid.NewGuid().ToString();
            /* Copy claims from previous token
             ***********************************/
            var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
            {
                IssuedUtc = context.Ticket.Properties.IssuedUtc,
                ExpiresUtc = DateTime.UtcNow.AddMinutes(40)
            };
            var refreshTokenTicket = await Task.Run(() => new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties));

            _refreshTokens.TryAdd(guid, refreshTokenTicket);

            // consider storing only the hash of the handle  
            context.SetToken(guid);
        }

        public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
        {
            AuthenticationTicket ticket;
            string header = await Task.Run(() => context.OwinContext.Request.Headers["Authorization"]);

            if (_refreshTokens.TryRemove(context.Token, out ticket))
                context.SetTicket(ticket);
        }

        public void Create(AuthenticationTokenCreateContext context)
        {
            throw new NotImplementedException();
        }
        public void Receive(AuthenticationTokenReceiveContext context)
        {
            throw new NotImplementedException();
        }


    }
}

That's it, we are done, now we can create tokens for users.

In the above code, we are expiring token after 40 minutes using these line of code

var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
            {
                IssuedUtc = context.Ticket.Properties.IssuedUtc,
                ExpiresUtc = DateTime.UtcNow.AddMinutes(40) //expire token after 40 min
            };

We are doing this for security purpose, so in the above example, user needs to get new access_token after every 40 mins.

If you have noticed, we are using dummy users like "Username 1" with "password 1", so let's build this project and run. You will see an error in browser, but that's fine, as we have not created any default view.

I have Postman installed on my pc, let's open it and try to call our OAuth API using it and get the token.

So here is what I tried in postman

postman-output-fortoken-based-authentication-c-sharp-mvc-min.png

  • URL: http://localhost:57512/token
  • Method: POST
  • Body: userName = user1@gmail.com
  • Body: password = password1
  • Body: grant_type = password

And Got the JSON response with "access_token" which is valid for 20 minutes ( 20 minutes time is set using Code in StartUp.cs AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(20)).

{
    "access_token": "eCYhgC-Hnjm_bUx0gxMx9XJvdkJj-CiTsRm4OGMF4zWNy4kIEOk9ca3bhRaa-wqJIoUBZHhmKVHzuMlL-t-4tfDstzctYgqKIqV24fZYKbYCA3HUXpwLTaysCti0-lLzCOjUFxadRnL4OtzOxZE-YQltj6z3YqYWn3mzjqwVrFOKocX5n0ZBLHQrnoEhICOZH6Wr41_opXSuLZieKJH0gFPjm5lnOLjkRkFhAE4lh_MidWBqcVX1KMeDBlWH5rvi9v1nq-Opj1GNU-HiAZfTiUVLj20h0kaEAlQurLfcONQIZGMFemCfBDinAn08yzgaflbqPbzV2CqJBtxd0yDt6XiHfkKbBMRWIYyMzYDrgLGVNsXEVUuJC_5uaODUtMT2",
    "token_type": "bearer",
    "expires_in": 1199,
    "refresh_token": "a6e63f6e-a997-4090-92e2-7cd37c75694f",
    "userName": "Username 1",
    "roles": "User",
    ".issued": "Wed, 25 Sep 2019 11:01:56 GMT",
    ".expires": "Wed, 25 Sep 2019 11:21:56 GMT"
}

How to check if our token is working?

Create a new WebAPI Controller inside Controller Folder of your project to test it.

Right-click on "Controllers"-> Select "Add"-> Select "Web API 2 Controller with read/write" -> keep the name same for testing purpose  "DefaultController"-> Click "OK"

Once you are done, add [Authorize] Attribute for this controller, so complete code for controller would be

using System.Collections.Generic;
using System.Web.Http;

namespace TokenBasedAuthentication.Controllers
{
    [Authorize]
    public class DefaultController : ApiController
    {
        // GET: api/Default
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET: api/Default/5
        public string Get(int id)
        {
            return "value";
        }

        // POST: api/Default
        public void Post([FromBody]string value)
        {
        }

        // PUT: api/Default/5
        public void Put(int id, [FromBody]string value)
        {
        }

        // DELETE: api/Default/5
        public void Delete(int id)
        {
        }
    }
}

Now try to call the " http://localhost:57512/api/default" using postman without passing token, you will get error

{
    "Message": "Authorization has been denied for this request."
}

error-token-based-no-token-authentication-min.png

As you can see we didn't passed the Token in above request, so got the error, now, let's pass the Authorisation token with api call

  • URL : http://localhost:57512/api/default
  • Method: GET
  • Header: Authorization = Bearer T-8NHXhRT....I4Rx8HRB

You will see the correct returned data, as shown in the image below

success-call-postman-api-token-based-min.png

That's it, we are done, if you have questions feel free to ask it in the comment's section.

Note: You may need to modify Refresh Token and claims code according to your project need.

You can download the demo project from here.

You may also like to read:

C# Create OR Generate Word Document using DocX

Bootstrap Pop Up Modal Validation in ASP.NET Core MVC