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

  1. Login: User provides email/password
  2. Verification: Server validates credentials
  3. Token Generation: Server creates access & refresh tokens
  4. Client Storage: Tokens stored securely (httpOnly cookies recommended)
  5. API Requests: Access token sent in Authorization header
  6. Token Refresh: Before expiry, use refresh token for new access token
  7. 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

ClaimPurposeExample
subSubject (User ID)"user_123"
emailUser email"user@example.com"
roleUser role"admin"
iatIssued at (Unix timestamp)1704067200
expExpiration (Unix timestamp)1704068100
issIssuer"https://aim.example.com"
audAudience"aim-api"
jtiJWT ID (unique)"jwt_abc123"