JWT Authentication System Explained - Building Secure APIs with Golang
A comprehensive guide to implementing production-grade JWT authentication in Go. Learn about access tokens, refresh tokens, security best practices, and protecting your APIs from common attacks.
JWT Authentication System Explained
Building secure APIs requires robust authentication mechanisms. In this guide, weโll explore how to implement a production-grade JWT (JSON Web Token) authentication system using Golang, complete with access tokens, refresh tokens, and comprehensive security measures.
๐ฐ The Castle Analogy
Think of your API as a castle with valuable treasures (user data, matches, etc.):
- Signup = Creating a new citizen account
- Login = Getting your daily entry pass (Access Token) + monthly pass (Refresh Token)
- Access Token = 15-minute temporary badge to enter castle rooms
- Refresh Token = 7-day voucher to get new temporary badges without re-login
- Logout = Throwing away your passes (client-side)
Why Two Tokens?
Access Token (15 min):
- Short-lived for security
- If stolen, attacker only has 15 minutes
- Sent with every API request
Refresh Token (7 days):
- Long-lived for convenience
- Used ONLY to get new access tokens
- Stored securely (httpOnly cookie in production)
- If stolen, attacker canโt access API directly, only get new access tokens
๐ Sequence Diagrams
1. Signup Flow

2. Login Flow

3. Refresh Token Flow

4. Protected API Request Flow

๐ JWT Security Deep Dive
JWT Structure
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzIiwiZW1haWwiOiJqb2huQGV4YW1wbGUuY29tIiwiZXhwIjoxNzAwMDAwMDAwfQ.signature_here
Header (Base64) Payload (Base64) Signature (HMAC-SHA256)
1. Header
{
"alg": "HS256", // HMAC with SHA-256
"typ": "JWT"
}
2. Payload (Claims)
{
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"email": "john.doe@example.com",
"exp": 1732560000, // Expiry timestamp
"iat": 1732559100 // Issued at timestamp
}
3. Signature
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret_key
)
๐ก๏ธ Security Measures in This Implementation
1. Password Security
// bcrypt with cost factor 12
// Input: "SecurePass123!"
// Output: "$2a$12$KIXxPz8vjT7lQ.zF3rF5h.BqV8..."
// - Salt automatically generated (random per password)
// - Computationally expensive (prevents brute force)
// - One-way hash (can't reverse)
Why bcrypt cost 12?
- Cost 10 = 77ms to hash
- Cost 12 = 250ms to hash โ Sweet spot
- Cost 14 = 1 second to hash
Higher cost = slower login BUT much harder to crack if database leaked.
2. Token Expiry Strategy
Access Token: 15 minutes
- Stolen token? Attacker has max 15 min
- User stays logged in via refresh tokens
- Sent with every request (higher exposure risk)
Refresh Token: 7 days
- Used rarely (only to refresh)
- Lower exposure risk
- If stolen, attacker can keep generating access tokens
- Should be stored in httpOnly cookie (future improvement)
3. Token Validation
// Step 1: Parse token structure
token, err := jwt.ParseWithClaims(...)
// Step 2: Verify signature (prevents tampering)
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return ErrInvalidToken
}
// Step 3: Check expiry
if errors.Is(err, jwt.ErrTokenExpired) {
return ErrExpiredToken
}
// Step 4: Validate claims
claims, ok := token.Claims.(*Claims)
if !ok || !token.Valid {
return ErrInvalidToken
}
4. Dual Token Check on Refresh
// Not just validating token...
// Also checking if user still active in DB
user, err := s.userRepo.FindByID(ctx, claims.UserID)
if !user.IsActive {
return ErrUnauthorized // User deactivated? No refresh!
}
๐จ Attack Scenarios & Protections
Scenario 1: Database Leaked
Attack: Hacker gets user table with password hashes
Protection:
- bcrypt hashes canโt be reversed
- Cracking one password takes hours/days
- Each password has unique salt
Scenario 2: Access Token Stolen (XSS)
Attack: Malicious JS steals token from localStorage
Protection:
- Only valid for 15 minutes
- After expiry, attacker is locked out
- User can change password โ invalidates refresh token
Scenario 3: Refresh Token Stolen
Attack: Attacker intercepts refresh token
Protection (Current):
- Must know when to use it (only when access expires)
- User can logout โ delete their tokens client-side
Future Improvement:
- Token rotation: New refresh token with each use
- Token families: Detect if old refresh token reused
- httpOnly cookies: JS canโt access
Scenario 4: Man-in-the-Middle (MITM)
Attack: Intercept HTTP requests
Protection:
- HTTPS/TLS in production (Render auto-provides)
- Tokens encrypted in transit
Scenario 5: Token Tampering
Attack: Modify token payload (change user_id)
Protection:
- HMAC signature verification
- Any change โ signature invalid โ rejected
๐ Token Lifecycle
Day 1, 9:00 AM - User logs in
โโ Access Token: Valid until 9:15 AM
โโ Refresh Token: Valid until Day 8, 9:00 AM
Day 1, 9:14 AM - Access token about to expire
โโ Client checks expiry
โโ Calls /refresh with refresh token
Day 1, 9:14 AM - Server issues new tokens
โโ NEW Access Token: Valid until 9:29 AM
โโ NEW Refresh Token: Valid until Day 8, 9:14 AM (rotation)
Day 1, 9:28 AM - Again, access token expiring
โโ Repeat refresh flow
Day 8, 9:00 AM - Refresh token expired
โโ User must login again (full authentication)
๐ Code Flow Summary
Signup:
- Validate email/phone not exists
- Hash password (bcrypt cost 12)
- Save user to database
- Generate access + refresh tokens
- Return tokens to client
Login:
- Find user by email
- Compare password hash
- Check user is active
- Generate tokens
- Update last_login
- Return tokens
Refresh:
- Validate refresh token signature
- Check token not expired
- Find user by ID from token
- Check user still active
- Generate NEW access token
- Generate NEW refresh token (rotation)
- Return new tokens
Protected Request:
- Extract token from header
- Validate token
- Extract user ID from claims
- Inject into request context
- Handler accesses user ID
- Process request
๐ฏ Key Takeaways
This implementation provides a production-grade JWT authentication system with:
โ
Short-lived access tokens (15 min) for security
โ
Long-lived refresh tokens (7 days) for convenience
โ
bcrypt password hashing (cost 12) for protection
โ
HMAC-SHA256 signatures to prevent tampering
โ
User status validation on every refresh
โ
Comprehensive error handling for all edge cases
โ
Context-based user injection for clean handler code
๐ฎ Future Enhancements
While this system is production-ready, here are some improvements to consider:
- Token Rotation: Issue new refresh token on each refresh
- Token Families: Track refresh token lineage to detect reuse
- httpOnly Cookies: Store refresh tokens in httpOnly cookies
- Rate Limiting: Prevent brute force attacks on login/refresh
- Device Tracking: Associate tokens with devices for better security
- Token Blacklisting: Ability to revoke tokens before expiry
- Multi-Factor Authentication: Add 2FA for additional security
๐ Further Reading
- JWT.io - JWT specification and debugger
- OWASP Authentication Cheat Sheet
- Go JWT Library Documentation
This authentication system demonstrates best practices for securing APIs with JWT tokens in Golang. The dual-token approach balances security and user experience, while the comprehensive validation ensures your API remains protected against common attack vectors.