Access Token vs Refresh Token: Understanding Secure Authentication

Access Token vs Refresh Token: Understanding Secure Authentication

In modern web applications, secure authentication is crucial to protect user data and ensure a smooth login experience. Two critical components of authentication are Access Tokens and Refresh Tokens. These tokens work together to verify user identity, maintain session security, and reduce the need for repeated logins. In this article, we’ll explore what they are, how they function, and best practices for using them effectively.


πŸ”Ή What is an Access Token?

An Access Token is a short-lived credential used to authenticate a user’s request to access a protected resource, such as an API or a web service. It is usually issued by an authentication server when a user logs in and must be included in the request headers for secure communication.

βœ… Key Features of an Access Token

  • Short lifespan (e.g., 15 minutes to 1 hour)

  • Encoded using JWT (JSON Web Token) format

  • Contains user information (e.g., user ID, role, permissions)

  • Sent with every API request in the Authorization header

  • Cannot be refreshed once expired; requires re-authentication or a refresh token

πŸ“Œ Example: Decoded Access Token (JWT)

{
  "_id": "user123",
  "role": "admin",
  "iat": 1700000000,  // Issued at
  "exp": 1700000900   // Expires in 15 minutes
}

πŸ”„ What is a Refresh Token?

A Refresh Token is a long-lived token used to generate a new Access Token without requiring the user to log in again. Since Access Tokens expire quickly, Refresh Tokens ensure users do not need to authenticate frequently while maintaining security.

βœ… Key Features of a Refresh Token

  • Longer lifespan (e.g., 7 to 30 days)

  • Used only to obtain a new Access Token

  • Stored securely (e.g., HTTP-only cookies, database)

  • Cannot be used to access APIs directly

  • Can be revoked if necessary (e.g., user logs out or session is invalidated)

πŸ“Œ Example: Decoded Refresh Token (JWT)

{
  "_id": "user123",
  "iat": 1700000000,
  "exp": 1700864000   // Expires in 7 days
}

⚑ How Do Access and Refresh Tokens Work Together?

1️⃣ User logs in β†’ Server issues an Access Token and a Refresh Token

2️⃣ User makes API requests β†’ Sends Access Token in the Authorization header

3️⃣ Access Token expires β†’ User sends Refresh Token to request a new one

4️⃣ If Refresh Token is valid β†’ Server issues a new Access Token

5️⃣ If Refresh Token is invalid or expired β†’ User must log in again


πŸ›  Implementing Access & Refresh Tokens in Node.js

πŸ”Ή Step 1: Install Required Packages

npm install jsonwebtoken dotenv express cookie-parser

πŸ”Ή Step 2: Configure Environment Variables (.env)

ACCESS_TOKEN_SECRET=secret
ACCESS_TOKEN_EXPIRY=1d
REFRESH_TOKEN_SECRET=secret
REFRESH_TOKEN_EXPIRY=10d

πŸ”Ή Step 3: Generate Tokens

import jwt from "jsonwebtoken";
import dotenv from "dotenv";
dotenv.config({ path: './.env' });

generateAccessToken = function ( userId, email ) {
    return jwt.sign(
        {
            _id: userId,
            email: email,
        },
        process.env.ACCESS_TOKEN_SECRET,
        {
            expiresIn: process.env.ACCESS_TOKEN_EXPIRY
        }
    )
}

generateRefreshToken = function ( userId ) {
    return jwt.sign(
        {
            _id: userId,
        },
        process.env.REFRESH_TOKEN_SECRET,
        {
            expiresIn: process.env.REFRESH_TOKEN_EXPIRY
        }
    )
}

export {
    generateAccessToken,
    generateRefreshToken,
}

πŸ”Ή Step 4: Authenticate Users

const loginUser = (req, res) => {
    const { email, password } = req.body;
    const user = { _id: "user123", email: "test@example.com" };

    if (!user) return res.status(401).json({ message: "Invalid credentials" });

    const accessToken = generateAccessToken(user._id, user.email );
    const refreshToken = generateRefreshToken(user._id);

    const options = { httpOnly: true, secure: true, sameSite: "Strict" }
    res
        .cookie("refreshToken", refreshToken, options);
        .json({ accessToken });
};

πŸ”Ή Step 5: Middleware for Protecting Routes

const authenticateToken = (req, res, next) => {
    const token = req.header("Authorization")?.split(" ")[1];
    if (!token) return res.status(401).json({ message: "Access Denied" });
    try {
        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        req.user = decoded;
        next();
    } catch (error) {
        res.status(403).json({ message: "Invalid Token" });
    }
};
export default authenticateToken;

πŸ”Ή Step 6: Refreshing the Access Token

const refreshAccessToken = (req, res) => {
    const { refreshToken } = req.cookies;
    if (!refreshToken) return res.status(401).json({ message: "No token provided" });

    jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, (err, user) => {
        if (err) return res.status(403).json({ message: "Invalid refresh token" });
        const newAccessToken = generateAccessToken(user._id)
        res.json({ accessToken: newAccessToken });
    });
};

πŸ”Ή Step 7: Logging Out Securely

const logoutUser = (req, res) => {
    res.clearCookie("refreshToken");
    res.json({ message: "Logged out successfully" });
};

πŸ” Best Security Practices

βœ” Use HTTPS to prevent token interception

βœ” Store Refresh Tokens securely (HTTP-only, Secure cookies)

βœ” Use short expiry for Access Tokens (15-30 minutes)

βœ” Rotate Refresh Tokens to enhance security

βœ” Blacklist compromised Refresh Tokens in a database


πŸš€ Conclusion

Access Tokens and Refresh Tokens work together to create a secure authentication system. While the Access Token ensures quick API access, the Refresh Token extends the session without requiring frequent logins. Implementing them correctly enhances security, improves user experience, and reduces authentication overhead.

By following best practices, you can effectively protect your users while providing seamless authentication. Let us know if you have any questions or need further guidance! πŸ”πŸš€

Β