In the world of modern web development, user authentication and authorization are crucial aspects of protecting APIs and sensitive data. The token based authentication is one of the most commonly used strategies, among them stand out JSON Web Tokens (JWT), an open and lightweight standard that defines a compact and self-contained mechanism for secure transmission of information between client and server parties.
A JWT is a token that contains information about a user's identity and authorization to access specific resources. It consists of three parts:
☝️ If you don't know what a token is, I would recommend this reading.
Header: Specifies the token type, signing algorithm and other relevant information.
Payload: Contains data about the user, such as identification, name, roles and expiration date of the token.
Signature: Guarantees the integrity and authenticity of the token by means of a digital signature using a cryptographic algorithm and a secret key shared between the server and the client.
The scheme to be implemented in this case can be summarized as follows
Authentication workflow](https://github.com/breatheco-de/content/blob/master/src/assets/images/authentication-diagram.png?raw=true)
Login: The user enters his credentials (email and password) in the client.
Authentication: The server validates the user's credentials and, if correct, generates a JWT with the user's information and signs it with its secret key.
Sending the token: The server sends the JWT to the client in response to the login request.
Storing the token: The client stores the JWT securely, usually in local storage or in an HTTP cookie.
Subsequent requests: In each subsequent API request, the client includes the JWT in the authorization header.
Token validation: The server verifies the signature of the JWT to ensure its authenticity and integrity. If the token is valid, it extracts the user information from the payload and uses it to authorize access to the requested resource.
Install these 3 libraries that will take care of generating the JWT tokens:
1npm install express-jwt @types/express-jwt jsonwebtoken @types/jsonwebtoken --save
Second step is to create one API Route that can be called by the client to
generate a token (a.k.a: login), this endpoint will receive the email
and password
information form the body
and look for any user in the DB that matches those two values.
If the value is found, it will generate a token by calling the function jwt.sign
.
1//this line goes in your public_routes.ts 2import jwt from 'jsonwebtoken' 3 4router.post('/token', safe(createToken)); 5 6// this function goes in your actions.ts 7export const createToken = async (req: Request, res: Response): Promise<Response> =>{ 8 9 if(!req.body.email) throw new Exception("Please specify an email on your request body", 400) 10 if(!req.body.password) throw new Exception("Please specify a password on your request body", 400) 11 12 const userRepo = await getRepository(Users) 13 14 // We need to validate that a user with this email and password exists in the DB 15 const user = await userRepo.findOne({ where: { email: req.body.email, password: req.body.password }}) 16 if(!user) throw new Exception("Invalid email or password", 401) 17 18 // this is the most important line in this function, it create a JWT token 19 const token = jwt.sign({ user }, process.env.JWT_KEY as string); 20 21 // return the user and the recently created token to the client 22 return res.json({ user, token }); 23}
Now we need to add a middleware that will check for the token on the Request Authoritzation Header. The middleware will intercept each request and execute the next
function to proceed only if it succeeds in validating the token, otherwise it will return an error.
Add these two middlewares inside ./src/app.js
that will take care of enforcing the token.
1// ⬆ anything ABOVE is public 2import jwt, { Options } from 'express-jwt'; 3 4let opt: Options = { secret: process.env.JWT_KEY as string, algorithms: ["HS256"] } 5app.use(jwt(opt)) 6// ⬇ anything BELOW is public 7app.use(((err: any, req: any, res: any, next: any) => { 8 if (err) console.error(err); 9 if (err.name === 'UnauthorizedError') { 10 return res.status(401).json({ status: err.message }); 11 } 12 next(); 13}))
Any endpoint that is added BELOW these middlewares will be private, for example:
1app.get('/public', (req, res) => { 2 res.json({ message: "Anyone can see me" }); 3}) 4 5// ⬆ anything ABOVE is public 6app.use(jwt(opt)) // ⬅ JWT Middleware 7// ⬇ anything BELOW is public 8 9app.get('/private', (req, res) => { 10 res.json({ message: "If you can se me, you are logged in" }); 11})
We are done, but if only logged in users are supposed to call our private endpoints, then we need a way to know who is calling them, for example we can use req.user
from now on, to identify request user):
1export const getMe = async (req: Request, res: Response): Promise<Response> =>{ 2 3 const users = await getRepository(Users).find({ where: }); 4 // ⬇ not comming from the BD 5 return res.json(req.user); 6}
Or we can use that info and get more information form the requester from the database.
1export const getMe = async (req: Request, res: Response): Promise<Response> =>{ 2 3 4 // ⬇ not comming from the BD 5 return res.json(req.user); 6}
On the front-end side, we need two main steps: Creating a new token (a.k.a: login) and appending the token to the headers when fetching any other private endpoints.
Based on the endpoints we built earlier we have to POST /token
with the username and password information in the request body.
1const login = async (username, password) => { 2 const resp = await fetch(`https://your_api.com/token`, { 3 method: "POST", 4 headers: { "Content-Type": "application/json" }, 5 body: JSON.stringify({ username, password }) 6 }) 7 8 if(!resp.ok) throw Error("There was a problem in the login request") 9 10 if(resp.status === 401){ 11 throw("Invalid credentials") 12 } 13 else if(resp.status === 400){ 14 throw ("Invalid email or password format") 15 } 16 const data = await resp.json() 17 // Save your token in the localStorage 18 // Also you should set your user into the store using the setItem function 19 localStorage.setItem("jwt-token", data.token); 20 21 return data 22}
Let's suppose I am using the front-end application and I just logged in, but now I want to fetch some private or protected endpoint:
1// Assuming "/protected" is a private endpoint 2const getMyTasks = async () => { 3 // Retrieve token from localStorage 4 const token = localStorage.getItem('jwt-token'); 5 6 const resp = await fetch(`https://your_api.com/protected`, { 7 method: 'GET', 8 headers: { 9 "Content-Type": "application/json", 10 'Authorization': 'Bearer ' + token // ⬅⬅⬅ authorization token 11 } 12 }); 13 14 if(!resp.ok) { 15 throw Error("There was a problem in the login request") 16 } else if(resp.status === 403) { 17 throw Error("Missing or invalid token"); 18 } else { 19 throw Error("Unknown error"); 20 } 21 22 const data = await resp.json(); 23 console.log("This is the data you requested", data); 24 return data 25}
That is it! As you can see, it's very simple to integrate JWT into your application using Express, just four steps on the backend and two steps on the front-ent. For any questions, you can contact me on Twitter @alesanchezr or use the #public-support
channel on 4Geeks Academy's Slack community.