Whenever we set up authentication and authorization using JWT (JSON Web Tokens), it's important to ensure the use of refresh tokens to enhance security and provide continuous access without requiring the user to log in frequently.
Authentication vs Authorization
Authentication is about confirming ‘who you are’. It's the process of verifying your identity, like when you log in to a website with a username and password. The system checks if you are who you say you are.
Authorization is about checking ‘what you are allowed to do’. After you're authenticated, the system checks if you have permission to access certain resources or perform specific actions, like viewing a page or editing a file.
JWT
JWT (JSON Web Token) is like a digital ID card. When you log in to a website or app, the server gives you this "ID card" (the JWT), which proves who you are.
It's a small piece of information, made up of three parts: header, payload, and signature.
You send this token every time you make a request (like visiting a new page or getting data) to prove you're logged in.
The server doesn't need to look you up in a database every time—it just checks the token.
Think of JWT as a fast and secure way for the server to remember who you are and what you're allowed to do.
Parts of JWT
A JWT consists of three parts separated by periods ‘.’, which are base64url-encoded strings:
Header: The header typically consists of two parts;
a. the token type (JWT)
b. the signing algorithm being used, such as HMAC SHA256 or RSA.
Example
{ "alg": "HS256", "typ": "JWT" }
Payload: The payload contains the claims, which are statements about the user or other data. Claims can be of three types: registered, public, and private claims.
Example:
{ "sub": "user1", "name": "Sankarlal", "admin": true }
Signature: To create the signature part, you need to take the encoded header, encoded payload, a secret, and the algorithm specified in the header, then sign that with the secret. The signature is used to verify that the sender of the JWT is who it says it is and to ensure that the message wasn’t changed along the way.
Example (using HMAC SHA256):
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
Working of JWT
When a user logs in or attempts to access a protected resource, the server generates a JWT after successful authentication(login process).
The client then stores this token, usually in local storage or a cookie.
For every subsequent request that requires authentication, the client sends the JWT in the request headers.
The server checks the token to make sure it’s valid and that the user has the right permissions before allowing access.
ACCESS TOKENS
Access tokens are temporary keys that allow users to access specific resources after logging in. They contain user information and are sent with requests to verify permissions, usually expiring after a short time for added security.
REFRESH TOKENS
A refresh token is a special type of key that helps you get new access tokens. This way, you can use short-lived access tokens that expire quickly without needing to log in again each time one runs out.
Access tokens contain user information and are valid for a short time. Refresh tokens, on the other hand, are kept safe as HTTP-only cookies(a type of cookie that can only be accessed by the server and not by client-side scripts, such as JavaScript). They allow you to stay logged in longer without risking your sensitive information being accessed by client-side JavaScript.
Integrating Access and Refresh Tokens
Step 1: Setup Environment
Install Dependencies: Make sure you have the necessary packages installed:
dotenv
,express
,cookie-parser
, andjsonwebtoken
.Environment Variables: Set up your
.env
file with two secrets:ACCESS_TOKEN_SECRET=your_access_token_secret REFRESH_TOKEN_SECRET=your_refresh_token_secret
Step 2: Initialize Express App
Create an Express Application: Use Express to handle HTTP requests.
Middleware Configuration: Set up middleware to parse JSON and URL-encoded data, and to handle cookies.
const app = express(); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieparser());
Step 3: Define User Credentials
Simulated User Data: Create an object with sample user credentials for authentication.
const userCredentials = { username: 'sankar', password: 'sankarlal2024', email: 'sankarlal@gmail.com' }
Step 4: Implement Login Route
Handle Login Requests: Create a POST endpoint
/login
to authenticate users.Check Credentials: Verify the provided username and password against the stored credentials.
app.post('/login', (req, res) => { // Destructuring username & password from body const { username, password } = req.body; // Checking if credentials match if (username === userCredentials.username && password === userCredentials.password) { //creating a access token const accessToken = jwt.sign({ username: userCredentials.username, email: userCredentials.email }, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '10m' }); // Creating refresh token not that expiry of refreh token const refreshToken = jwt.sign({ username: userCredentials.username, }, process.env.REFRESH_TOKEN_SECRET, { expiresIn: '1d' }); // Assigning refresh token in http-only cookie res.cookie('jwt', refreshToken, { httpOnly: true, sameSite: 'None', secure: true, maxAge: 24 * 60 * 60 * 1000 }); return res.json({ accessToken }); } else { // Return unauthorized error if credentials don't match return res.status(406).json({ message: 'Invalid credentials' }); } })
Step 5: Implement Token Refresh Route
Handle Refresh Requests: Create a POST endpoint
/refresh
to obtain a new access token using the refresh token.Verify Refresh Token: Check if the refresh token exists and is valid.
app.post('/refresh', (req, res) => { if (req.cookies?.jwt) { // Destructuring refreshToken from cookie const refreshToken = req.cookies.jwt; // Verifying refresh token jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, (err, decoded) => { if (err) { // Wrong Refesh Token return res.status(406).json({ message: 'Unauthorized' }); } else { // Correct token we send a new access token const accessToken = jwt.sign({ username: userCredentials.username, email: userCredentials.email }, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '15m' }); return res.json({ accessToken }); } }) } else { return res.status(406).json({ message: 'Unauthorized' }); } })
Step 6: Start the Server
Run the Application: Listen on a specified port to start the server.
app.listen(6700, () => { console.log(`Server active on http://localhost:${6700}!`); });
So this code demonstrates how to use access tokens for immediate access and refresh tokens for prolonged authentication. The access token is short-lived and needs to be refreshed periodically using the refresh token stored securely in an HTTP-only cookie, ensuring better security and user experience.