Introduction
Series : Token based authentication using ASP.NET Web API in AngularJS
- Part 1 : Token based authentication in ASP.NET Web API
- Part 2 : Token Based Authentication Using ASP.NET Web API in AngularJS
Today I am going to show you how to Secure ASP.NET Web API using Token Based Authentication.
ASP.NET Web API is a framework that makes it easy to build HTTP services that reach a broad range of clients, including browsers, mobile devices, and traditional desktop applications. Nowadays Web API adoption is increasing at a rapid pace. So it's very essential to implement security for all types of clients trying to access data from Web API services.
Nowadays the most preferred approach to secure server resources by authenticating users in WEB API is to use signed token, which contains enough data to identify a particular user.This is called token-based approach. This is because of following reason:
- Loose Coupling - The client application is not tied to a particular authentication scheme. The token is generated, validated and perform the authentication by the server.
- Mobile Friendly - In native platform like iOS, Android, Windows 8 etc. handling cookies are not an easy task. Token-based approach simplifies this a lot.
Follow the following steps in order to implement "Part 1 : Token based authentication using ASP.NET Web API 2".
Step - 1: Create New Project.
It will bring up a new dialog window for select template > here I will select empty template > and then checked MVC & Web API checkbox from Add folder and core references for > and then click on Ok button.
Step-2: Add required references from NuGet packages into our application.
- Microsoft.Owin.Host.SystemWeb
- Microsoft.Owin.Security.OAuth
- Microsoft.Owin.Cors
Step-3: Add a class for validating user credentials asking for tokens.
Now we will add a class in our application for validate the credentials for users and generate token.For adding the class, go to solution explorer > Right click on your application name > add > New Item...> here we will select class. enter your class name> click on Add button.
In this class we will inherit "OAuthAuthorizationServerProvider" class for overriding 2 methods "ValidateClientAuthentication" and "GrantResourceOwnerCredentials". "ValidateClientAuthentication" method is used for validating client app (for the sake of simplicity, we will deep dive on "ValidateClientAuthentication" method later) and in the "GrantResourceOwnerCredentials" method we will validate the credentials of users and if we found valid credential, we will generate the signed token, using which user can access authorized resources of server.
MyAuthorizationServerProvider.cs
using Microsoft.Owin.Security.OAuth; using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using System.Web; namespace webApiTokenAuthentication { public class MyAuthorizationServerProvider : OAuthAuthorizationServerProvider { public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { context.Validated(); // } public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) { var identity = new ClaimsIdentity(context.Options.AuthenticationType); if (context.UserName == "admin" && context.Password == "admin") { identity.AddClaim(new Claim(ClaimTypes.Role, "admin")); identity.AddClaim(new Claim("username", "admin")); identity.AddClaim(new Claim(ClaimTypes.Name, "Sourav Mondal")); context.Validated(identity); } else if (context.UserName == "user" && context.Password == "user") { identity.AddClaim(new Claim(ClaimTypes.Role, "user")); identity.AddClaim(new Claim("username", "user")); identity.AddClaim(new Claim(ClaimTypes.Name, "Suresh Sha")); context.Validated(identity); } else { context.SetError("invalid_grant", "Provided username and password is incorrect"); return; } } } }
You can see in this (line-18 to line 40), I have used static data for validating user credential. later part of this series I will validate user from our database when we will implement client application (in AngularJS) for token based authentication.
Step-4: Add Owin Start Up class.
Go to Solution Explorer > Right Click on Project Name form Solution Explorer > Add > New Item > Select OWIN Startup class > Enter class name > Add.
Startup.cs
using System; using System.Threading.Tasks; using Microsoft.Owin; using Owin; using Microsoft.Owin.Security.OAuth; using System.Web.Http; [assembly: OwinStartup(typeof(webApiTokenAuthentication.Startup))] namespace webApiTokenAuthentication { public class Startup { public void Configuration(IAppBuilder app) { // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888 //enable cors origin requests app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll); var myProvider = new MyAuthorizationServerProvider(); OAuthAuthorizationServerOptions options = new OAuthAuthorizationServerOptions { AllowInsecureHttp = true, TokenEndpointPath = new PathString("/token"), AccessTokenExpireTimeSpan = TimeSpan.FromDays(1), Provider = myProvider }; app.UseOAuthAuthorizationServer(options); app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()); HttpConfiguration config = new HttpConfiguration(); WebApiConfig.Register(config); } } }
Step-5: Add an another Class for override authorize attribute.
Unfortunately, the ASP.NET MVC/Web API [Authorize] attribute doesn’t behave that way – it always emits 401. So, here in our Web API application, I am going to add a class for override this behavior. Here we will return 403 when the user is authenticated but not authorized to perform the requested operation.
AuthorizeAttribute.cs
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace webApiTokenAuthentication { public class AuthorizeAttribute : System.Web.Http.AuthorizeAttribute { protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext) { if (!HttpContext.Current.User.Identity.IsAuthenticated) { base.HandleUnauthorizedRequest(actionContext); } else { actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Forbidden); } } } }
Step-6: Add WEB API Controller.
Go to Solution Explorer > Right click on Controllers folder > Add > Controller > Select WEB API 2 Controller - Empty > Click on add button. > Enter controller name (in my case It's DataController.cs) > Add.
Step-7: Add an action for getting data from the server for all anonymous user.
[AllowAnonymous] [HttpGet] [Route("api/data/forall")] public IHttpActionResult Get() { return Ok("Now server time is: " + DateTime.Now.ToString()); }
Step-8: Add an another action for getting data from the server for all authenticated user.
[Authorize] [HttpGet] [Route("api/data/authenticate")] public IHttpActionResult GetForAuthenticate() { var identity = (ClaimsIdentity)User.Identity; return Ok("Hello " + identity.Name); }
Step-9: Add an another action for getting data from the server only for Admin user.
[Authorize(Roles="admin")] [HttpGet] [Route("api/data/authorize")] public IHttpActionResult GetForAdmin() { var identity = (ClaimsIdentity)User.Identity; var roles = identity.Claims .Where(c => c.Type == ClaimTypes.Role) .Select(c => c.Value); return Ok("Hello " + identity.Name + " Role: " + string.Join(",", roles.ToList())); }
Step-10: Run Application.
Postman is an extension of Chrome, which is used as a client application to test the request and response between web service and client.
When you will now your application, you will get "The resource cannot be found" error message because we don't have anything in our root url now. But don't worry, we will do later.
What we need to do now, we have to open our POSTMAN for test our Web API. After that the followings link...
Test 1:
Select GET (see below picture section 1), Enter this url http://localhost:
We will get 200 OK status code (see section 3 in the below picture) and the result in the section 4 in that picture. That means our first action working fine when the request is anonymous.
But now if we try to access our 2nd action (GetForAuthenticate with url : http://localhost:
So, what we need to access the 2nd and 3rd action? We need access token from server first and then we can access the 2nd and 3rd action with that access token.
Test 2 : Getting access token.
Select POST (in section 1), Enter this URL http://localhost:
Now we can access http://localhost:
Test 3: Access restricted resource with access token.
Select GET(in section 1), Enter this URL http://localhost:
In the same way, we can access our 3rd action but we have to get token logged in with username : admin and password: admin because the 3rd action accessible only for Admin role user.