Skip to main content

Authenticate with JWT

This tutorial demonstrates how to create and apply a JSON Web Token (JWT) to authenticate an eth_blockNumber API request with Node.js.

Developers can configure the expiry time and scope of JWTs to enhance the security profile of their dapps.

Prerequisites

Steps

1. Initiate your project

Create a new directory for your project:

mkdir infura-jwt-demo

Next, change into the infura-jwt-demo directory:

cd infura-jwt-demo

Initialize a new Node.js project:

npm init -y

Install the required dependencies:

npm install axios jsonwebtoken dotenv

2. Create a key pair

2.1. Generate your private key

Generate your private key using the RSA (PKCS #8) or ES256 algorithm:

openssl genpkey                 \
-algorithm RSA \
-out private_key.pem \
-pkeyopt rsa_keygen_bits:2048 \
-outform PEM

2.2. Generate your public key

Generate your public key from your RSA or ES256 private key:

openssl rsa           \
-in private_key.pem \
-pubout \
-out public_key.pem

3. Set up your .env file

3.1. Update the MetaMask Developer dashboard

In the MetaMask Developer dashboard, under API Keys, select the key you want to use for authentication. Go to its Settings tab. Under Requirements, fill out these fields:

  • JWT PUBLIC KEY NAME - Provide a unique name for your JWT public key, which can help you manage multiple keys.
  • JWT PUBLIC KEY - Paste the entire contents of the public_key.pem file.
note

Optionally, you can check REQUIRE JWT FOR ALL REQUESTS. If this option is not checked, you can make calls using your key without a JWT in the request header, however, invalid or expired tokens will result in the call being rejected.

3.2. Create your .env file

At the root of your Node.js project, create an environment file:

touch .env

Add the following details to .env:

.env
INFURA_API_KEY=<YOUR-API-KEY>
JWT_KEY_ID=<YOUR-JWT-KEY-ID>
INFURA_NETWORK_URL=<NETWORK-URL>

Replace the following values in the .env file:

  • <YOUR-API-KEY> with your API key from the MetaMask Developer dashboard.
  • <YOUR-JWT-KEY-ID> with the JWT's key ID. This is generated by Infura, and you can find it in the MetaMask Developer dashboard. The code in Step 4 applies this ID to the JWT header to allow Infura to identify which key was used to sign the JWT.
  • <NETWORK-URL> with the URL of an Infura network for which your key has access rights, and that supports the method eth_blockNumber.
Important

Before pushing code to a public repository, add .env to your .gitignore file. This reduces the likelihood that keys are exposed in public repositories.

Note that .gitignore ignores only untracked files. If your .env file was committed in the past, it's tracked by Git. Untrack the file by deleting it and running git rm --cached .env, then include it in .gitignore.

4. Create and apply your JWT

Create a new file named call.js:

touch call.js

Add the following to call.js:

call.js
require("dotenv").config();
const axios = require("axios");
const jwt = require("jsonwebtoken");
const fs = require("fs");

function getAlgorithm(privateKey) {
if (privateKey.includes("BEGIN RSA PRIVATE KEY") || privateKey.includes("BEGIN PRIVATE KEY")) {
return "RS256";
} else if (privateKey.includes("BEGIN EC PRIVATE KEY")) {
return "ES256";
} else {
throw new Error("Unsupported key type");
}
}

// Function to generate the JWT
function generateJWT() {
const privateKey = fs.readFileSync("private_key.pem", "utf8");
const algorithm = getAlgorithm(privateKey);
const token = jwt.sign(
{},
privateKey,
{
algorithm: algorithm, // Dynamically set the algorithm based on the key type
keyid: process.env.JWT_KEY_ID,
audience: "infura.io",
expiresIn: "1h",
header: {
typ: "JWT"
}
}
);

return token;
}

// Function to authenticate with Infura and get the latest block number
async function getBlockNumber() {
const jwtToken = generateJWT();
const url = `${process.env.INFURA_NETWORK_URL}${process.env.INFURA_API_KEY}`;
const data = {
jsonrpc: "2.0",
method: "eth_blockNumber",
params: [],
id: 1
};

try {
const response = await axios.post(url, data, {
headers: {
Authorization: "Bearer " + jwtToken,
"Content-Type": "application/json"
}
});
console.log("Block number:", response.data.result);
} catch (error) {
console.error("Error fetching block number:", error.response ? error.response.data : error.message);
}
}

// Call the function to get latest block
getBlockNumber();

Next, run the code:

node call.js

Your console outputs the response, for example:

Block number: 0x61fc48
tip

This script:

  1. Generates a JWT with a 1 hour expiry that is only valid on infura.io.
  2. Applies this JWT to form the header of a getBlockNumber call.
  3. Submits the API call.

(Optional) Examine the curl equivalent

If you want to better understand the code applied in Step 4, you can examine the equivalent curl request.

Create a new file named curl.js:

touch curl.js

Add the following to curl.js:

curl.js
require("dotenv").config();
const jwt = require("jsonwebtoken");
const fs = require("fs");

function getAlgorithm(privateKey) {
if (privateKey.includes("BEGIN RSA PRIVATE KEY") || privateKey.includes("BEGIN PRIVATE KEY")) {
return "RS256";
} else if (privateKey.includes("BEGIN EC PRIVATE KEY")) {
return "ES256";
} else {
throw new Error("Unsupported key type");
}
}

// Function to generate the JWT
function generateJWT() {
const privateKey = fs.readFileSync("private_key.pem", "utf8");
const algorithm = getAlgorithm(privateKey);
const token = jwt.sign(
{},
privateKey,
{
algorithm: algorithm, // Dynamically set the algorithm based on the key type
keyid: process.env.JWT_KEY_ID,
audience: "infura.io",
expiresIn: "1h",
header: {
typ: "JWT"
}
}
);
return token;
}

// Generate the JWT and print the curl command
async function printCurlRequest() {
const jwtToken = generateJWT();
console.log("Generated JWT:", jwtToken);
const curlRequest = `
curl -X POST ${process.env.INFURA_NETWORK_URL}${process.env.INFURA_API_KEY} \\
-H "Authorization: Bearer ${jwtToken}" \\
-H "Content-Type: application/json" \\
-d '{
"jsonrpc": "2.0",
"method": "eth_blockNumber",
"params": [],
"id": 1
}'
`;
console.log("Equivalent curl request:");
console.log(curlRequest);
}

// Call the function to print the curl request
printCurlRequest();

Next, run the code:

node curl.js

Your console outputs the curl request, for example:

curl -X POST https://sepolia.infura.io/v3/<YOUR-API-KEY> \
-H "Authorization: Bearer pqJhbG**JWT-token**2Ud5o2Q" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_blockNumber",
"params": [],
"id": 1
}'

You can run this request yourself to make the call. Your console outputs the response, for example:

{"jsonrpc":"2.0","id":1,"result":"0x61fc48"}

Next steps

Consider following these next steps:

  • Decode your JWT: Copy the JWT provided in the console by the optional curl equivalent step, and paste it into the Encoded field in jwt.io.

  • Add a layer of verification to your call by applying the JWT's FINGERPRINT provided in the MetaMask Developer dashboard.

    note

    The JWT's fingerprint is a hash of the public key, used to ensure the key's integrity and authenticity.