← Back to Blogs

Understanding Tokens.

Harsh Ranjan JhaWritten by Harsh Ranjan JhaPublished on 2025-02-21·5 min read

Introduction.

if you have ever logged into a website and stayed logged-in for a while without re-entering your password, you have likely used “Access Tokens” and “Refresh Tokens” without even realizing it. These tokens play a crucial role in authentication and security.

Q. What is an Access Token?

Ans:- Access token is a short lived credential used to authenticate users. When a user logs in, the server issues an access token, which the client(e.g., browser , mobile app) sends with each request to prove its identity.

Q. How does an Access Token Work?

  1. The user logs in with their email and password.

  2. The server verifies the credentials and issues an access token.

  3. The client stores the token and sends it in the Authorization header of every request.(We will see later on how and where to store it so that it can’t be misused)

  4. The server validates the token and processes the request.

  5. If the token is valid, the server responds with protected data.

  6. If the token is expired or invalid, the client must request a new one.

Example of Generating an Access Token

Access tokens are typically created using JWT (JSON Web Token). Here’s how you generate an access token in Node.js using the jsonwebtoken library:

const jwt = require("jsonwebtoken");

const generateAccessToken = (user) => {
    return jwt.sign(
        { _id: user._id, email: user.email, username: user.username },
        process.env.ACCESS_TOKEN_SECRET,
        { expiresIn: "15m" } // Access token expires in 15 minutes
    );
};

Q. What is a Refresh Token?

An Access Token expires quickly for security reasons. To avoid forcing users to log in frequently, we use a Refresh Token.

A Refresh Token is a long-lived token stored securely and used to obtain a new Access Token when the previous one expires.

Q. How does a Refresh Token Work?

  1. When the user logs in, the server issues both an Access Token and a Refresh Token.

  2. When the Access Token expires, the client sends the Refresh Token to the server.

  3. If the Refresh Token is valid, the server issues a new Access Token.

  4. If the Refresh Token is expired or invalid, the user must log in again.

Example of Generating a Refresh Token

const generateRefreshToken = (user) => {
    return jwt.sign(
        { _id: user._id },
        process.env.REFRESH_TOKEN_SECRET,
        { expiresIn: "7d" } // Refresh token expires in 7 days
    );
};

Where to Store Tokens Securely?

Storing the Access Token

Best practice: Memory or Http-Only Cookies

  • Access tokens should NOT be stored in local storage (as they are vulnerable to XSS attacks).

  • Instead, they should be stored in memory (for single-page apps) or sent as an HTTP-only cookie.

Storing the Refresh Token

Best practice: HTTP-Only Secure Cookies

Refresh tokens need higher security because they are long-lived. The safest way to store them is in HTTP-only, Secure, Same-Site cookies

res.cookie("refreshToken", token, {
    httpOnly: true,  // Prevents JavaScript access (prevents XSS attacks)
    secure: true,    // Ensures the cookie is sent only over HTTPS
    sameSite: "Strict", // Protects against CSRF attacks
    maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
});

Security Risks and How to Prevent Them?

While tokens improve security, they can still be vulnerable to certain attacks. Here’s how to mitigate these risks:

  1. Refresh Token Theft

    Risk: If an attacker steals the refresh token, they can generate new access tokens.

    Prevention:

    • Rotate refresh tokens (Issue a new one and invalidate the old one on each refresh request).

    • Store refresh tokens only in HTTP-only cookies (Prevents JavaScript access).

    • Limit refresh token lifespan (e.g., 7 days).

  1. Token Reuse Attack

    Risk: A stolen refresh token could be used multiple times.

    Prevention:

    • Implement one-time-use refresh tokens (Invalidate old tokens upon issuing a new one).

    • Store refresh tokens in a database and check for mismatches.

  1. Session Hijacking

    Risk: If an attacker hijacks a user’s session, they can reuse the refresh token.

    Prevention:

    • Use device-based refresh tokens (Issue different tokens for different devices).

    • Implement IP and device fingerprint checks.

    • Monitor unusual login locations and revoke tokens if needed.

  1. XSS Attacks (Cross-Site Scripting)

    Risk: If an attacker injects malicious JavaScript, they could steal tokens stored in local storage.

    Prevention:

    • Never store tokens in local storage.

    • Use Content Security Policy (CSP) headers to block malicious scripts.

    • Store tokens in memory or HTTP-only cookies.

CSRF Attacks (Cross-Site Request Forgery)

Risk: An attacker could trick a user into making an unauthorized request.

Prevention:

  • Store refresh tokens with Same-Site Strict cookies.(Cookies are small pieces of data stored on the client’s browser and sent with requests to the server. They help manage sessions, store authentication tokens securely, and prevent CSRF attacks.)

  • Implement CSRF tokens for critical actions.

Implementing Token Authentication in an API

Login Route: Issue Tokens

app.post("/login", async (req, res) => {
    const user = await User.findOne({ email: req.body.email });
    if (!user) return res.status(401).json({ error: "User not found" });

    const accessToken = generateAccessToken(user);
    const refreshToken = generateRefreshToken(user);

    user.refreshToken = refreshToken;
    await user.save();

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

Conclusion

Access Tokens and Refresh Tokens are essential for secure authentication in modern web applications. By implementing security best practices, you can minimize risks and keep user sessions safe.

Using these best practices will help you build a secure and scalable authentication system