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
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?
This is why many applications move toward:
OAuth2 Login
+
JWT Access Tokens
+
Refresh Tokens
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
After login, the client does not call Google for every request.
Instead:
Client
↓
Authorization: Bearer JWT
↓
Spring Boot API
↓
JWT validation
↓
Protected resource
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
Characteristics:
- short expiration time
- sent with requests
- stateless validation
- contains limited claims
Example:
Expires in:
15 minutes
Refresh Token
Purpose:
Request a new access token
Characteristics:
- longer lifetime
- stored securely
- persisted in database
- can be revoked
Example:
Expires in:
7-30 days
Flow:
Access token expires
↓
Client sends refresh token
↓
Backend validates refresh token
↓
New access token issued
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
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
The goal is:
Controllers should not manually check tokens.
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
The application usually stores:
User:
- id
- email
- provider
- provider id
RefreshToken:
- token
- expiry
- associated user
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
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
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
This reduces risk if tokens leak.
Secure Cookies
Some applications store tokens using:
HttpOnly
Secure
SameSite
cookies
instead of browser storage.
The best choice depends on frontend architecture.
Rate Limiting
Authentication endpoints should be protected:
Examples:
/auth/login
/auth/refresh
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
The goal is simple:
Spend less time rebuilding authentication infrastructure and more time building the application.
Try SpringGen
Free CLI:
npm install -g springgen
GitHub:
https://github.com/springgen-dev/springgen
App:
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)