So far, we have covered the core functionality of JWT authentication in the first part of this series: “Creating a full-stack MERN app using JWT authentication: Part 1.”
The logic runs on the server side, and we need to have an interface that helps us to consume the logic. So we will be creating a REST API-based Express.js server. The server will expose a few endpoints for signing in and authenticating the users.
The steps are simple. We need to first export the functions so that we can import them into our main Express.js server script. Then, we need to expose a few REST endpoints that accept HTTP GET
and POST
requests, preferably one for signing in, one for getting an existing token, and one for signing out. We will also expose one more endpoint to make use of the Bearer authentication headers.
The term Bearer authentication, sometimes called token authentication, is an HTTP authentication scheme that involves some secret strings or security tokens called Bearer tokens. The name “bearer authentication” can be understood as “give access to the bearer of this token.”
The Bearer token is a cryptographically generated string, usually generated by the server when a login request is made by the client. Every time the client tries to access resources that require authentication, it must send this token in the Authorization
header:
Authorization: Bearer <token>
This scheme was originally created as part of OAuth 2.0 in RFC 6750. Like Basic authentication, Bearer authentication should only be used over HTTPS (SSL).
Bearer
in front of <token>
The most common question that comes to anyone’s mind concerns the reason behind using Bearer
in front of the <token>
. Why not simply:
Authorization: <token>
It’s definitely a valid question for most developers. The Authorization: <type> <credentials>
pattern was introduced by the W3C in HTTP 1.0 and has been reused in many places since. Many web servers support multiple methods of authorization. In those cases, sending just the token isn’t sufficient. Long before Bearer authorization, this header was used for Basic authentication.
For interoperability, the use of these headers is governed by W3C norms, so even if you’re reading and writing the header, you should follow them. Bearer distinguishes the type of authorization you’re using, so it’s important. Google describes it as a Bearer Token
is set in the Authorization
header of every inline action HTTP request. For example:
POST /rsvp?eventId=123 HTTP/1.1 Host: praveen.science Authorization: Bearer Prav33nIsGr3a7JK Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/1.0 (KHTML, like Gecko; Gmail Actions) rsvpStatus=YES
The string Prav33nIsGr3a7JK
in the example above is the Bearer authorization token. This is a cryptographic token produced by our application.
If using Bearer tokens, verify that the request is coming from the trusted domain (say, your client application location) and is intended for the the sender domain. If the token doesn’t verify, the service should respond to the request with the HTTP response code 401 (Unauthorized)
.
Since we have the code in dec-enc.js
, we need to export the right functions. At the end of the file, let’s add some export statements and make a few changes to the way it works with the REST endpoint. The few things we will be doing with this approach are converting the claims
, key
, and header
to be parameters of the encode function.
In this section, we will be only dealing with the dec-enc.js
. We have a lot of console.log()
s, and we need to remove them at any cost since they might leak out some rather sensitive data to the server logger (that is, if someone gains access to it). The next thing would be splitting the code into different units and functions and exporting everything, one by one.
This process should be fairly easy — just find all the console.log
statements and remove them. We have added them earlier just for debugging purposes and not for anything else. The modified code looks something like the below.
const JSRSASign = require("jsrsasign"); // Generation const claims = { Username: "praveen", Age: 27, Fullname: "Praveen Kumar" }; const key = "$PraveenIsAwesome!"; const header = { alg: "HS512", typ: "JWT" }; const sHeader = JSON.stringify(header); const sPayload = JSON.stringify(claims); // Generate the JWT const sJWT = JSRSASign.jws.JWS.sign("HS512", sHeader, sPayload, key); const token = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJVc2VybmFtZSI6InByYXZlZW4iLCJBZ2UiOjI3LCJGdWxsbmFtZSI6IlByYXZlZW4gS3VtYXIifQ.Nut6I57FYUGP973LgfOUNUBjMlaIm6NKk8ffgX4BTdQ_Wc2ob8m6uOwWlgoNMxpuRQaOoeFQOHhrIOJ_V8E-YA"; const algorithm = "HS512"; // Decoding const sJWS = token; const aJWT = sJWS.split("."); const uHeader = JSRSASign.b64utos(aJWT[0]); const uClaim = JSRSASign.b64utos(aJWT[1]); const pHeader = JSRSASign.jws.JWS.readSafeJSONString(uHeader); const pClaim = JSRSASign.jws.JWS.readSafeJSONString(uClaim);
Since we are getting everything from the user or the server, we have to convert the above into callable units or functions. The two functions that are required here are one to generate the JWT and another to verify the JWT. This makes us export just two functions at the end, and we will be exporting using module.exports.Variable
and importing using require
.
The first function we will be writing takes in a few parameters, like claims
, key
, and header
, and returns us a JWT. One thing we need to be clear about is where to define what. For example, where will the constants like the key
and header
go? The scope of the dec-enc.js
is to provide a generic function to generate a JWT and verify a JWT.
So let’s keep it generic — no hardcoding of the above. Let’s create the three functions for them. Since we are using Node.js as the back end, let’s go with ES6 fat arrow functions since they are far more efficient and useful in certain cases than traditional functions. But before all that, let’s start with importing the required jsrsasign
(i.e., JS RSA Sign) library.
const JSRSASign = require("jsrsasign"); const GenerateJWT = () => {}; const DecodeJWT = () => {}; const ValidateJWT = () => {}; module.exports = { GenerateJWT, DecodeJWT, ValidateJWT };
In the above code, some people might get confused on the exports part. The last section of code starting with module.exports
can be explained this way: the module
is a built-in object that is used to describe the module, and it has a parameter exports
, which can be assigned multiple times with the named variables we want to export.
The object looks a bit different, too. It makes use of the object literal property value shorthand. This is yet another syntactic sugar in ES6. Consider the following ES5 fragment:
{ GenerateJWT: GenerateJWT, DecodeJWT: DecodeJWT, ValidateJWT: ValidateJWT }
With the new shorthand form, this can be rewritten as the following:
{ GenerateJWT, DecodeJWT, ValidateJWT }
As you can see, this works because the property value has the same name as the property identifier. This a new addition to the syntax of Object Initialiser (section 12.1.5) in the latest ECMAScript 6 draft Rev 13. And yeah, just like the limitations set from ECMAScript 3, you can’t use a reserved word as your property name.
So with the above said, we will be writing the body of the two functions from what we have done before, and the function parameters are going to be the inputs in this case. Let’s crack on writing the functions here:
const JSRSASign = require("jsrsasign"); const GenerateJWT = (header, claims, key) => { // Let's convert everything into strings. const sHeader = JSON.stringify(header); const sPayload = JSON.stringify(claims); // Generate the JWT and return it to the caller. const sJWT = JSRSASign.jws.JWS.sign("HS512", sHeader, sPayload, key); return sJWT; }; const DecodeJWT = sJWS => { const aJWT = sJWS.split("."); const uHeader = JSRSASign.b64utos(aJWT[0]); const uClaim = JSRSASign.b64utos(aJWT[1]); const pHeader = JSRSASign.jws.JWS.readSafeJSONString(uHeader); const pClaim = JSRSASign.jws.JWS.readSafeJSONString(uClaim); return pClaim; }; const ValidateJWT = (header, token, key) => { return JSRSASign.jws.JWS.verifyJWT(token, key, header); }; module.exports = { GenerateJWT, DecodeJWT, ValidateJWT };
The module.exports
, or exports
, is a special object that should be present in JavaScript files that require the exporting of resources in a Node.js application. module
is a variable that represents the current module, and exports
is an object that will be exposed as a module. So, whatever you assign to module.exports
or exports
will be exposed as a module.
module.exports = { GenerateJWT, DecodeJWT, ValidateJWT };
In the above code, we are exporting GenerateJWT
, DecodeJWT
, and ValidateJWT
from this file. We will be able to import the functions by using the following code:
const DecEnc = require("./dec-enc.js");
We can use them in a few different ways. One common way is to use the parent object, something like this:
DecEnc.GenerateJWT(); DecEnc.DecodeJWT(); DecEnc.ValidateJWT();
The other way is to use named exports and object destructuring assignment, which can be destructured as follows:
const { GenerateJWT, DecodeJWT, ValidateJWT } = require("./dec-enc.js");
The above method will be easier because we know the names of the functions, and they are the only ones that we will be using.
Let’s start by creating a basic Express.js server, app.js
. We can very much use the Express “Hello World” example to start with.
Starting with requiring the express
and defining a port address for the app to listen to, we create an instance of the Express.js server by calling express()
and storing the returned value inside a variable named app
. We then define the routes one by one:
const express = require('express'); const app = express(); const port = process.env.PORT || 3100; app.get('/', (req, res) => res.send('Hello World!')); app.listen(port, () => console.log(`Server listening on port ${port}!`));
When you run node app
, the default route would show us Hello World!
on your browser when you open the webpage http://localhost:3100
, or as defined in the PORT
environment variable.
If that variable is undefined, the server application falls back to port 3100
by using an OR
condition represented by ||
and gets stored in the port
constant. Finally, we make the server to listen to the port in the last line.
With the above done, let’s also include the three functions from our dec-enc.js
. Let’s keep all the require
statements together and have the library require
s on the top, followed by our custom require
s. Adding this to the above code will get our app.js
looking like the following:
const express = require('express'); const { GenerateJWT, DecodeJWT, ValidateJWT } = require("./dec-enc.js"); const app = express(); const port = process.env.PORT || 3100; app.get('/', (req, res) => res.send('Hello World!')); app.listen(port, () => console.log(`Server listening on port ${port}!`));
Let’s carry on by creating the routes for the API calls.
For a REST endpoint that gets connected to a React application, it is always better to use a common route prefix of /api/
for all the calls. And since the calls are neither idempotent operators nor contain insensitive data, it is always better to use POST
methods here. This will not pass the data to the server through query string, but by content body.
Express.js cannot handle content inside POST data. To parse or read the POST data, we need to use a Node.js body-parsing middleware. express.json()
is a built-in middleware function in Express. It parses incoming requests with JSON payloads and is based on body-parser
. This middleware is activated using the following new line after defining the app
.
Also, let’s update the default home root (/
) with a proper message that explains them to use this in the right way. For illustration purposes, the below code doesn’t have the implementation of the API routes.
const express = require("express"); const { GenerateJWT, DecodeJWT, ValidateJWT } = require("./dec-enc.js"); const app = express(); app.use(express.json()); const port = process.env.PORT || 3100; const welcomeMessage = "Welcome to the API Home Page. Please look at the documentation to learn how to use this web service."; app.get("/", (req, res) => res.send(welcomeMessage)); app.post("/api/GenerateJWT", (req, res) => res.send("")); app.post("/api/DecodeJWT", (req, res) => res.send("")); app.post("/api/ValidateJWT", (req, res) => res.send("")); app.listen(port, () => console.log(`Server listening on port ${port}!`));
The app.use(express.json())
returns middleware that only parses JSON and only looks at requests where the Content-Type
header matches the type
option. This parser accepts any Unicode encoding of the body and supports automatic inflation of gzip
and deflate
encodings.
A new body
object containing the parsed data is populated on the request
object after the middleware (i.e., req.body
) or an empty object ({}
) if there was no body to parse, the Content-Type
was not matched, or an error occurred.
All we need to do now is to call the three methods with the right parameters from the user’s request object. The request (req
) object will contain the information sent by the client browser or user to the server, and if we need to send something back to the client browser or the user, we will use the response (res
) object. The req.body
will have all the information that’s needed for each call.
const express = require("express"); const { GenerateJWT, DecodeJWT, ValidateJWT } = require("./dec-enc.js"); const app = express(); app.use(express.json()); const port = process.env.PORT || 3100; const welcomeMessage = "Welcome to the API Home Page. Please look at the documentation to learn how to use this web service."; app.get("/", (req, res) => res.send(welcomeMessage)); app.post("/api/GenerateJWT", (req, res) => res.json(GenerateJWT(req.body.header, req.body.claims, req.body.key)) ); app.post("/api/DecodeJWT", (req, res) => res.json(DecodeJWT(req.body.sJWS)) ); app.post("/api/ValidateJWT", (req, res) => res.json(ValidateJWT(req.body.header, req.body.token, req.body.key)) ); app.listen(port, () => console.log(`Server listening on port ${port}!`));
Each of the three functions GenerateJWT
, DecodeJWT
, and ValidateJWT
returns a valid JavaScript object. So, we can blindly send the response into a JSON format by calling res.json()
and passing the return value of the functions. This will be formatted into JSON and sent to the browser. This can be used by any client that accepts a Content-type: application/json
response, like a React application.
To make sure these work right, we can use a free tool called Postman, which is a complete API development environment. This tool helps us to test API endpoints and examine responses.
With Postman, we can add the URL, set the HTTP request method, add the headers, and execute the request to find the right output to be displayed and verified. Now that we have completed our work on the server side, generating the tokens, sending data to client through REST endpoints, let’s build the client side to get our token and decode it in part three!
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBackdrop and background have similar meanings, as they both refer to the area behind something. The main difference is that […]
AI tools like IBM API Connect and Postbot can streamline writing and executing API tests and guard against AI hallucinations or other complications.
Explore DOM manipulation patterns in JavaScript, such as choosing the right querySelector, caching elements, improving event handling, and more.
`window.ai` integrates AI capabilities directly into the browser for more sophisticated client-side functionality without relying heavily on server-side processing.