Introduction
In this article, we are going to learn about how to secure our Azure function serverless APIs using JWT(JSON Web Tokens) &.Net 6. When we are building these functions, we generally have to take care of the authentication and authorization because every API has to be authorized before pulling or pushing the data from the system/server. In this article, we are using one standard custom authentication for our Azure functions using JWT.
If you want to know more about Azure Functions do check out this amazing article
Prerequisites
Setting up our Azure Function Project
Once you opened the Visual Studio search for Azure function in the search bar as shown in the below image
In the next step it asks us to create a project name and its location to save the project and along with the function name
Once you clicks on the create it takes you to the next section where we have to select the template and framework and the azure function authorization level. So as we already downloaded and installed the .Net 6 SDK(Software Development Kit). It shows the .Net 6 in the Dropdown we have to choose that and make sure to select Authorization level as Anonymous and then click on create.
Sample Function Performing the Credential Check
The credential check is the first thing to do. This simply implies that we compare a set of credentials we've received to a set of credentials saved in a custom database or in a service/protocol other than App Services' regular ones. In this example, we'll send a JSON object to our function as an argument and just return true each time.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
namespace AzureFuntions_Auth_JWT {
public static class Function1 {
[FunctionName(nameof(UserAuthenication))]
public static async Task < IActionResult > UserAuthenication(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "auth")] UserCredentials userCredentials, ILogger log) {
log.LogInformation("C# HTTP trigger function processed a request.");
// TODO: Perform custom authentication here; we're just using a simple hard coded check for this example
bool authenticated = userCredentials?.User.Equals("Jay", StringComparison.InvariantCultureIgnoreCase) ?? false;
if (!authenticated) {
return await Task.FromResult(new UnauthorizedResult()).ConfigureAwait(false);
} else {
return await Task.FromResult(new OkObjectResult("User is Authenticated")).ConfigureAwait(false);
}
}
}
public class UserCredentials {
public string User {
get;
set;
}
public string Password {
get;
set;
}
}
}
Run & Test this API response in Postman
Once you run this function it will run using the storage emulator and we can see the respective API URL in the emulator as shown in the fig below.
Copy that URL and open postman to check the response from that Endpoint.
As per the above image, it validates the user credentials and sends back the response with output as we hardcoded the values for the demo purpose you can actually work as per your need.
Generate JWT
JSON Web Tokens are nothing more than a way to send JSON strings securely. We may simply retrieve the appropriate information about the user and encrypt it into a token, which we deliver to the client as a cookie, rather than having the user transfer credentials back and forth on each call and then validating the user's credentials over and over. When the client makes further calls, the JWT is passed back to the application, which decrypts the contents and verifies that they are legitimate.
Because the token is given with each request, you'll want to be careful not to add unnecessary information, such as the username, user ID, user email, and role(s). I have created a separate class named GenerateJWTToken for issuing JWT per request.
To handle JWT in Azure Functions we need a package, Please find the nuget library below
GenerateJWTToken.cs
using JWT;
using JWT.Algorithms;
using JWT.Serializers;
using System.Collections.Generic;
namespace AzureFuntions_Auth_JWT {
/// <summary>
/// Service class for performing authentication.
/// </summary>
public class GenerateJWTToken {
private readonly IJwtAlgorithm _algorithm;
private readonly IJsonSerializer _serializer;
private readonly IBase64UrlEncoder _base64Encoder;
private readonly IJwtEncoder _jwtEncoder;
public GenerateJWTToken() {
// JWT specific initialization.
_algorithm = new HMACSHA256Algorithm();
_serializer = new JsonNetSerializer();
_base64Encoder = new JwtBase64UrlEncoder();
_jwtEncoder = new JwtEncoder(_algorithm, _serializer, _base64Encoder);
}
public string IssuingJWT(string user) {
Dictionary < string, object > claims = new Dictionary < string, object > {
// JSON representation of the user Reference with ID and display name
{
"username",
user
},
// TODO: Add other claims here as necessary; maybe from a user database
{
"role",
"admin"
}
};
string token = _jwtEncoder.Encode(claims, "Your Secret Securtity key string"); // Put this key in config
return token;
}
}
}
Now let's consume this JWT Code in the Functional Code to get the token on basis of User credentials and attach that token in the response where that token can be consumed to get access for other endpoints for this we need to modify the existing API lets make those changes and check that response in the postman.
We just created an object for GenerateJWTToken Class to access that IssuingJWT Function which you can see in line number 27 & 28 in the code below
Function1.cs
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
namespace AzureFuntions_Auth_JWT {
public static class Function1 {
[FunctionName(nameof(UserAuthenication))]
public static async Task < IActionResult > UserAuthenication(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "auth")] UserCredentials userCredentials, ILogger log) {
log.LogInformation("C# HTTP trigger function processed a request.");
// TODO: Perform custom authentication here; we're just using a simple hard coded check for this example
bool authenticated = userCredentials?.User.Equals("Jay", StringComparison.InvariantCultureIgnoreCase) ?? false;
if (!authenticated) {
return await Task.FromResult(new UnauthorizedResult()).ConfigureAwait(false);
} else {
GenerateJWTToken generateJWTToken = new();
string token = generateJWTToken.IssuingJWT(userCredentials.User);
return await Task.FromResult(new OkObjectResult(token)).ConfigureAwait(false);
}
}
}
public class UserCredentials {
public string User {
get;
set;
}
public string Password {
get;
set;
}
}
}
Run and Test the same API in the postman again
In the next step we have to validate the JWT Token which we have generated using the auth API and now using the same token let's access another API for that we need to Validate the token by using the Key and username let's hook it up the code now.
ValidateJWT.cs
using JWT.Algorithms;
using JWT.Builder;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
namespace AzureFuntions_Auth_JWT {
public class ValidateJWT {
public bool IsValid {
get;
}
public string Username {
get;
}
public string Role {
get;
}
public ValidateJWT(HttpRequest request) {
// Check if we have a header.
if (!request.Headers.ContainsKey("Authorization")) {
IsValid = false;
return;
}
string authorizationHeader = request.Headers["Authorization"];
// Check if the value is empty.
if (string.IsNullOrEmpty(authorizationHeader)) {
IsValid = false;
return;
}
// Check if we can decode the header.
IDictionary < string, object > claims = null;
try {
if (authorizationHeader.StartsWith("Bearer")) {
authorizationHeader = authorizationHeader.Substring(7);
}
// Validate the token and decode the claims.
claims = new JwtBuilder().WithAlgorithm(new HMACSHA256Algorithm()).WithSecret("Your Secret Securtity key string").MustVerifySignature().Decode < IDictionary < string, object >> (authorizationHeader);
} catch (Exception exception) {
IsValid = false;
return;
}
// Check if we have user claim.
if (!claims.ContainsKey("username")) {
IsValid = false;
return;
}
IsValid = true;
Username = Convert.ToString(claims["username"]);
Role = Convert.ToString(claims["role"]);
}
}
}
Let's create another endpoint which will have to accept authentication in order to fetch the data from the system and in the output we are passing the JSON object of the user credentials after successful authentication pass. In this API actually, we were validating the API with our Credentials once its passes then we are allowed to send the success response as the output.
GetData
[FunctionName(nameof(GetData))]
public static async Task < IActionResult > GetData(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "data")] HttpRequest req, ILogger log) {
// Check if we have authentication info.
ValidateJWT auth = new ValidateJWT(req);
if (!auth.IsValid) {
return new UnauthorizedResult(); // No authentication info.
}
string postData = await req.ReadAsStringAsync();
return new OkObjectResult($ "{postData}");
}
Run and test the API in Postman
That’s all there is to it!
Source Code - GItHub
Keep Learning.....!