Tuesday 30 November 2021

Microservice API using gRPC on .NET

 gRPC is a hot cake now for Microservices development. it is a high-performance, lightweight RPC framework for Microservices development. It uses Contract-first API development, using Protocol Buffers by default, allowing for language agnostic implementations. Protobuf (Protocol Buffers) binary serialization reduces the network usage which makes it highly performative.

gRPC is based on Remote Procedure Call (RPC) pattern, uses HTTP/2 protocol for communication and uses three basic concepts Channel, Remote Procedure calls (streams) and Messages.

A channel can have multiple RPCs (streams) and a stream is a collection of many messages.

Like gRPC, Windows Communication Framework (WCF) also based on Remote Procedure Call (RPC) pattern but gRPC and WCF differs in many ways. Lets see how they differ:

How to create gRPC Service

syntax = "proto3";import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
option csharp_namespace = "GrpcServiceDemo";package weatherForcast;// The weather forecast service definition.
service WeatherForcast {
// Get weather forecast
rpc GetWeatherForecast (google.protobuf.Empty) returns (WeatherForecastReply);
rpc GetWeatherForecastForDate (google.protobuf.Timestamp) returns (WeatherForecastReply);}// The response message containing the weather information.
message WeatherForecastReply {
repeated WeatherForecast Result = 1;
}
message WeatherForecast {
google.protobuf.Timestamp Date = 1;

int32 TemperatureC = 2;

int32 TemperatureF = 3;

string Summary = 4;
}
<ItemGroup>
<Protobuf Include="Protos\weatherForecast.proto" GrpcServices="Server" />
</ItemGroup>
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
using GrpcServiceDemo;
namespace GrpcServiceDemo.Services
{
public interface IWeatherForecastService
{
Task<WeatherForecastReply> GetWeatherForecast(ServerCallContext context);
Task<WeatherForecastReply> GetWeatherForecastForDate(Timestamp date, ServerCallContext context); }
}
using Grpc.Core;
using GrpcServiceDemo;
using Google.Protobuf.WellKnownTypes;
namespace GrpcServiceDemo.Services
{
public class WeatherForecastService : IWeatherForecastService
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastService> _logger;public WeatherForecastService(ILogger<WeatherForecastService> logger)
{
_logger = logger;
}
public Task<WeatherForecastReply> GetWeatherForecast(ServerCallContext context)
{
return Task.FromResult<WeatherForecastReply>(GetWeather());
}
public Task<WeatherForecastReply> GetWeatherForecastForDate(Timestamp date, ServerCallContext context)
{
return Task.FromResult<WeatherForecastReply>(GetWeather(date));
}
private WeatherForecastReply GetWeather()
{
var result = new WeatherForecastReply();
for (var index = 1; index <= 5; index++)
{
result.Result.Add(
new WeatherForecast
{
Date = Timestamp.FromDateTime(DateTime.UtcNow.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)],
TemperatureF = (int)(32 + (Random.Shared.Next(-20, 55) / 0.5556))
}
);
}
return result;
}
private WeatherForecastReply GetWeather(Timestamp date)
{
var result = new WeatherForecastReply();
result.Result.Add(
new WeatherForecast
{
Date = date,
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)],
TemperatureF = (int)(32 + (Random.Shared.Next(-20, 55) / 0.5556))
}
);
return result;
}
}
}
using Grpc.Core;
using Google.Protobuf.WellKnownTypes;
namespace GrpcServiceDemo.Services
{
public sealed class WeatherForecastGrpcService : WeatherForcast.WeatherForcastBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastService> _logger;
private readonly IWeatherForecastService _weatherForecastService;
public WeatherForecastGrpcService(ILogger<WeatherForecastService> logger, IWeatherForecastService weatherForecastService)
{
_logger = logger;
_weatherForecastService = weatherForecastService;
}
public override Task<WeatherForecastReply> GetWeatherForecast(Empty request, ServerCallContext context)
{
return _weatherForecastService.GetWeatherForecast(context);
}
public override Task<WeatherForecastReply> GetWeatherForecastForDate(Timestamp date, ServerCallContext context)
{
return _weatherForecastService.GetWeatherForecastForDate(date, context);
}
}
}
using GrpcServiceDemo.Services;var builder = WebApplication.CreateBuilder(args);// Add services to the container.
builder.Services.AddGrpc();
builder.Services.AddTransient<IWeatherForecastService, WeatherForecastService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.MapGrpcService<WeatherForecastGrpcService>();
app.Run();

How to create gRPC Service Client

<ItemGroup>
<Protobuf Include="Protos\weatherForecast.proto" GrpcServices="Client" />
</ItemGroup>
using Google.Protobuf.WellKnownTypes;
using Grpc.Net.Client;
using GrpcServiceDemo;
// The port number must match the port of the gRPC server.
using var channel = GrpcChannel.ForAddress("https://localhost:7001");
var weatherClient = new WeatherForcast.WeatherForcastClient(channel);var request = new Google.Protobuf.WellKnownTypes.Empty();
var weatherInfo = weatherClient.GetWeatherForecast(request);
Console.WriteLine("*****Weather forecast for 5 days*****");
Console.WriteLine(weatherInfo.Result);
weatherInfo = weatherClient.GetWeatherForecastForDate(Timestamp.FromDateTime(DateTime.UtcNow));
Console.WriteLine("*****Weather forecast for today*****");
Console.WriteLine(weatherInfo.Result);
request = new Google.Protobuf.WellKnownTypes.Empty();
weatherInfo = await weatherClient.GetWeatherForecastAsync(request);
Console.WriteLine("*****Weather forecast for 5 days*****");
Console.WriteLine(weatherInfo.Result);
weatherInfo = await weatherClient.GetWeatherForecastForDateAsync(Timestamp.FromDateTime(DateTime.UtcNow));
Console.WriteLine("*****Weather forecast for today*****");
Console.WriteLine(weatherInfo.Result);
Console.ReadKey();

Monday 22 November 2021

JWT vs OAuth2.0 — Wants to use to secure APIs but in dilemma?

 Are you too getting through this dilemma for you Web App or APIs? It’s 11:20 PM IST and with this article I’m trying to help you to understand about JWT and OAuth. Lets understand both from the definition first as they say:

OAuth 2.0 is the industry-standard protocol for authorization. OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, desktop applications, mobile phones, and living room devices. In short OAuth is token based authentication protocol.

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

So here we know the difference as, OAuth 2.0 is a protocol for authorization and JWT is an standard for defining a token. Now we come to conclusion of getting the authorization done in two way.

  1. Token Based authentication — access token, JWT
  2. OAuth 2.0

If I need to differentiate both them here, I would OAuth takes care of only authorization without knowing the identity of who is using it. Take and example of having Access Card (a key) to enter into a office premises, just swipe it and and you are authorize to access it. No matter who is using the access card, it is either you or someone else if lost the access card… boom boom boom.

And JWT is like, you went to office security desk and the security guy took you photo and an Identity and issued then issued a access card (a key with limited access) by feeding all your information to it. So now when you swipe, system know who is person and what he can access…

Is that mean, OAuth 2.0 implementation shouldn’t be considered as it does only Authorization without the identity (authentication)? Of course not, Never forget that OAuth 2.0 is a industry-standard protocol accepted worldwide. So how to fill the gap of identity verification with OAuth 2.0 implementation then and answer is “OpenID Connect”.

OpenID Connect is a simple identity layer on top of OAuth 2.0 protocol, which allows clients to verify the identity of an end-user based on the authentication performed by an authorization server, as well as to obtain basic profile information about the end user.

In Today’s world we have IdentityServer4 as an OpenID Connect and OAuth 2.0 framework for ASP.NET Core to build our identity server to get the access token and validate the token. Please visit the IdentityServer4 to know more about it.

Hope by now the concept about JWT and OAuth 2.0 is clear. Now we can go ahead with the example of implementing Authentication & Authorization using IdentityServer4 and JWT with ASP.NET core web api .

Note: In my example I’m using .Net 5

IdentityServer4 Implementation with ASP.NET Core.

First we need a IdentityServer which can issue/verify the token so I’m gonna create a ASP.NET web API project which will be a demo identity server as well as it also hosts secured APIs. Below are steps:

Step 1: Create a ASP.NET Core Web API Project and install two packages.
i. IdentityServer 4 => this is for demo identity server
ii. IdentityServer 4.AccessTokenValidation => for validating token issued by identity server.

Also create a folder name “IdentityServer4” which will hold the code related to Identity Server configuration like clients info, demo test users, api scopes etc.

Step 2: Add below code for Config.cs and Users.cs

using IdentityServer4.Models;
using Microsoft.Extensions.Configuration;
using System.Collections.Generic;
namespace IdentityServer4APIDemo.IdentityServer4
{
public class Config
{
private IConfiguration _configuration;
public Config(IConfiguration configuration) => _configuration = configuration;
public static IEnumerable<ApiScope> ApiScopes =>
new List<ApiScope>
{
new ApiScope("demoapi.read", "Read Access to Weather API"),
new ApiScope("demoapi.write", "Write Access to Weather API"),
};
public static IEnumerable<ApiResource> ApiResources =>
new List<ApiResource>
{
new ApiResource
{
Name = "demoapi",
DisplayName = "Demo API",
Scopes = new List<string> { "demoapi.read", "demoapi.write"},
ApiSecrets = new List<Secret> {new Secret("1234567890123456789".Sha256())},
UserClaims = new List<string> {"admin"}
}
};
public static IEnumerable<Client> Clients =>
new List<Client>
{
new Client
{
ClientId = "demoapi",
ClientSecrets = { new Secret("1234567890123456789".Sha256()) },
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
// scopes that client has access to
AllowedScopes = { "demoapi.read", "demoapi.write"},
// RequirePkce =false
}
};
}
}

Above config file has the details of clients (register the applications with IdentityServer4 that are allowed to use it. ), API Resources (used to define the API that the identity server is protecting) and API scope (register API Scopes for the IdentityServer4) and below class has some demo test users. This code is only for demonstration purpose and in real production none of the above data like clients, secrets or users will be hardcoded and even identity server would be a separated hosted service.

using IdentityModel;
using IdentityServer4.Test;
using System.Collections.Generic;
using System.Security.Claims;
namespace IdentityServer4APIDemo.IdentityServer4
{
public class Users
{
public static List<TestUser> Get()
{
return new List<TestUser>
{
new TestUser
{
SubjectId = "123456786",
Username = "admin",
Password = "password",
Claims = new List<Claim>
{
new Claim(JwtClaimTypes.Email, "binodk.mahto@gmail.com"),
new Claim(JwtClaimTypes.Role, "admin"),
new Claim(JwtClaimTypes.WebSite, "https://localhost:5001")
}
},
new TestUser
{
SubjectId = "123456787",
Username = "user1",
Password = "user1",
Claims = new List<Claim>
{
new Claim(JwtClaimTypes.Email, "binodk.mahto@gmail.com"),
new Claim(JwtClaimTypes.Role, "user"),
new Claim(JwtClaimTypes.WebSite, "https://localhost:5001")
}
},
new TestUser
{
SubjectId = "123456788",
Username = "user2",
Password = "user2",
Claims = new List<Claim>
{
new Claim(JwtClaimTypes.Email, "binodk.mahto@gmail.com"),
new Claim(JwtClaimTypes.Role, "user"),
new Claim(JwtClaimTypes.WebSite, "https://localhost:5001")
}
}
};
}
}
}

Step 3: Add below code to Startup.ConfigureServices method.

services.AddIdentityServer()
.AddInMemoryClients(Config.Clients)
.AddInMemoryApiResources(Config.ApiResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddTestUsers(Users.Get())
.AddDeveloperSigningCredential();

Step 4: At last Add the Identity Server to pipeline through Startup.Configure() method, just before the app.UseEndpoints() pipeline.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "IdentityServer4APIDemo v1"));
}
app.UseHttpsRedirection(); app.UseRouting(); app.UseIdentityServer(); app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();//.RequireAuthorization("demoapiScope"); ; ;
});
}

Identity Server is ready now, to test the endpoints provided by Identity Server hit the below url through postman or browser: http://localhost:5000/.well-known/openid-configuration . Here the out would be:

Above confirms that identity server is up and running and if can see the endpoints to get the token with other operations which you can perform.

Now let’s try to get the token.

Your response will have access token to use for further call.

Now lets add secured APIs using this token. You may create a new project but here I’m gonna add APIs in the same project. For this we need to do:

Steps 1: Add a controller, going for default template of API.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace IdentityServer4APIDemo.Controllers
{
[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
}

Step 2: Register services and IdentityServer authentication handler through Startup.ConfigureServices method.

services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication("Bearer", options =>
{
options.ApiName = "demoapi";
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
});

Boom we are ready. You can test the code by running it. In case of you are publishing this to host in IIS then don’t forget to use the app pool identity as LocalSystem otherwise you may get access issue for tempkey.jwk (stores key used for JWT token signing and validation).

Download the full project from here: https://github.com/binodmahto/FunProjects/tree/main/IdentityServer4APIDemo

JWT Authentication Implementation with ASP.NET Core

To use the JWT authentication to secure web APIs follow the below steps. Below steps are again for demonstration purpose based on my learning and in production you have to arrange it.

Step 1: Create a ASP.NET Core Web API project and install the packages “Microsoft.AspNetCore.Authentication.JwtBearer”, “System.IdentityModel.Tokens.Jwt” and “Newtonsoft.Json”.

Step 2: Add the services related to JWT Authentication from Startup.ConfigureServices() method

var jwtTokenConfig = Configuration.GetSection("jwtTokenConfig").Get<JwtTokenConfig>();
services.AddSingleton(jwtTokenConfig);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.RequireHttpsMetadata = true;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtTokenConfig.Issuer,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtTokenConfig.Secret)),
ValidAudience = jwtTokenConfig.Audience,
ValidateAudience = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(1)
};
});

Step 3: Create APIs to get token and services related to it for valdating the token etc.
Since the code is little bigger hence please download the code from here:

This project also has the code coverage of my other stories Middleware and Filters power in ASP.NET Core and Use multiple implementations of an interface with ASP.NET Core DI. So for this demonstration please focus on below highlighted folders/files.

Download the code from here: https://github.com/binodmahto/FunProjects/tree/main/CoreWebAPIDemo

Hope above helps you to understand the concept about JWT and OAuth 2.0. For more details I would recommend to visit:

https://jwt.io/introduction\
https://oauth.net/2/jwt/
https://identityserver4.readthedocs.io/en/latest/

Thank you reading. Don’t forget to clap if you like and leave comments for suggestion.