Securing C#/.NET WebAPI with public-private-key-signed JWTs signed by NodeJS
Tags: jwt, c, node
Published: 2018-04-26 22:40:42

---
title: Securing C#/.NET WebAPI with public-private-key-signed JWTs signed by NodeJS
published: true
description: 
tags: JWT, C#, NodeJS
---
*This article was cross-posted on [Medium](https://medium.com/@kevinle/securing-c-net-webapi-with-public-private-key-signed-jwts-signed-by-nodejs-4fd60d97b7f4)*

In this article, I will show how to implement and secure a C#/.NET (hereinafter I will only say C#) WebAPI. To secure the WebAPI, we will use JWT.  The JWT is signed by a NodeJS backend using Private Key. The WebAPI will verify the JWT using the Public Key.

I'd like to be clear so let me clarify some the terminologies that I prefer to use. When I say `client`, I mean a client application such as mobile app, a web application, Postman, etc. On the other had, a `user` is a human who uses those `clients`. When a `client` sends a login request to the server, it's actually doing it on behalf of `user` who enters his/her name on the mobile app and tab Submit button. 

So with that, the client first makes the request to `/login` endpoint of the NodeJS server.  This NodeJS server is the Authorization Server. Its job is to issue JWT if the login is correct. Assume it is, once the client obtains the JWT, the client can store this JWT in memory, or in local storage or cookie or elsewhere. Now the client wants to access the resources provided by the C# WebAPI. So when it sends a request, it includes a JWT in the `Authorization` attribute of the request header. The C# WebAPI is the Resource Server or Provider. Its job is to provide resources. But it only does so if it can verify the JWT.

In a sequence diagram:

![Sequence diagram](https://thepracticaldev.s3.amazonaws.com/i/882vk6pv0fayuvr7hbx0.png)

The Authorization Server (NodeJS) and the Resource Provider (C# WebAPI) can run on 2 totally different servers or clouds. Instead of using public private key to sign and verify the JWT like in his article, we could also have used a shared secret that is known by both the Authorization Server (NodeJS) and the Resource Provider (C# WebAPI). However, the `shared secret` approach is not as effective as the `public private key` approach for the following reasons.

1. There are 2 potential points of failure instead of 1. Either the Authorization Server or the Resource Provider could compromise the shared secret. On the other hand, the private key can still be compromised, but there's only one entity that knows about the private key.

2. If there are multiple Resource Providers, sharing 1 secret only increases the number of potential points of failure. 

3. Having a different secret for each Resource Provider is an option, but in some cases we don't have control of the Resource Provider, then we have to deal with the problem of distributing of the shared secrets.

Anyway, let's generate public and private keys.          

##Generate Public Private Key

On a Windows computer,

```shell
$ ssh-keygen -t rsa -b 4096 -f jwtRS256.key
$ openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub
```
Credit: [https://gist.github.com/ygotthilf/baa58da5c3dd1f69fae9](https://gist.github.com/ygotthilf/baa58da5c3dd1f69fae9)

On a Mac,
```shell
$ openssl genrsa -out jwtRS256.key 4096
$ openssl rsa -in jwtRS256.key -pubout -out jwtRS256.key.pub
```
Credit: https://gist.github.com/h-sakano/84dc4bd8371be4f0e8dddc9388974348#file-file0-sh

The file `jwtRS256.key.pub` is the public key and will be served as a static file. This will be shown later. The file `jwtRS256.key` is the private key and we will use it to sign the JWT.

##Sign the JWT in NodeJS

We will write a NodeJS server code that has an endpoint called `/login` and accepts a POST request. The body of the POST request contains the userid and password in JSON format.

Run `npm init` and install the necessary packages:

```shell
$ npm init -y
$ npm i --save express path body-parser

```

Create a `public` and a `private` folder and move the public `jwtRS256.key.pub` and private key `jwtRS256.key` files to those folders respectively.


Create a file called `server.js` with the content show in the screenshot below.

At this point the file structure and the `server.js` file should look like: 

![Figure 1](https://thepracticaldev.s3.amazonaws.com/i/it5mzc834w8ke7i6hoaf.png)


(Can't copy and paste, don't worry, this code will be completed and available then. Just read on)

We have not really done anything yet. But you can see the place holders. If the correct `userid` and `password` are entered, we will generate a signed JWT and return with a status code 200. Otherwise, we return with status of 401. The logic to check for `userid` and `password` is up to you.

If you run the NodeJS server locally at this point, you can use Postman or your browser to go address http://localhost:8080/jwtRS256.key.pub, the public key is readily available.

 
Now we install the `jsonwebtoken` package, which is the essence of signing the JWT and also `fs`.

```shell
npm i --save jsonwebtoken
npm i --save fs
```

Now the complete code:

```js
const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const fs = require('fs');
const jwt = require('jsonwebtoken');

const app = express();
const router = express.Router();

const port = process.env.PORT || 8080;

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));

app.post('/login', (req, res) => {
    const { userid, password } = req.body;
    
    if (userid === 'kevin' && password === '123456') { //replace with your logic
        const privateKey = fs.readFileSync(__dirname + '/private/jwtRS256.key', 'utf8');
        const issuer = 'Name-of-Issuer-that-you-want';
        const subject = 'Subject-name';
        const audience = 'Your-audience';
        const expiresIn = '24h';
        const algorithm = 'RS256'; //important
        const payload = { userid };

        const signOptions = {
            issuer,
            subject,
            audience,
            expiresIn,
            algorithm
        }

        const token = jwt.sign(payload, privateKey, signOptions);

        console.log(token);

        res.status(200).json( {token} );
    } else {
        res.status(401).json('Incorrect userid and/or password');
    }
});

app.listen(port);
module.exports = app;
```
There are only 3 lines that are more important than the rest. The first line is reading the private key (`const privateKey = ...`). The second line is assigning `'RS256'` to `algorithm`. The third line is the one where the token is signed (`const token = jwt.sign(...)`)   

Now launch Postman, and make a POST request like in the figure below, you will get a JWT in the response.

![Login to NodeJS](https://thepracticaldev.s3.amazonaws.com/i/z1wlq9l3pniffb2j4bcm.png)


##Verify the JWT in C# WebAPI

As you see, a JWT is returned in the response. Where to store this JWT depends on which kind of client app you are developing, mobile, web application or Electron desktop, etc.

What I will show next is how to secure a C# WebAPI resource.

So in Visual Studio 2017 or 2015, just use the WebAPI Project template to create a new solution. 

You will see a file called `ValuesController.js` with the following code generated for you.

```csharp
public class ValuesController : ApiController
{
    // GET api/values
    public async Task<IEnumerable<string>> Get()
    {
        await Task.Delay(10);
        return new string[] { "value1", "value2" };
    }
    ...
}

```  

Right now, this endpoint `GET api/values` is unprotected. Let's go ahead and secure this endpoint.

Modify this file by adding one single line

```csharp
public class ValuesController : ApiController
{
    // GET api/values
    [JwtAuthorization]
    public async Task<IEnumerable<string>> Get()
    {
        await Task.Delay(10);
        return new string[] { "value1", "value2" };
    }
    ...
}
```

`JwtAuthorization` is a class that we will write. It subclasses from `AuthorizationFilterAttribute`. Before I'll show it, we have to install a Nuget package called `BouncyCastle`.  

![BouncyCastle](https://thepracticaldev.s3.amazonaws.com/i/4aek9fmytmznhlnpnoi6.png)


Then let's write a class that reads the public key. Remember the public key is a static file served at address http://localhost:8080/jwtRS256.key.pub

Since the public only has to be read once, I just create singleton for it.

```csharp
public class PublicKeyStore
{
    private readonly string URL = "http://localhost:8080/jwtRS256.key.pub";
    private static PublicKeyStore _instance;
    private string _publicKey;

    public static async Task<PublicKeyStore> GetInstance()
    {
        if (_instance == null)
        {
            _instance = new PublicKeyStore();
            await _instance.FetchPublicKey();
        }

        return _instance;
    }

    public string PublicKey
    {
        get { return _publicKey; }
    }

    private async Task FetchPublicKey()
    {
        using (HttpClient client = new HttpClient())
        {
            using (HttpResponseMessage response = await client.GetAsync(URL))
            using (Stream receiveStream = await response.Content.ReadAsStreamAsync())
            {
                using (StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8))
                {
                    _publicKey = readStream.ReadToEnd();
                }
            }
        }
    }

    private PublicKeyStore()
    {
    }
}
```

Now we get to the most important part which is verifying the JWT. As I mentioned, this will be done in the `JwtAuthorization` class which `overrides` the `OnAuthorization(HttpActionContext actionContext)` of the base class `AuthorizationFilterAttribute`

```csharp
public class JwtAuthorizationAttribute : AuthorizationFilterAttribute
{
    public override async void OnAuthorization(HttpActionContext actionContext)
    {
        try
        {
            if (actionContext.Request.Headers.Authorization == null)
            {
                actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
            }
            else
            {
                var bearer = actionContext.Request.Headers.Authorization.Scheme;
                var jwt = actionContext.Request.Headers.Authorization.Parameter;                    
                if (bearer.ToLower() != "bearer" || jwt == null)
                {
                    actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
                }
                else
                {
                    var publicKeyStore = await PublicKeyStore.GetInstance();
                    var publicKey = publicKeyStore.PublicKey;

                    var pr = new PemReader(new StringReader(publicKey));
                    var asymmetricKeyParameter = (AsymmetricKeyParameter)pr.ReadObject();
                    var rsaKeyParameters = (RsaKeyParameters)asymmetricKeyParameter;
                    var rsaParams = DotNetUtilities.ToRSAParameters((RsaKeyParameters)asymmetricKeyParameter);                        
                    var rsaCsp = new RSACryptoServiceProvider();
                    rsaCsp.ImportParameters(rsaParams);

                    string[] jwtParts = jwt.Split('.');
                    if (jwtParts.Length < 3)
                    {
                        actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
                    }
                    else
                    {
                        var sha256 = SHA256.Create();
                        var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(jwtParts[0] + '.' + jwtParts[1]));

                        var rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsaCsp);
                        rsaDeformatter.SetHashAlgorithm("SHA256");

                        if (!rsaDeformatter.VerifySignature(hash, FromBase64Url(jwtParts[2])))
                        {
                            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
                        }
                        else
                        {
                            byte[] data = Convert.FromBase64String(jwtParts[1]);
                            var payload = Encoding.UTF8.GetString(data);
                            //Check for time expired claim or other claims
                        }
                    }

                    base.OnAuthorization(actionContext);
                }
            }
        }
        catch (Exception)
        {
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, "JWT is rejected");
        }
    }
    private static byte[] FromBase64Url(string base64Url)
    {
        string padded = base64Url.Length % 4 == 0
                ? base64Url : base64Url + "====".Substring(base64Url.Length % 4);
        string base64 = padded.Replace("_", "/")
                                  .Replace("-", "+");
        return Convert.FromBase64String(base64);
    }
}
```

Now go to Postman, and make a Post request to where your WebAPI is running, pass in the JWT that you got above (using the bearer scheme) in the `Authorization` attribute, you will the response with status 200.

![Call WebAPI endpoint](https://thepracticaldev.s3.amazonaws.com/i/lvacwu21efynloioo299.png)

Without the JWT or with a different scheme will result in a 401 Unauthorized.


####Points of interests


1- Instead of the following code

```csharp
...
var pr = new PemReader(new StringReader(publicKey));
var asymmetricKeyParameter = (AsymmetricKeyParameter)pr.ReadObject();
...
```

I have seen
```csharp
...
var keyBytes = Convert.FromBase64String(publicKey);
var asymmetricKeyParameter = PublicKeyFactory.CreateKey(keyBytes);
...
```

The problem is with the latter, the following FormatException was thrown
```
The format of s is invalid. s contains a non-base-64 character, more than two padding characters, or a non-white space-character among the padding characters.
```

2- The `JwtAuthorizationAttribute` filter runs asynchronously because of the singleton reading the public key is also asynchronously. To ensure the filter always runs before the controller method, I artificially introduced a delay of 10 ms. However as I said, the public key only has to be read once, and after that, it's available in memory. Therefore if every request gets penalized 10 ms, that does not seem fair. So I'm looking for a better solution.

Finally, if you want source code, I'm still tidying it up. In the mean time, you could help motivating me by giving this article a like and sharing it.