REST Web API is a light-weight essential component of web development in order to share the data across multiple client machines or devices e.g. mobile devices, desktop applications or any website. Authorization of REST Web API is an equally important part for sharing data across multiple client machines and devices in order to protect data sensitivity from any outside breaches and to authenticate the utilization of the target REST Web API.
Authorization of REST Web API can be done via a specific username/password with the combination of a secret key, but, for this type of authorization scheme, REST Web API access needs to be authenticated per call to the hosting server. Also, we as the owner of the server have no way to verify who is utilizing our REST Web API, whether it's the clients that we have allowed access to or if some malicious user is also using our API(s) without our knowledge. Finally, since the username/password is packed to a base64 format automatically by the browser, if any malicious user traces my browser activity and gets ahold of my REST Web API calls they can easily decrypt base64 format and could use my REST Web API for malicious activities.
Hmmmmm.....scary stuff! Not to mention that despite the fact that I have authorized my REST Web API, it is still open for malicious users to utilize without even my knowledge. So, what to do? To answer that a new authorization scheme is introduced which can also be utilized in Login flow of any web application as well, but, I will be focusing on it from a REST Web API perspective. So, this new scheme of authorization is OAuth 2.0 which is a token based authorization scheme.
Let's compare OAuth 2.0 authorization scheme to the traditional username/password authorization scheme from REST Web API perspective, i.e.,
| Username/Password REST Web API Authorization | VS | OAuth 2.0 REST Web API Authorization |
1. | Access Authorization to same or different REST Web API(s) on the same server is authenticated on each call with provided username/password. | Access Authorization is authenticated only once based on the system’s existing user credential, then on successful authorization, an access token is provided for a specific period of time. REST Web API(s) call can utilize the access token which server will not authenticate again and again. |
2. | REST Web API(s) call cannot track the user who is consuming the REST Web API(s). Its utilization is based on mutual trust between Producer and consumer. | Only local system users can consume the REST Web API(s), so, REST Web API call can track the user who is consuming the REST Web API. |
3. | Username/Password is encrypted in base64 format. So, hackers can easily decrypt the request headers. | Access Token is encrypted in a special format. So, hackers cannot easily decrypt it even with access to request header. |
4. | All client machines or devices code needs to be updated in case of the change in username/password for malicious activities. | Access token is activated for a specific time period. Change in system user’s credential will not require the change of code in target consumer client machines and devices. Update credential generated a new access token. |
5. | Username/Password is fixed. | Access token is generated automatically based on system user’s credential. |
Today, I shall demonstrate OAuth 2.0 mechanism to authorize a REST Web API which will also give us the benefit of [Authorize] attribute via OWIN security layer.
Following are a few prerequisites before you proceed any further,
- Knowledge of OAuth 2.0.
- Knowledge of ASP.NET MVC5.
- Knowledge of C# programming.
- Knowledge of REST Web API.
You can download the complete source code or you can follow the step by step discussion below. The sample code is developed in Microsoft Visual Studio 2015 Enterprise.
Let's begin now:
Step 1
Create a new Web API project and name it "WebApiOauth2".
Step 2
Install the following NuGet packages into your project, i.e.,
- Microsoft.Owin.Security.OAuth
- Microsoft.Owin.Cors
- Microsoft.AspNet.WebApi.Core
- Microsoft.AspNet.WebApi.Owin
Step 3
Create "DB_Oauth_API" database into your SQL Server and execute the following script on it, i.e.,
- USE [DB_Oauth_API]
- GO
- /****** Object: StoredProcedure [dbo].[LoginByUsernamePassword] Script Date: 5/10/2018 2:37:02 PM ******/
- DROP PROCEDURE [dbo].[LoginByUsernamePassword]
- GO
- /****** Object: Table [dbo].[Login] Script Date: 5/10/2018 2:37:02 PM ******/
- DROP TABLE [dbo].[Login]
- GO
- /****** Object: Table [dbo].[Login] Script Date: 5/10/2018 2:37:02 PM ******/
- SET ANSI_NULLS ON
- GO
- SET QUOTED_IDENTIFIER ON
- GO
- SET ANSI_PADDING ON
- GO
- CREATE TABLE [dbo].[Login](
- [id] [int] IDENTITY(1,1) NOT NULL,
- [username] [varchar](50) NOT NULL,
- [password] [varchar](50) NOT NULL,
- CONSTRAINT [PK_Login] PRIMARY KEY CLUSTERED
- (
- [id] ASC
- )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
- ) ON [PRIMARY]
-
- GO
- SET ANSI_PADDING OFF
- GO
- SET IDENTITY_INSERT [dbo].[Login] ON
-
- INSERT [dbo].[Login] ([id], [username], [password]) VALUES (1, N'admin', N'adminpass')
- SET IDENTITY_INSERT [dbo].[Login] OFF
- /****** Object: StoredProcedure [dbo].[LoginByUsernamePassword] Script Date: 5/10/2018 2:37:02 PM ******/
- SET ANSI_NULLS ON
- GO
- SET QUOTED_IDENTIFIER ON
- GO
-
-
-
-
-
- CREATE PROCEDURE [dbo].[LoginByUsernamePassword]
- @username varchar(50),
- @password varchar(50)
- AS
- BEGIN
- SELECT id, username, password
- FROM Login
- WHERE username = @username
- AND password = @password
- END
-
- GO
In the above script, I have created a simple login table and a stored procedure to retrieve the specific login. I am using entity framework database first approach for database connection for this ASP.NET MVC WebAPI application. Do also update your SQL server connection string in the project "Web.config" file if you have downloaded the project i.e.
Step 4
Rename "Controllers/ValueController.cs" file to "Controllers/WebApiController.cs".
Step 5
Open, "Controllers/WebApiController.cs" file replace following code,
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Net;
- using System.Net.Http;
- using System.Web.Http;
-
- namespace WebApiOauth2.Controllers
- {
- [Authorize]
- public class WebApiController : ApiController
- {
-
- public IEnumerable<string> Get()
- {
- return new string[] { "Hello REST API", "I am Authorized" };
- }
-
-
- public string Get(int id)
- {
- return "Hello Authorized API with ID = " + id;
- }
-
-
- public void Post([FromBody]string value)
- {
- }
-
-
- public void Put(int id, [FromBody]string value)
- {
- }
-
-
- public void Delete(int id)
- {
- }
- }
- }
In the above code, I have created very simple and basic REST Web API(s). Notice [Authorize] attribute is already placed at the top of the controller to make the REST Web API(s) access secure.
Step 6
Now open "App_Start/WebApiConfig.cs" file and replace the following code in it i.e.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Web.Http;
- using Microsoft.Owin.Security.OAuth;
-
- namespace WebApiOauth2
- {
- public static class WebApiConfig
- {
- public static void Register(HttpConfiguration config)
- {
-
-
- config.SuppressDefaultHostAuthentication();
- config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
-
-
- config.MapHttpAttributeRoutes();
-
- config.Routes.MapHttpRoute(
- name: "DefaultApi",
- routeTemplate: "api/{controller}/{id}",
- defaults: new { id = RouteParameter.Optional }
- );
-
-
-
- var formatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
- formatter.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
-
-
- config.Formatters.Clear();
- config.Formatters.Add(formatter);
- }
- }
- }
In the above code the following two lines of code will add authentication filter for Oauth 2.0 authorization scheme and surpass any existing authorization scheme i.e.
- surpass any existing authorization scheme i.e.
-
-
- config.SuppressDefaultHostAuthentication();
- config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
Step 7
Now, open "App_Start/Startup.Auth.cs" file and replace following code in it i.e.
- using System;
- using Microsoft.AspNet.Identity;
- using Microsoft.AspNet.Identity.Owin;
- using Microsoft.Owin;
- using Microsoft.Owin.Security.Cookies;
- using Microsoft.Owin.Security.Google;
- using Owin;
- using WebApiOauth2.Models;
- using Microsoft.Owin.Security.OAuth;
- using WebApiOauth2.Helper_Code.OAuth2;
-
- namespace WebApiOauth2
- {
- public partial class Startup
- {
- #region Public /Protected Properties.
-
-
-
-
- public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
-
-
-
-
- public static string PublicClientId { get; private set; }
-
- #endregion
-
-
- public void ConfigureAuth(IAppBuilder app)
- {
-
-
-
- app.UseCookieAuthentication(new CookieAuthenticationOptions
- {
- AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
- LoginPath = new PathString("/Account/Login"),
- LogoutPath = new PathString("/Account/LogOff"),
- ExpireTimeSpan = TimeSpan.FromMinutes(5.0),
- });
-
- app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
-
-
- PublicClientId = "self";
- OAuthOptions = new OAuthAuthorizationServerOptions
- {
- TokenEndpointPath = new PathString("/Token"),
- Provider = new AppOAuthProvider(PublicClientId),
- AuthorizeEndpointPath = new PathString("/Account/ExternalLogin"),
- AccessTokenExpireTimeSpan = TimeSpan.FromHours(4),
- AllowInsecureHttp = true
- };
-
-
- app.UseOAuthBearerTokens(OAuthOptions);
-
-
- app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
-
-
-
-
- app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- }
- }
- }
In the above piece of code, "PublicClientId" is used when "AuthorizeEndpointPath" is utilized for unique instantiation from client-side. Following lines of code will enable the OAuth 2.0 authorization scheme, i.e.
- from client side. Following lines of code will enable the OAuth 2.0 authorization scheme i.e.
-
- PublicClientId = "self";
- OAuthOptions = new OAuthAuthorizationServerOptions
- {
- TokenEndpointPath = new PathString("/Token"),
- Provider = new AppOAuthProvider(PublicClientId),
- AuthorizeEndpointPath = new PathString("/Account/ExternalLogin"),
- AccessTokenExpireTimeSpan = TimeSpan.FromHours(4),
- AllowInsecureHttp = true
- };
-
-
- app.UseOAuthBearerTokens(OAuthOptions);
OAuthAuthorizationOptions are explained as follow i.e.
- TokenEndpointPath ->
This is the path which will be called in order to authorize the user credentials and in return it will return the generated access token.
- Provider ->
You need to implement this class (which I have in this tutorial) where you will verify the user credential and create identity claims in order to return the generated access token.
- AuthorizeEndpointPath ->
In this tutorial, I am not using this property as I am not taking consent of external logins. So, if you are using external logins then you can update this path to get user consent then required access token will be generated.
- AccessTokenExpireTimeSpan ->
This is the time period you want your access token to be accessible. The shorter time span is recommended for sensitive API(s).
- AllowInsecureHttp ->
Use this property for developer environment.
More properties can be studied here.
Step 8
Now, create "Helper_Code/OAuth2/AppOAuthProvider.cs" file and replace following code in it i.e.
-
-
-
-
-
-
-
- namespace WebApiOauth2.Helper_Code.OAuth2
- {
- using Microsoft.Owin.Security;
- using Microsoft.Owin.Security.Cookies;
- using Microsoft.Owin.Security.OAuth;
- using Models;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Security.Claims;
- using System.Threading.Tasks;
- using System.Web;
-
-
-
-
- public class AppOAuthProvider : OAuthAuthorizationServerProvider
- {
- #region Private Properties
-
-
-
-
- private readonly string _publicClientId;
-
-
-
-
- private Oauth_APIEntities databaseManager = new Oauth_APIEntities();
-
- #endregion
-
- #region Default Constructor method.
-
-
-
-
-
- public AppOAuthProvider(string publicClientId)
- {
-
- if (publicClientId == null)
- {
- throw new ArgumentNullException(nameof(publicClientId));
- }
-
-
- _publicClientId = publicClientId;
- }
-
- #endregion
-
- #region Grant resource owner credentials override method.
-
-
-
-
-
-
- public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
- {
-
- string usernameVal = context.UserName;
- string passwordVal = context.Password;
- var user = this.databaseManager.LoginByUsernamePassword(usernameVal, passwordVal).ToList();
-
-
- if (user == null || user.Count() <= 0)
- {
-
- context.SetError("invalid_grant", "The user name or password is incorrect.");
-
-
- return;
- }
-
-
- var claims = new List<Claim>();
- var userInfo = user.FirstOrDefault();
-
-
- claims.Add(new Claim(ClaimTypes.Name, userInfo.username));
-
-
- ClaimsIdentity oAuthClaimIdentity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
- ClaimsIdentity cookiesClaimIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationType);
-
-
- AuthenticationProperties properties = CreateProperties(userInfo.username);
- AuthenticationTicket ticket = new AuthenticationTicket(oAuthClaimIdentity, properties);
-
-
- context.Validated(ticket);
- context.Request.Context.Authentication.SignIn(cookiesClaimIdentity);
- }
-
- #endregion
-
- #region Token endpoint override method.
-
-
-
-
-
-
- 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);
- }
-
- #endregion
-
- #region Validate Client authntication override method
-
-
-
-
-
-
- public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
- {
-
- if (context.ClientId == null)
- {
-
- context.Validated();
- }
-
-
- return Task.FromResult<object>(null);
- }
-
- #endregion
-
- #region Validate client redirect URI override method
-
-
-
-
-
-
- public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
- {
-
- if (context.ClientId == _publicClientId)
- {
-
- Uri expectedRootUri = new Uri(context.Request.Uri, "/");
-
-
- if (expectedRootUri.AbsoluteUri == context.RedirectUri)
- {
-
- context.Validated();
- }
- }
-
-
- return Task.FromResult<object>(null);
- }
-
- #endregion
-
- #region Create Authentication properties method.
-
-
-
-
-
-
- public static AuthenticationProperties CreateProperties(string userName)
- {
-
- IDictionary<string, string> data = new Dictionary<string, string>
- {
- { "userName", userName }
- };
-
-
- return new AuthenticationProperties(data);
- }
-
- #endregion
- }
- }
In the above code, "GrantResourceOwnerCredentials(...)" method is the key method which is called when TokenEndpointPath is called. Notice that "GrantResourceOwnerCredentials(...)" method is used by "grant_type=password" scheme. If you are using "grant_type=client_credentials" scheme then you need to override "GrantClientCredentials(...)" method. Other methods are part of "OAuthAuthorizationServerProvider" class, use them as they are. In "GrantResourceOwnerCredentials(...)" method we are verifying the system login user and then create the require identities claims and then generate the returning access token ticket i.e.
- #region Grant resource owner credentials override method.
-
-
-
-
-
-
- public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
- {
-
- string usernameVal = context.UserName;
- string passwordVal = context.Password;
- var user = this.databaseManager.LoginByUsernamePassword(usernameVal, passwordVal).ToList();
-
-
- if (user == null || user.Count() <= 0)
- {
-
- context.SetError("invalid_grant", "The user name or password is incorrect.");
-
-
- return;
- }
-
-
- var claims = new List<Claim>();
- var userInfo = user.FirstOrDefault();
-
-
- claims.Add(new Claim(ClaimTypes.Name, userInfo.username));
-
-
- ClaimsIdentity oAuthClaimIdentity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
- ClaimsIdentity cookiesClaimIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationType);
-
-
- AuthenticationProperties properties = CreateProperties(userInfo.username);
- AuthenticationTicket ticket = new AuthenticationTicket(oAuthClaimIdentity, properties);
-
-
- context.Validated(ticket);
- context.Request.Context.Authentication.SignIn(cookiesClaimIdentity);
- }
-
- #endregion
Step 9
Now, execute the project and use the following link in the browser to see your newly created REST Web API method in action as follows:
In the above snippet, you will notice that since now our REST Web API has been authorized, therefore, we cannot directly execute the REST Web API URL in the browser.
Step 10
Let's test out REST Web API in REST Web API client. I am using Firefox plugin i.e. "RESTED". At, first, I simply try to hit the REST Web API without any authorization details and I will get following response i.e.
![]()
Step 11
Now, I will provide the system user authorization to get access token and then use that access token as a header in the REST Web API and try to his the REST Web API which will return the following response, i.e.
![]()
Notice in the above snippets that access token is provided as "Authorization" header with "Bearer access_token" scheme in order to call the REST Web API. Also, notice the path when the token is being generated i.e. "{your_site_url}/Token".
Conclusion