DEV Community

Cover image for OAuth2 Login with JWT and Refresh Tokens in Spring Boot — The Setup You'll Rebuild Every Time
Yadrs
Yadrs

Posted on

OAuth2 Login with JWT and Refresh Tokens in Spring Boot — The Setup You'll Rebuild Every Time

Most Spring Boot applications eventually need authentication.

And many teams rebuild the same foundation every time.

Add a login endpoint. Generate a token. Protect some APIs.
Then production arrives — and the real requirements show up.

A real backend often needs:

  • external identity providers
  • secure access tokens
  • refresh token rotation
  • user persistence
  • token expiration handling
  • protected APIs
  • logout support
  • environment-based secrets

This is where OAuth2 combined with JWT access tokens and refresh tokens becomes a common approach.

This guide walks through the architecture, moving parts, and decisions behind building a production-style authentication flow with Spring Security.


The Problem with Basic Token Authentication

A simple JWT authentication flow usually looks like this:

User logs in
      ↓
Backend validates credentials
      ↓
Backend creates JWT
      ↓
Frontend stores JWT
      ↓
JWT is sent with API requests
Enter fullscreen mode Exit fullscreen mode

For small applications, this works.

But production systems quickly need more:

What happens when the JWT expires?

How does a user stay logged in?

How do you revoke access?

Where are user identities stored?

How do you support Google login?
Enter fullscreen mode Exit fullscreen mode

This is why many applications move toward:

OAuth2 Login
      +
JWT Access Tokens
      +
Refresh Tokens
Enter fullscreen mode Exit fullscreen mode

Understanding the Authentication Flow

A common production flow looks like this:

User
 ↓
Login with Google/GitHub
 ↓
OAuth2 Provider verifies identity
 ↓
Spring Security receives user info
 ↓
Backend creates application tokens
 ↓
Return:
   - short-lived access token
   - longer-lived refresh token
Enter fullscreen mode Exit fullscreen mode

After login, the client does not call Google for every request.

Instead:

Client
 ↓
Authorization: Bearer JWT
 ↓
Spring Boot API
 ↓
JWT validation
 ↓
Protected resource
Enter fullscreen mode Exit fullscreen mode

Your backend remains responsible for securing its own APIs.


Access Tokens vs Refresh Tokens

A common mistake is treating one JWT as enough.

A better pattern separates responsibilities.

Access Token

Purpose:

Authorize API requests
Enter fullscreen mode Exit fullscreen mode

Characteristics:

  • short expiration time
  • sent with requests
  • stateless validation
  • contains limited claims

Example:

Expires in:
15 minutes
Enter fullscreen mode Exit fullscreen mode

Refresh Token

Purpose:

Request a new access token
Enter fullscreen mode Exit fullscreen mode

Characteristics:

  • longer lifetime
  • stored securely
  • persisted in database
  • can be revoked

Example:

Expires in:
7-30 days
Enter fullscreen mode Exit fullscreen mode

Flow:

Access token expires
          ↓
Client sends refresh token
          ↓
Backend validates refresh token
          ↓
New access token issued
Enter fullscreen mode Exit fullscreen mode

The user stays logged in without keeping permanent access tokens.


Typical Spring Boot Structure

A production authentication module usually introduces:

security/
├── JwtService           → creates and validates tokens
├── JwtAuthenticationFilter → intercepts requests before controllers
├── OAuth2SuccessHandler → handles post-login token generation
└── SecurityConfig       → wires the filter chain together

entity/
├── User
└── RefreshToken

repository/
├── UserRepository
└── RefreshTokenRepository

controller/
└── AuthController
Enter fullscreen mode Exit fullscreen mode

Each component has a responsibility.


Spring Security Configuration

Spring Security becomes the entry point.

The configuration usually handles:

  • public endpoints
  • protected endpoints
  • OAuth2 login
  • JWT validation filters
  • CORS configuration
  • unauthorized responses

Conceptually:

Incoming request
        ↓
Security filter chain
        ↓
JWT filter
        ↓
Authentication context
        ↓
Controller
Enter fullscreen mode Exit fullscreen mode

The goal is:

Controllers should not manually check tokens.
Enter fullscreen mode Exit fullscreen mode

Security should happen before requests reach your business logic.


OAuth2 Success Handling

After a successful OAuth2 login:

Google authentication succeeds
          ↓
Spring Security receives user profile
          ↓
Find or create local user
          ↓
Generate application tokens
          ↓
Return tokens to client
Enter fullscreen mode Exit fullscreen mode

The application usually stores:

User:
- id
- email
- provider
- provider id

RefreshToken:
- token
- expiry
- associated user
Enter fullscreen mode Exit fullscreen mode

This lets your application own authorization decisions.


Token Refresh Flow

Refreshing authentication usually looks like:

POST /auth/refresh

Refresh token
       ↓
Validate token exists
       ↓
Check expiration
       ↓
Generate new JWT
       ↓
Return new access token
Enter fullscreen mode Exit fullscreen mode

Important considerations:

  • expire old tokens
  • support logout
  • remove compromised refresh tokens
  • avoid unlimited token lifetime

Environment Configuration

Production authentication should avoid hardcoded secrets.

Common configuration:

JWT_SECRET

JWT_EXPIRATION

GOOGLE_CLIENT_ID

GOOGLE_CLIENT_SECRET

FRONTEND_REDIRECT_URL
Enter fullscreen mode Exit fullscreen mode

These values should come from:

  • environment variables
  • deployment secrets
  • cloud secret managers

Never commit production credentials.


Common Production Improvements

After the basic flow works, teams often add:

Refresh Token Rotation

Instead of reusing refresh tokens:

Use refresh token
        ↓
Delete old token
        ↓
Create new token
Enter fullscreen mode Exit fullscreen mode

This reduces risk if tokens leak.


Secure Cookies

Some applications store tokens using:

HttpOnly
Secure
SameSite
cookies
Enter fullscreen mode Exit fullscreen mode

instead of browser storage.

The best choice depends on frontend architecture.


Rate Limiting

Authentication endpoints should be protected:

Examples:

/auth/login

/auth/refresh
Enter fullscreen mode Exit fullscreen mode

to reduce abuse.


What Goes Wrong in Production

A few things that catch teams off guard:

  • Refresh tokens stored in localStorage are vulnerable to XSS.
    HttpOnly cookies are safer but require careful CORS configuration.

  • Token expiration mismatches between frontend and backend
    can cause confusing 401 errors that are hard to debug.

  • Refresh token rotation without proper cleanup can create
    orphaned tokens that accumulate in your database.

  • Logout that only clears frontend storage is not a complete logout.
    The refresh token may still remain valid until expiration.


Why This Setup Becomes Repetitive

OAuth2 authentication is not just adding a dependency.

A complete setup usually includes:

  • Spring Security configuration
  • OAuth2 handlers
  • JWT utilities
  • refresh token persistence
  • database entities
  • repositories
  • exception handling
  • environment configuration

None of these pieces are impossible.

But most projects rebuild a very similar foundation.


Automating This Setup with SpringGen

After you understand the architecture, rebuilding these files for every new project becomes repetitive.

SpringGen helps generate Spring Boot backend foundations with:

  • OAuth2 authentication setup
  • JWT access tokens
  • refresh token flow
  • database configuration
  • security structure
  • Docker setup
  • deployment configuration

Generated projects are normal Spring Boot applications:

No runtime dependency
No vendor lock-in
Fully editable source code
Enter fullscreen mode Exit fullscreen mode

The goal is simple:

Spend less time rebuilding authentication infrastructure and more time building the application.


Try SpringGen

Free CLI:

npm install -g springgen
Enter fullscreen mode Exit fullscreen mode

GitHub:

https://github.com/springgen-dev/springgen

App:

https://app.springgen.dev


Final Thoughts

Authentication is one of the first systems every backend needs.

A good authentication setup is not only about logging users in.

It is about creating a foundation that is secure, maintainable, and ready to evolve.

Understanding the pieces matters.

Repeating the same setup forever does not.

Top comments (0)