Authentication: JWT strategy

Authentication: JWT strategy

#1: Authentication: Session Strategies
#2: Authentication: Google OAuth2.0 iwth PKCE
#3: Authentication: JWT strategy

LinkIconBest Practices for JWT Implementation

  1. Secure Storage: Store JWTs in HTTP-only cookies to prevent access from JavaScript, reducing the risk of XSS attacks.
  2. Token Expiration: Set a reasonable expiration time on JWTs to limit the time window for potential misuse.
  3. Token Revocation: Have a mechanism to revoke or blacklist compromised tokens to enhance security.
  4. Use HTTPS: Ensure that all communications between the client and server use HTTPS to prevent eavesdropping and man-in-the-middle attacks.
  5. Don’t Store Sensitive Data: Avoid storing sensitive data in the JWT payload, as the payload is easily readable.

LinkIconRegister

First step of an auth system. Here you take the password and hash it (commonly with bcrypt library), then store it in a database.

const app = express()
 
app.post("/api/auth/register", (req, res) => {
	const { email, password, ...restUserInfo}  = req.body
	
	// Hash password using bcrypt and save it
	// Save new user into the database with hash password
	// Return success
	return res.status(200).json({message: "successfull"})
})

LinkIconLogin

In this part, we will skip the password validation (that involves using bcrypt to compare both hashed passwords from user input and database).

import { encrypt } from 'jsonwebtoken'
const app = express()
 
app.post("/api/auth/login", (req, res) => {
	const { email, password }  = req.body
	
	// TODO: validate password and get user name
	
	const user = { email, name }
	
	// Create the session
	const expires = new Date(Data.now() + 1000 * 10) // expires in 10 sec
	const session = await encrypt({ user, expires })
	
	// Save the session in cookie
	res.cookies('session', session, { expires, httpOnly: true })
	
})

Notice we are using a third party-library to encrypt the session, thejose library.

import { SignJWT } from 'jose'
 
export async function encrypt(payload) {
	return await new SignJWT(payload)
		.setProtectedHeader({ alg: 'HS256' })
		.setIssueAt()
		.setExpirationTime('10 sec from now')
		.sign(key)
}

There is a argument passed called key that we did not talk about. Every JWT comes with an unique sign. This sign is needed so that no bad actors injects malicious JWT in every request. Your server will know if JWT belongs to you when decrypting it.

jose under the hood encrypts the sign but it needs your own secret (only you know it) to make the sign. The secret must be a string.

const secretKey = `very-secret-key`
const key = new TextEncoder().encode(secretKey)

Defintely you should store secretKey in an environment variable where people don't have access to.

LinkIconRefresh Token

Now that we create our JWT and a session in cookies. We need to keep our session alive. For that, we can use middleware.

Middleware catches the incoming request and do some operation before calling the API endpoint, in this case we will update the session.

export async function updateSessionMiddleware(req, res) {
	const session = req.cookies['session']
	if (!session) return
	
	// Refresh the session
	const parsed = await decrypt(session)
	parsed.expires = new Date(Date.now() + 1000 * 10)
	
	const resNext = res.next()
	resNext.cookies('session', await encrypt(parsed), { httpOnly: true, expires: parsed.expires })
	
	return resNext
}

Notice that we have a decrypt function

import { jwtVerify } from 'jose'
 
export async function decrypt(input) {
	const { payload } = await jwtVerify(input, key, {
		algorithms: ['HS256']
	})
	
	return payload
}

LinkIconSession

Now we need a function that allows us to get the current session from cookies. This function must run in the server side since cookies is HttpOnly (read-only on server-side).

export async function getSession(req) {
	cosnt session = req.cookies['session']
	if (!session) return null
	return await decrypt(session)
}

If you want to retrieve the session from client-side then I recommend to create a GET endpoint that the client can call.

LinkIconLogout

This step is simple as deleting the session from the cookies.

app.post("/api/auth/logout", (req, res) => {
	res.cookies('session', '', { expires; new Date(0)})
})