Understanding Login, JWT, Access Tokens, Refresh Tokens & JWK in Adarsh Autho Forge

A deep dive into how authentication actually works — access tokens, refresh tokens, JWKs, and secure login flows used by real-world identity systems.

  • Authentication
  • JWT
  • Security
  • Spring Boot
  • JWK
  • Microservices

Modern authentication systems — whether it’s Google, Auth0, Okta, or AWS Cognito — all use the same fundamental architecture for user login and token lifecycle: short-lived access tokens, long-lived refresh tokens, public verification keys, and secure password hashing.

In Adarsh Autho Forge, I built the same model but in a lightweight, self-hosted, developer-friendly form designed specifically for microservices.

This article explains in depth how the login flow works and why these concepts matter.


🔐 Why Authentication is Designed This Way

A login system needs to be:

  • Secure (no plaintext passwords, no leaked secrets)
  • Stateless (microservices should not call the auth server for every request)
  • Scalable (100k+ token verifications / second)
  • Extendable (roles, permissions, multi-device sessions)
  • Language-agnostic (other services may be in Go, Node, Rust, etc.)

To achieve all this, modern systems rely on:

  • JWT Access Tokens
  • Refresh Tokens
  • RSA Signing & JWK Publishing
  • Hashed credentials and tokens
  • Zero shared secrets between services

Let’s break this down in a simple way.


🔥 1. What is a JWT Access Token?

A JWT Access Token is:

✔ A digitally signed token
✔ Given to a user after login
✔ Sent with every request
✔ Short-lived (5–15 minutes)
✔ Verifiable without calling auth server
✔ Impossible to tamper with

Think of it like a boarding pass:

  • It proves who you are
  • What you’re allowed to do (roles)
  • When it expires
  • Who issued it
  • It has a signature microservices can verify

🧬 Anatomy of a JWT

A JWT has three parts:

  • Header, Payload and Signature

Header example:

{
  "alg": "RS256",
  "kid": "key_2025_01"
}

Payload example:

{
  "sub": 1,
  "username": "adarsh",
  "roles": ["ADMIN"],
  "exp": 1734550000,
  "iat": 1734548200,
  "iss": "https://autho-forge"
}

Signature:

Signed with the private RSA key stored ONLY in the auth server. Microservices validate it using the public JWK.

🔑 2. What is a Refresh Token?

A refresh token is:

  • ✔ A long-lived, secure random string
  • ✔ NOT a JWT
  • ✔ NOT signed
  • ✔ Impossible to guess
  • ✔ Stored hashed in the database
  • ✔ Used to issue new access tokens
  • ✔ Used to maintain long-term session

If your access token expires, the client can request:

POST /auth/refresh
{
  refreshToken: "raw-random-token"
}

The refresh token provides:

  • A new access token

  • A new refresh token (rotation)

This allows users to stay logged in for weeks/months without re-entering credentials.

🤔 Why do we need BOTH Access & Refresh Tokens?

Because they serve different purposes:

🟣 Access Token — Short-lived, high-speed security

  • Used in every API call

  • Verified instantly by microservices

  • No DB checks, no network calls

  • If stolen, damage is limited

🔵 Refresh Token — Long-lived session

  • Used rarely (only when the access token expires)

  • Allows issuing new tokens without re-login

  • Stored securely in a hashed format

  • Revocable

Together, they give:

✔ Security ✔ Performance ✔ Convenience ✔ Scalability

This is exactly how Auth0, Cognito, Google OAuth, and Okta work.

🗝️ 3. Why Use RSA Keys to Sign JWT?

JWTs are signed using the private RSA key inside the auth service.

Microservices verify tokens using the public RSA key, provided as JWK:

  • No shared secrets

  • No central dependency

  • No DB calls for each API request

  • Tokens can be verified locally in microservices

  • Perfect for distributed architectures

This makes the system truly scalable.

🌍 4. What is a JWK?

A JWK (JSON Web Key) is simply the public RSA key in JSON format, served at:

GET /.well-known/jwks.json

Example:

{
  "keys": [
    {
      "kid": "key_2025_01",
      "kty": "RSA",
      "alg": "RS256",
      "n": "base64EncodedModulus",
      "e": "AQAB"
    }
  ]
}

Microservices:

  • Fetch this once

  • Cache it

  • Use it to validate all tokens

This makes each microservice independent of the auth service.

🧱 5. Why Store Refresh Tokens Hashed?

If your DB gets hacked and someone sees:

refreshToken = "raw_token_123"

They can impersonate users.

So instead, you do:

hashedRefreshToken = bcrypt("raw_token_123")

Just like passwords.

Even if the DB leaks, sessions remain safe.

🔄 6. Complete Login Flow — Step by Step

Let’s tie everything together.

1️⃣ Client sends username + password

POST /auth/login

2️⃣ System fetches user

If user does not exist → throw InvalidCredentialsException

3️⃣ Verify password

Using:

passwordEncoder.matches(raw, hash)

4️⃣ Issue JWT Access Token

Signed with RSA private key

Contains roles

Includes expiry

5️⃣ Generate Refresh Token

Long-lived

Secure random

Stored hashed

6️⃣ Save refresh token in DB

Supports rotation + revocation.

7️⃣ Respond with TokenResponse

Contains:

  • accessToken

  • refreshToken

  • accessTokenExpiry

  • refreshTokenExpiry

📦 Summary Table

Token TypeFormatLifespanStored InPurpose
Access TokenJWT (signed)Short (5–15 min)Client onlyAuthorize API calls
Refresh TokenRandom stringLong (days/months)Database (hashed)Get new access tokens
JWKPublic keyAlways validAuth serviceValidate JWT signatures

🎯 Final Thoughts

Understanding how login, JWT, access tokens, refresh tokens, and JWKs work gives you the foundation for secure, scalable authentication systems.

With this knowledge, you now understand:

Why JWTs exist

Why we sign with RSA

Why JWK is published

Why access tokens must be short-lived

Why refresh tokens must be hashed

Why microservices validate tokens independently

How modern auth systems achieve both security and scalability

This is exactly the architecture used by:

  • Google

  • Facebook

  • Auth0

  • Okta

  • AWS Cognito

And now — Adarsh Autho Forge.