Table of contents
- What is JWT?
- How does JWT authentication work?
- Advantages of using JWT Authentication
- Disadvantages of using JWT authentication
- What is the JWT structure?
- Integrating JWT authentication into our project
- Prerequisites
- Integrating JWT into our Node.js (server-side)
- Joining our node.js (Backend) to our React.js (front-end)
- Conclusion
In past years API developers have used sessions/cookies for authorization of users on their apps but in recent times, a lot of developers have moved to the usage of JSON web tokens due to a lot of advantages it offers to developers which will be touched later on in this tutorial, and React is a widely used framework (actually my personal favorite) for front-end development, so we'll be looking at how JWT can be used with React.js. Before we dive into coding we'll be defining some key terms to understand how exactly JWT works and its application in React.js.
What is JWT?
JWT stands for JSON web token which is an open standard used to share security information as a JSON object between a client and a server. The presence of a JWT signifies that the information is verified and its credibility established because it is digitally signed using a secret written with a cryptographic algorithm to ensure that the claims aren't altered after the token is issued. When a server receives a JWT, it guarantees the data it contains can be trusted because it's signed by the source, no middleman can modify a JWT once it is sent, although it is not encrypted meaning anyone can see it, which is why it is recommended to use JWT in an HTTPS environment.
How does JWT authentication work?
JWT authentication is a stateless authentication mechanism popularly used as a client-side stateless session, meaning that the server doesn't have to completely rely on a database to save session information instead they are stored on the client's end therefore database lookup isn't required to verify the identity of the requesting user. It works like this:
- A user logs in to a website or application with an email/username and password to prove his identity.
- The server confirms the user's identity and sends back a unique access token containing a reference to his identity.
- The client then includes this unique access token with every request to the server, so if the access token is wrong/altered the client is denied access.
- For protected routes, authentication middleware asserts the presence of a valid token, the server can further use the identity to implement more granular permissions.
Advantages of using JWT Authentication
- More compact: JSON is less verbose than XML, so when it is encoded, a JWT is smaller than a SAML token. This makes JWT a good choice to be passed in HTML and HTTP environments.
- More secure: JWTs can use a public/private key pair in the form of an X.509 certificate for signing. A JWT can also be symmetrically signed by a shared secret using the HMAC algorithm. And while SAML tokens can use public/private key pairs like JWT, signing XML with XML Digital Signature without introducing obscure security holes is very difficult when compared to the simplicity of signing JSON. Read more about JWT signing algorithms.
- More common: JSON parsers are common in most programming languages because they map directly to objects. Conversely, XML doesn't have a natural document-to-object mapping. This makes it easier to work with JWT than SAML assertions.
- Easier to process: JWT is used on an internet-scale. This means that it is easier to process on users' devices, especially mobile.
Disadvantages of using JWT authentication
- If a client needs to be blocked or deactivated, the application will have to wait for the token to expire for the lockout to be fully effective.
- If a client needs to change their password, and authentication has been performed beforehand, then a token generated with the previous password will still be valid until expiry.
- No “refresh” token is specified by the standard implementation. On expiry, the client will therefore have to re-authenticate.
- It is not possible to destroy a token without breaching the “stateless” aspect of JWT tokens: Even if the token is deleted from the browser, it is still valid until expiry, so no real logout is possible. To deal with these challenges, some JWT libraries add a layer above the standard specification to allow refresh token mechanisms as well as some other features such as forcing a user to re-authenticate, if need be.
To deal with these challenges, some JWT libraries add a layer above the standard specification and allow refresh token mechanisms as well as some features like forcing a user to re-authenticate, if need be.
What is the JWT structure?
A JSON web token in its compact form consists of three parts separated by dots, which are:
- Header -Payload -Signature
The Header
The header contains just two parts which are; the type of token and the signing algorithm in use, which could be HMAC SHA256 or RSA. For example:
{
"alg" : "HS256",
"typ": "JWT"
}
This JSON is then Base64Url encoded to form the first part of the JWT.
The Payload
The payload mainly contains claims, claims are statements about an item in our case the user, and additional data. This information is typically used by the server to verify that the user has permission to carry out the action they are requesting. There are three types of claims: registered, public, and private claims. For example:
{
"iss":" Signin route",
"name": "janet",
"sub":"2"
"admin": true
}
The Signature
The signature is used to verify that the message wasn't changed and it is also used to verify that the sender of the JWT is who it says it is. The signature is created by taking the encoded header, the encoded payload, a secret, and the algorithm specified in the header, and signing that. For example, if you want to use the HMAC SHA256 algorithm, the signature will be created this way:
HMAC256(
base64UrlEncode(header) + "." + base64UrlEncode(payload), secret
)
Putting the structure Together
After all, this is done we get three Base64Url strings representing the three different parts of a JWT token separated by dots that can be easily passed in an HTML and HTTP environments.
Here is an example: You can visit token.dev/paseto to practice and understand more on how the JWT structure comes together.
Integrating JWT authentication into our project
Prerequisites
To be able to follow through with the rest of the article you need the following;
- A working knowledge of JavaScript
- A good understanding of Node.js and how to use it in creating a server.
- Basic knowledge of React.js
- Postman and some knowledge on how to use Postman.
Integrating JWT into our Node.js (server-side)
Node.js is an open-source, cross-platform, back-end JavaScript runtime environment that executes JavaScript code outside a web browser, it was designed to build scalable network applications. To me Node.js is the react of backend web development.
Using Node.js we'll create our login, registration and authentication routes that will receive the user information from the front-end, hash the passwords where necessary, create, verify and authenticate the user whenever the user logs in and request to access our protected route.
Alongside Node.js we'll be using PostgreSQL to create our database you can learn how to do that here, Knex.js for query building, and bcrypt.js to hash our passwords so that if our database is compromised the user password wouldn't be available to the hacker.
Creating our registration route
- Firstly we need to install the JWT library and other dependencies we'll be needing for this project, to do that we'll run the following command;
npm i jsonwebtoken bcrypt knex postgres
- We'll then create a process.env for storing our token;
REACT_APP_TOKEN= "jwtsecrettutorial"
- We'll create our registration end point that will receive the user's information from the frontend request , hash the password, send the information to our database, create our JWT token and respond to the front-end with our JWT token ;
app.post('/register',(req, res, db, bcrypt,jwt) => {
const { email, name, password } = req.body;
if (!email || !name || !password) {
return res.status(400).json(`${console.log(req.body)}incorrect form submission`);
}
//Hashing our password with bcrypt
const hash = bcrypt.hashSync(password);
db.transaction(trx => {
trx.insert({
hash: hash,
email: email
})
//Inserting our users info into our login and user table
.into('login')
.returning('email')
.then(loginEmail => {
return trx('users')
.returning('*')
.insert({
email: loginEmail[0].email,
name: name,
joined: new Date()
})
//creating the JWT token and responding to the front end with our token
.then(user => {
const accessToken = jwt.sign({email},
process.env.REACT_APP_TOKEN,
{expiresIn: "3000s"});
res.json({accessToken});
})
})
.then(trx.commit)
.catch(trx.rollback)
})
}
In the image above using postman, the client's information was sent to the server, after saving the client's information the server responds with the JWT token for subsequent request.
Creating our login route
For the login route, we'll create our end-point to receive the user information from the front-end request, verify if the user exist in our database, if the user exist a JWT token is created and sent to the client-side, this token will be used for subsequent API request.
app.post('/signin', (req, res, db, bcrypt, jwt) => {
const { email, name, password } = req.body;
//Selecting the user from the database based on the log in details gotten from the front end
db.select('email', 'hash').from('login')
.where('email', '=', req.body.email)
//comparing our password with the one in the database using bcrypt
.then(data =>{
const isValid = bcrypt.compareSync(req.body.password, data[0].hash);
if(isValid){
return db.select('*').from('users')
.where('email','=',req.body.email)
//Creating a JWT token for the already verified user
.then(user => {
const accessToken = jwt.sign({email},
process.env.REACT_APP_TOKEN,
{expiresIn: "3000s"});
res.json({accessToken});
})
.catch(err => res.status(400).json('error signing in'));
} else {res.status(400).json('wrong credentials')}
})
.catch(err => res.status(400).json('wrong credentials'));
}
In the image above using postman, the client's information was sent to the server, after cross-checking and confirming the client's information the server responds with the JWT token for subsequent request.
Creating our authentication route
For authentication we'll create a middleware that carries out authentication whenever a request is made through our protected route. This route will require the user token in the request header as 'x-auth-token'.
To create the middleware use the code below;
require('dotenv').config();
var jwt = require('jsonwebtoken');
module.exports = { jwtauth (req,res,next) {
const token = req.header("x-auth-token");
if(!token) {
res.status(401).json("token not found");}
try {
const user = jwt.verify(token,process.env.REACT_APP_TOKEN);
req.user = user.email;
console.log("middleware is working");
next();
}
catch(error){res.status(401).json("invalid token")}
}}
- We can now add a protected route to our server, a request to this route will first go through our middleware before access is granted or denied.
const jwtauth = require("./middleware/jwtauth");
app.post("/protected", jwtauth, (req, res) => {
res.status(200).send("Here's the info you requested ");
});
If a request is made without adding a token using the x-access-token an error message will be returned saying "token not found". In the image above a request is sent to the server without any token in the header and the server responds with an error message saying token not found.
If the token is correct it allows the client access to the protected route, but if the token is wrong it responds with an error message saying "invalid token" In the image above a request is sent to the server with a wrong token in the header and the server responds with an error message saying invalid token.
Joining our node.js (Backend) to our React.js (front-end)
To do this you would need to install Axios by doing;
npm i axios
- On our register/sign-up route we want our client-side to store the jwt-token it receives from the server-side so that we can use it for subsequent API requests, so we add this to our code;
onSubmitSignIn = () => {
console.log(this.state);
axios.post('http://localhost:3001/register', {
email: this.state.email,
password: this.state.password,
name: this.state.name,
enteries: this.state.enteries,
})
.then(response =>{
if(response.data.accessToken){
localStorage.setItem("user",JSON.stringify(response.data));
}
else{ console.log('no response')}
})
}
With this done the token is then stored in our local storage on the client-side.
- We'll do the same thing to our sign-in route so that when an already existing user sign's in, a token will be created for subsequent API calls,
onSubmitSignIn = () => {
console.log(this.state);
axios.post('http://localhost:3001/signin', {
email: this.state.email,
password: this.state.password,
})
.then(response =>{
if(response.data.accessToken){
localStorage.setItem("user",JSON.stringify(response.data));
}
else{ console.log('no response')}
})
}
- For our authentication route we have to retrieve the already stored token from our storage which will then be sent to the server-side for authentication and further action.
export default function authHeader
{
const user = JSON.parse(localStorage.getItem("user");
if (user && user.accessToken){
return {"x-auth-token" : user.accessToken};
else{};
}
axios.post('http://localhost:3001/protected', {header: authHeader()});
With all this done we have successfully set up a working authentication system that works with our node.js (backend) and our react.js (frontend)
Conclusion
So far we've looked at JWT authentication, the JWT structure, what JWT authentication is, how it works and how to add it to our server-side node.js routes and our React.js app. You can learn more on JWT and libraries and tools that makes your use of JWT authentication easier and more secure here, Hope this tutorial was helpful, have fun authenticating requests with JSON web Tokens.