JWT (JSON Web Token) is a compact, self-contained way to securely transmit information between parties as a JSON object.
A JWT token looks like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cIt has three parts separated by dots (.):
HEADER.PAYLOAD.SIGNATURE {
"alg": "HS256",
"typ": "JWT"
} {
"sub": "user123",
"email": "user@example.com",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516325422
} HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Browser │ │ Frontend │ │ Backend │
│ │ │ (React) │ │ (Spring Boot)│
└──────┬──────┘ └──────┬───────┘ └──────┬──────┘
│ │ │
│ 1. User clicks │ │
│ "Sign in with Google"│ │
├──────────────────────>│ │
│ │ │
│ │ 2. Redirect to │
│ │ accounts.google.com │
│<──────────────────────┤ │
│ │ │
┌──────▼──────┐ │ │
│ Google │ │ │
│ Login │ │ │
└──────┬──────┘ │ │
│ │ │
│ 3. User logs in │ │
│ at Google │ │
│ │ │
│ 4. Google redirects │ │
│ with authorization │ │
│ code │ │
├──────────────────────>│ │
│ /login/oauth2/code/ │ │
│ google?code=ABC123 │ │
│ │ │
│ │ 5. Send code to │
│ │ backend │
│ ├───────────────────────>│
│ │ POST /auth/google │
│ │ { "code": "ABC123" } │
│ │ │
│ │ │ 6. Backend calls
│ │ │ Google API to
│ │ │ exchange code
│ │ │ for access token
│ │ ├────────┐
│ │ │ │
│ │ │<───────┘
│ │ │
│ │ │ 7. Get user info
│ │ │ from Google
│ │ ├────────┐
│ │ │ │
│ │ │<───────┘
│ │ │
│ │ │ 8. Create JWT
│ │ │ token with
│ │ │ user info
│ │ ├────────┐
│ │ │ │
│ │ │<───────┘
│ │ │
│ │ 9. Return JWT token │
│ │<───────────────────────┤
│ │ { "token": "eyJ..." } │
│ │ │
│ │ 10. Store JWT in │
│ │ localStorage │
│ ├────────┐ │
│ │ │ │
│ │<───────┘ │
│ │ │
│ 11. User is now │ │
│ logged in │ │
│<──────────────────────┤ │
│ │ │What happens:
URL after Google redirect:
https://taskmanager.sriinfosoft.com/login/oauth2/code/google?code=4/0AbCD1234567890xyz&state=random_stateFrontend makes API call:
// React code
const handleGoogleCallback = async (code) => {
try {
const response = await fetch('https://api-taskmanager.sriinfosoft.com/auth/google', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ code: code })
});
const data = await response.json();
const jwtToken = data.token;
// Store JWT in localStorage
localStorage.setItem('jwt_token', jwtToken);
// Redirect to dashboard
navigate('/dashboard');
} catch (error) {
console.error('Login failed:', error);
}
};Spring Boot backend code:
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private OAuth2AuthorizedClientService clientService;
@Autowired
private JwtTokenProvider jwtTokenProvider;
@PostMapping("/google")
public ResponseEntity<?> authenticateWithGoogle(@RequestBody AuthRequest request) {
// 1. Exchange authorization code for access token
OAuth2AccessToken accessToken = exchangeCodeForToken(request.getCode());
// 2. Get user info from Google
GoogleUserInfo userInfo = getUserInfoFromGoogle(accessToken);
// 3. Save or update user in database
User user = userService.createOrUpdateUser(userInfo);
// 4. Generate JWT token
String jwtToken = jwtTokenProvider.createToken(user);
// 5. Return JWT to frontend
return ResponseEntity.ok(new AuthResponse(jwtToken));
}
private GoogleUserInfo getUserInfoFromGoogle(OAuth2AccessToken accessToken) {
// Make API call to Google
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken.getTokenValue());
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<GoogleUserInfo> response = restTemplate.exchange(
"https://www.googleapis.com/oauth2/v2/userinfo",
HttpMethod.GET,
entity,
GoogleUserInfo.class
);
return response.getBody();
}
}Google returns user info:
{
"id": "1234567890",
"email": "user@gmail.com",
"verified_email": true,
"name": "John Doe",
"given_name": "John",
"family_name": "Doe",
"picture": "https://lh3.googleusercontent.com/a/...",
"locale": "en"
}JwtTokenProvider class:
@Component
public class JwtTokenProvider {
@Value("${jwt.secret}")
private String jwtSecret;
@Value("${jwt.expiration}")
private long jwtExpiration;
public String createToken(User user) {
// Current time
Date now = new Date();
// Expiration time (24 hours from now)
Date expiryDate = new Date(now.getTime() + jwtExpiration);
// Build JWT token
return Jwts.builder()
.setSubject(user.getId().toString()) // User ID
.claim("email", user.getEmail()) // User email
.claim("name", user.getName()) // User name
.claim("roles", user.getRoles()) // User roles
.setIssuedAt(now) // Issued time
.setExpiration(expiryDate) // Expiration time
.signWith(SignatureAlgorithm.HS256, jwtSecret) // Sign with secret
.compact();
}
}Generated JWT token:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0IiwiZW1haWwiOiJ1c2VyQGdtYWlsLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsInJvbGVzIjpbIlVTRVIiXSwiaWF0IjoxNzI5NDU2Nzg5LCJleHAiOjE3Mjk1NDMxODl9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cDecoded payload:
{
"sub": "1234",
"email": "user@gmail.com",
"name": "John Doe",
"roles": ["USER"],
"iat": 1729456789,
"exp": 1729543189
}Frontend receives JWT:
// Response from backend
{
"token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0...",
"user": {
"id": 1234,
"email": "user@gmail.com",
"name": "John Doe"
}
}Store in localStorage:
// Store JWT token
localStorage.setItem('jwt_token', response.token);
// Store user info (optional)
localStorage.setItem('user', JSON.stringify(response.user));Frontend includes JWT in Authorization header:
// React code - Making API calls with JWT
const getTasks = async () => {
const token = localStorage.getItem('jwt_token');
try {
const response = await fetch('https://api-taskmanager.sriinfosoft.com/api/tasks', {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`, // ← JWT token included here
'Content-Type': 'application/json',
}
});
const tasks = await response.json();
return tasks;
} catch (error) {
console.error('Failed to fetch tasks:', error);
}
};HTTP Request looks like:
GET /api/tasks HTTP/1.1
Host: api-taskmanager.sriinfosoft.com
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0...
Content-Type: application/jsonJwtAuthenticationFilter intercepts every request:
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenProvider jwtTokenProvider;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 1. Extract JWT token from Authorization header
String token = getJwtFromRequest(request);
if (token != null && jwtTokenProvider.validateToken(token)) {
// 2. Extract user ID from token
Long userId = jwtTokenProvider.getUserIdFromToken(token);
// 3. Load user details
UserDetails userDetails = userDetailsService.loadUserById(userId);
// 4. Create authentication object
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
// 5. Set authentication in security context
SecurityContextHolder.getContext().setAuthentication(authentication);
}
// Continue with the request
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7); // Remove "Bearer " prefix
}
return null;
}
}JwtTokenProvider validates token:
public boolean validateToken(String token) {
try {
// Parse and verify JWT signature
Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token);
return true;
} catch (SignatureException ex) {
// Invalid JWT signature
return false;
} catch (MalformedJwtException ex) {
// Invalid JWT token
return false;
} catch (ExpiredJwtException ex) {
// Expired JWT token
return false;
} catch (UnsupportedJwtException ex) {
// Unsupported JWT token
return false;
} catch (IllegalArgumentException ex) {
// JWT claims string is empty
return false;
}
}
public Long getUserIdFromToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody();
return Long.parseLong(claims.getSubject());
}┌──────────────────────────────────────────────────────────┐
│ JWT Token Lifecycle │
└──────────────────────────────────────────────────────────┘
1. CREATION (Backend)
├─ User logs in successfully
├─ Backend generates JWT with user info
├─ JWT signed with secret key
└─ JWT sent to frontend
2. STORAGE (Frontend)
├─ JWT stored in localStorage or sessionStorage
├─ User info extracted and stored separately (optional)
└─ Token persists across page refreshes
3. USAGE (Every API Request)
├─ Frontend includes JWT in Authorization header
├─ Format: "Authorization: Bearer <token>"
└─ Sent with EVERY authenticated API request
4. VALIDATION (Backend - Every Request)
├─ Backend extracts token from Authorization header
├─ Verifies signature using secret key
├─ Checks expiration time
├─ Extracts user info from payload
└─ Allows or denies request
5. EXPIRATION (After 24 hours)
├─ Token becomes invalid
├─ Backend rejects requests with expired token
├─ Frontend receives 401 Unauthorized
└─ User must log in again
6. REFRESH (Optional)
├─ Use refresh token to get new access token
├─ Or simply require user to log in again
└─ New JWT issued with new expiration| Feature | JWT | Session Cookie |
|---|---|---|
| Storage | Frontend (localStorage) | Server-side |
| Stateless | Yes | No (requires session store) |
| Cross-domain | Easy (just send token) | Complex (CORS, SameSite) |
| Size | Larger (contains user info) | Smaller (just session ID) |
| Revocation | Hard (need blacklist) | Easy (delete session) |
| Scaling | Easy (no shared state) | Complex (shared session store) |
// Auth context provider
import { createContext, useContext, useState, useEffect } from 'react';
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [token, setToken] = useState(localStorage.getItem('jwt_token'));
// Check if user is logged in on app load
useEffect(() => {
if (token) {
// Decode JWT to get user info (without verification - that's done on backend)
const payload = JSON.parse(atob(token.split('.')[1]));
setUser({
id: payload.sub,
email: payload.email,
name: payload.name,
});
}
}, [token]);
const login = async (code) => {
const response = await fetch('https://api-taskmanager.sriinfosoft.com/auth/google', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code }),
});
const data = await response.json();
// Store token
localStorage.setItem('jwt_token', data.token);
setToken(data.token);
setUser(data.user);
};
const logout = () => {
localStorage.removeItem('jwt_token');
setToken(null);
setUser(null);
};
// API call with authentication
const apiCall = async (url, options = {}) => {
const headers = {
'Content-Type': 'application/json',
...options.headers,
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(url, {
...options,
headers,
});
if (response.status === 401) {
// Token expired or invalid
logout();
throw new Error('Session expired');
}
return response;
};
return (
<AuthContext.Provider value={{ user, login, logout, apiCall }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);Using the auth context:
// In a React component
import { useAuth } from './AuthContext';
function Dashboard() {
const { user, apiCall } = useAuth();
const [tasks, setTasks] = useState([]);
useEffect(() => {
const fetchTasks = async () => {
try {
const response = await apiCall('https://api-taskmanager.sriinfosoft.com/api/tasks');
const data = await response.json();
setTasks(data);
} catch (error) {
console.error('Failed to fetch tasks:', error);
}
};
fetchTasks();
}, []);
return (
<div>
<h1>Welcome, {user.name}!</h1>
<ul>
{tasks.map(task => (
<li key={task.id}>{task.title}</li>
))}
</ul>
</div>
);
}JWT Token Creation:
JWT Token Usage:
Key Benefits:
This is why JWT is perfect for your React + Spring Boot separated architecture with CloudFront!