Introduction
This article demonstrates implementation of Client Credentials Grant Type to authorize WebAPI.This grant type would be useful in case of machine-to-machine communication and when client and resource owner are the same entity and separate user entity is not involved.
To implement this we will use Microsoft.OwinNuget Packages into ASP.NET WebAPI project to configure OAuth.
This is one of the approaches to support OAuth2.0 into legacy applications where user activity is not directly involved and client application to server application interaction has been performed. Depending on the use case and requirement, we can choose different type of grants.However, modern application generally used OpenID Connect implementation using IdentityServer4.
Related Reads
Configuring OAuth Provider Using Owin
Let’s create a sample ASP.NET WebAPI project. We are going to create two endpoints to test the token, which are /oauth2/token and api/getvalues. Client app will call “/oauth2/token” endpoint to generate access token.
Once default WebAPI project template is loaded, install Nuget packages that are required to set up our OWIN server.
- Owin.Security.OAuth
- Owin.Host.SystemWeb
Add a new folder named "Providers" to the project then add new class named "OAuthProvider", paste the below code snippet to it where "OAuthProvider" class inherits from class "OAuthAuthorizationServerProvider".
using System;
using System.Configuration;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Owin.Security.OAuth;
namespace OAuth2App.Provider {
public class OAuthProvider: OAuthAuthorizationServerProvider {
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) {
string clientId;
string clientSecret;
Guid client IdGuid;
if (!context.TryGetBasicCredentials(outclientId, outclientSecret)) {
context.TryGetFormCredentials(outclientId, outclientSecret);
}
if (null == context.ClientId || null == clientSecret || !Guid.TryParse(clientId, outclientIdGuid)) {
context.SetError("invalid_credentials", "A valid client_Id and client_Secret must be provided.");
context.Rejected();
return;
}
//validate aginstdb or config: GetClient(clientIdGuid, clientSecret);
bool is Valid Client = Configuration Manager.AppSettings["ClientId"] == clientId && Configuration Manager.AppSettings["ClientSecret"] == clientSecret;
if (!isValidClient) {
context.SetError("invalid_credentials", "A valid client_Id and client_Secret must be provided.");
context.Rejected();
return;
}
awaitTask.Run(() => context.Validated(clientId));
}
public override async Task GrantClientCredentials(OAuthGrantClientCredentialsContext context) {
GuidclientId;
Guid.TryParse(context.ClientId, outclientId);
//validate aginstdb or config: GetByClientId(clientId);
bool client = ConfigurationManager.AppSettings["ClientId"] == clientId.ToString().ToUpper();
if (!client) {
context.SetError("invalid_grant", "Invaild client.");
context.Rejected();
return;
}
var claims Identity = newClaimsIdentity(context.Options.AuthenticationType);
claims Identity.AddClaim(new Claim("LoggedOn", DateTime.Now.ToString()));
await Task.Run(() => context.Validated(claimsIdentity));
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context) {
if (context.TokenIssued) {
context.Properties.ExpiresUtc = DateTimeOffset.UtcNow.AddSeconds(3600);
}
return Task.FromResult < object > (null);
}
}
}
Here, we have overridden three methodsc alled "ValidateClientAuthentication", "GrantClientCredentials" and “TokenEndpoint”.
- The "ValidateClientAuthentication" method is responsible for validating client id and client secret against web.config or DB.Inside it, "TryGetBasicCredentials" used to retrieve the values of the client credential from basic authorization header. In addition, "TryGetFormCredentials" used to retrieve client id and secret as form-encoded POST parameters.
- The "GrantClientCredentials" method is responsible to validate the client id before adding claims details into the access token. Based on the need, we can add different sets of claims.
- The "TokenEndpoint" method is responsible to override token lifetime. If you have multiple clients, app and token lifetime varies based on the client, then this override method will be useful to set it.
Every OWIN application has a startup class. Add "OWIN Startup" class in which we specify components for the application pipeline.
Here we are using the OwinStartup Attribute to connect to the startup class with the hosting runtime.
using Microsoft.Owin;
using Microsoft.Owin.Security.OAuth;
using OAuth2App.Provider;
using Owin;
using System;
[assembly: OwinStartup(typeof(OAuth2App.Startup))]
namespace OAuth2App {
public class Startup {
public void Configuration(IAppBuilder app) {
Configure Auth(app);
}
public void ConfigureAuth(IAppBuilder app) {
var oAuthOptions = new OAuthAuthorizationServerOptions {
Allow InsecureHttp = true, // need set to false in PROD
Token EndpointPath = newPathString("/oauth2/token"),
Access Token ExpireTimeSpan = TimeSpan.FromMinutes(60), //token expiration time
Provider = new OAuthProvider(),
};
app.UseOAuthBearerTokens(oAuthOptions);
app.UseOAuthAuthorizationServer(oAuthOptions);
}
}
}
Here we created a new instance of the "OAuthAuthorizationServerOptions" class where we set token endpoint path ashttp://localhost:port/oauth2/token and token expiry time as 60 minutes.We have also specified custom class called “OAuthProvider” to validate the client. This class has already been created in earlier steps.
OAuth Client Registration
Before using OAuth, client application must be registered into authorization server.
Once the client is being registered, authorization server will provide Client ID and Client secret to client, and then the client will use this while requesting access token.
The client id is the publicly exposed string that is used by the authorization server to identify the client application. In my case, I used GUID.
The client secret must be kept private between client and authorization server and used to authenticate client app when client is requesting for user resources. In my case, I created a cryptographic string using the below code.
Random Number Generator crypto RandomDataGenerator = newRNGCryptoServiceProvider();
byte[] buffer = newbyte[32];
cryptoRandomDataGenerator.GetBytes(buffer);
string clientSecret = Convert.ToBase64String(buffer);
Creating and Authorizing an API
Now create an action method called "GetValues" and added [Authorize] attribute into it so that we can validate access token. Here, we will retrieve claims from token that we added during token generation.
[Authorize]
[HttpGet]
[Route("getvalues")]
public IHttp Action Result GetValues() {
var identity = (ClaimsIdentity) User.Identity;
var LogTime = identity.Claims.FirstOrDefault(c => c.Type == "LoggedOn").Value;
return Ok("Hi, You are Authorized! Your LoggedOn Time: " + LogTime);
} Generating Access Token from Postman and Used for API access
Aright! Now we are ready to run WebAPI application and test API from Postman.
If we access API https://localhost:44374/getvalues without access token, we will get unauthorizedresponse with code 401.
Let’s generate access token with valid credentials. Here, we will get status code 200 Ok and access token value, token type as Bearer and the token expire time in seconds in the Response section.
When requesting a token with invalid client credentials.
Now we use valid access token and add it to Authorization header while accessing API.
Sample Http Request from .NET application using Access Token
Create the following Token class, which will used to de-serialize token object.
internal class Token {
[JsonProperty("access_token")]
public string AccessToken {
get;
set;
}
[JsonProperty("token_type")]
public string TokenType {
get;
set;
}
[JsonProperty("expires_in")]
public int ExpiresIn {
get;
set;
}
}
To generate access token first, An HTTP POST request made to the URL "/oauth2/token" endpoint with grant_type parameter "client_credentials"; then we will pass this token to API access.
string base Address = "https://localhost:44374";
var client = newHttpClient();
var form = new Dictionary < string,
string > {
{
"grant_type",
"client_credentials"
},
{
"client_id",
ConfigurationManager.AppSettings["ClientId"]
},
{
"client_secret",
ConfigurationManager.AppSettings["ClientSecret"]
},
};
var tokenResponse = client.PostAsync(baseAddress + "/oauth2/token", newFormUrlEncodedContent(form)).Result;
var token = tokenResponse.Content.ReadAsAsync < Token > (new [] {
newJsonMediaTypeFormatter()
}).Result;
client.DefaultRequestHeaders.Authorization = newAuthenticationHeaderValue("Bearer", token.AccessToken);
var authorizedResponse = client.GetAsync(baseAddress + "/api/getvalues").Result;
Conclusion
In this article, we have implemented client credentials grant type using Owin packages. In addition, we have seen how we add claims into access token and retrieve the same while accessing the API. Hope you find this article short and simple! Happy Reading!