JWT Token Authentication
JSON Web Tokens provide secure, stateless authentication for web applications and APIs with automatic refresh token rotation for enhanced security.
15 min
Access Token
7 days
Refresh Token
RS256
Algorithm
Rotation
Auto Refresh
Authentication Flow
- Login: User provides email/password
- Verification: Server validates credentials
- Token Generation: Server creates access & refresh tokens
- Client Storage: Tokens stored securely (httpOnly cookies recommended)
- API Requests: Access token sent in Authorization header
- Token Refresh: Before expiry, use refresh token for new access token
- Rotation: Old refresh token invalidated, new one issued
Implementation Examples
1. User Login
// POST /api/v1/public/login
const response = await fetch('https://aim.example.com/api/v1/public/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'user@example.com',
password: 'SecurePassword123!'
})
});
const data = await response.json();
// Response
{
"user": {
"id": "user_123",
"email": "user@example.com",
"role": "member",
"organization_id": "org_456"
},
"tokens": {
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 900, // 15 minutes
"refresh_expires_in": 604800 // 7 days
}
}2. Using Access Token
// Include token in Authorization header
const agents = await fetch('https://aim.example.com/api/v1/agents', {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});
// Token payload structure
{
"sub": "user_123", // User ID
"email": "user@example.com",
"role": "member",
"org": "org_456",
"permissions": [
"agent:read",
"agent:create",
"agent:verify"
],
"iat": 1704067200, // Issued at
"exp": 1704068100, // Expires at (15 min)
"iss": "https://aim.example.com",
"aud": "aim-api"
}3. Refresh Token Rotation
// POST /api/v1/auth/refresh
const response = await fetch('https://aim.example.com/api/v1/auth/refresh', {
method: 'POST',
headers: {
'Authorization': `Bearer ${refreshToken}`
}
});
const data = await response.json();
// Response with NEW tokens (rotation)
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", // New
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", // New (rotated)
"expires_in": 900,
"refresh_expires_in": 604800
}
// Old refresh token is now invalid!4. Auto-Refresh Implementation
class AuthService {
constructor() {
this.accessToken = null;
this.refreshToken = null;
this.refreshTimer = null;
}
async login(email, password) {
const response = await fetch('/api/v1/public/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
const data = await response.json();
this.setTokens(data.tokens);
return data;
}
setTokens(tokens) {
this.accessToken = tokens.access_token;
this.refreshToken = tokens.refresh_token;
// Schedule refresh 1 minute before expiry
const refreshIn = (tokens.expires_in - 60) * 1000;
this.scheduleRefresh(refreshIn);
}
scheduleRefresh(delay) {
// Clear existing timer
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
}
this.refreshTimer = setTimeout(() => {
this.refreshAccessToken();
}, delay);
}
async refreshAccessToken() {
try {
const response = await fetch('/api/v1/auth/refresh', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.refreshToken}`
}
});
if (response.ok) {
const tokens = await response.json();
this.setTokens(tokens);
} else {
// Refresh failed - redirect to login
this.logout();
}
} catch (error) {
console.error('Token refresh failed:', error);
this.logout();
}
}
async makeAuthenticatedRequest(url, options = {}) {
const response = await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${this.accessToken}`
}
});
// Handle 401 - try refresh once
if (response.status === 401) {
await this.refreshAccessToken();
// Retry request with new token
return fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${this.accessToken}`
}
});
}
return response;
}
logout() {
this.accessToken = null;
this.refreshToken = null;
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
}
// Redirect to login page
window.location.href = '/login';
}
}Security Best Practices
Token Storage
- httpOnly cookies: Prevents XSS access
- Secure flag: HTTPS only transmission
- SameSite: CSRF protection
- ✗localStorage: Vulnerable to XSS
Token Handling
- • Short access token lifetime (15 min)
- • Automatic refresh before expiry
- • One-time use refresh tokens
- • Invalidate tokens on logout
- • Monitor for token reuse attacks
- • Implement token blacklisting
Server-Side Token Validation
// Go example - Fiber middleware
func JWTMiddleware(jwtService *auth.JWTService) fiber.Handler {
return func(c fiber.Ctx) error {
// Extract token from Authorization header
authHeader := c.Get("Authorization")
if authHeader == "" {
return c.Status(401).JSON(fiber.Map{
"error": "Missing authorization header"
})
}
// Remove "Bearer " prefix
token := strings.Replace(authHeader, "Bearer ", "", 1)
// Validate token
claims, err := jwtService.ValidateToken(token)
if err != nil {
return c.Status(401).JSON(fiber.Map{
"error": "Invalid or expired token"
})
}
// Check token expiration
if time.Now().Unix() > claims.ExpiresAt {
return c.Status(401).JSON(fiber.Map{
"error": "Token expired"
})
}
// Store user info in context
c.Locals("user_id", claims.Subject)
c.Locals("role", claims.Role)
c.Locals("permissions", claims.Permissions)
return c.Next()
}
}JWT Claims Reference
| Claim | Purpose | Example |
|---|---|---|
| sub | Subject (User ID) | "user_123" |
| User email | "user@example.com" | |
| role | User role | "admin" |
| iat | Issued at (Unix timestamp) | 1704067200 |
| exp | Expiration (Unix timestamp) | 1704068100 |
| iss | Issuer | "https://aim.example.com" |
| aud | Audience | "aim-api" |
| jti | JWT ID (unique) | "jwt_abc123" |
🚀 Next Steps
Ready to implement JWT authentication?