Skip to content

Token Management

Learn how to handle JWT token expiration and refresh tokens.

Token Lifetimes

Token Lifetime Purpose
Access Token 60 minutes Make API requests
Refresh Token 1 day Get new access token when expired

When Access Token Expires

After 60 minutes, API requests will return 401 Unauthorized:

{
  "detail": "Given token not valid for any token type",
  "code": "token_not_valid"
}

Solution: Use the refresh token to get a new access token.

Refreshing Access Token

Request

POST /api/token/refresh/
Content-Type: application/json

{
  "refresh": "eyJ0eXAiOiJKV1QiLCJhbGc..."
}

Response

{
  "access": "eyJ0eXAiOiJKV1QiLCJhbGc..."
}

JavaScript Implementation

async function refreshAccessToken() {
  const refreshToken = window.authTokens.refresh;

  try {
    const response = await fetch('http://localhost:8000/api/token/refresh/', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        refresh: refreshToken
      })
    });

    if (response.ok) {
      const data = await response.json();

      // Update access token
      window.authTokens.access = data.access;

      console.log('Token refreshed successfully');
      return true;
    } else {
      console.error('Token refresh failed');
      return false;
    }
  } catch (error) {
    console.error('Error refreshing token:', error);
    return false;
  }
}

Automatic Token Refresh

Handle 401 errors automatically and retry the request:

async function makeAPIRequest(endpoint, options = {}, retry = true) {
  const accessToken = window.authTokens.access;

  const response = await fetch(`http://localhost:8000${endpoint}`, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
    }
  });

  // If 401 and we haven't retried yet, refresh token
  if (response.status === 401 && retry) {
    console.log('Token expired, refreshing...');

    const refreshed = await refreshAccessToken();

    if (refreshed) {
      // Retry original request with new token
      return makeAPIRequest(endpoint, options, false);
    } else {
      // Refresh failed - user needs to re-authenticate
      console.log('Session expired. Please sign in again.');
      redirectToLogin();
    }
  }

  return response;
}

Complete Example (React with All Auth Methods)

import { useState, useEffect } from 'react';

function App() {
  const [tokens, setTokens] = useState({ access: null, refresh: null });
  const [user, setUser] = useState(null);

  // Generic authenticate function (works with all methods)
  const authenticate = async (endpoint, authData) => {
    const response = await fetch(`http://localhost:8000${endpoint}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(authData)
    });

    if (response.ok) {
      const data = await response.json();
      setTokens({ access: data.access, refresh: data.refresh });
      setUser(data.user);
      return true;
    }
    return false;
  };

  // Email/Password Signup
  const handleSignup = async (email, password, firstName, lastName) => {
    return authenticate('/api/signup/', {
      email,
      password,
      password_confirm: password,
      first_name: firstName,
      last_name: lastName
    });
  };

  // Email/Password Login
  const handleEmailLogin = async (email, password) => {
    return authenticate('/api/login/', { email, password });
  };

  // Google OAuth
  const handleGoogleSignIn = async (googleToken) => {
    return authenticate('/api/auth/google/', { access_token: googleToken });
  };

  // Facebook OAuth
  const handleFacebookSignIn = async (facebookToken) => {
    return authenticate('/api/auth/facebook/', { access_token: facebookToken });
  };

  // Microsoft OAuth
  const handleMicrosoftSignIn = async (microsoftToken) => {
    return authenticate('/api/auth/microsoft/', { access_token: microsoftToken });
  };

  // Refresh access token
  const refreshToken = async () => {
    const response = await fetch('http://localhost:8000/api/token/refresh/', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ refresh: tokens.refresh })
    });

    if (response.ok) {
      const data = await response.json();
      setTokens(prev => ({ ...prev, access: data.access }));
      return true;
    }
    return false;
  };

  // Make API request with automatic refresh
  const apiRequest = async (endpoint, options = {}) => {
    let response = await fetch(`http://localhost:8000${endpoint}`, {
      ...options,
      headers: {
        ...options.headers,
        'Authorization': `Bearer ${tokens.access}`,
        'Content-Type': 'application/json',
      }
    });

    // Handle 401 - token expired
    if (response.status === 401) {
      const refreshed = await refreshToken();
      if (refreshed) {
        // Retry with new token
        response = await fetch(`http://localhost:8000${endpoint}`, {
          ...options,
          headers: {
            ...options.headers,
            'Authorization': `Bearer ${tokens.access}`,
            'Content-Type': 'application/json',
          }
        });
      }
    }

    return response;
  };

  // Logout
  const handleLogout = () => {
    setTokens({ access: null, refresh: null });
    setUser(null);
    console.log('Logged out successfully');
  };

  // Example: Fetch user profile
  useEffect(() => {
    if (tokens.access) {
      apiRequest('/api/me/')
        .then(res => res.json())
        .then(data => console.log('Profile:', data));
    }
  }, [tokens.access]);

  return (
    <div>
      {user ? (
        <div>
          <h1>Welcome, {user.first_name}!</h1>
          <p>Email: {user.email}</p>
          <button onClick={handleLogout}>Logout</button>
        </div>
      ) : (
        <div>
          <button onClick={() => handleGoogleSignIn('GOOGLE_TOKEN')}>
            Sign in with Google
          </button>
          <button onClick={() => handleFacebookSignIn('FACEBOOK_TOKEN')}>
            Sign in with Facebook
          </button>
          <button onClick={() => handleMicrosoftSignIn('MICROSOFT_TOKEN')}>
            Sign in with Microsoft
          </button>
          <button onClick={() => handleEmailLogin('[email protected]', 'password')}>
            Sign in with Email
          </button>
        </div>
      )}
    </div>
  );
}

When Refresh Token Expires

After 1 day, the refresh token expires. When this happens:

  1. Refresh attempt will return 401 Unauthorized
  2. User must sign in again with Google OAuth
  3. Clear all stored tokens
function handleRefreshFailure() {
  // Clear tokens
  window.authTokens = { access: null, refresh: null };

  // Show login screen
  alert('Your session has expired. Please sign in again.');

  // Redirect to login
  window.location.href = '/login';
}

Testing Token Refresh

With cURL

# 1. Get tokens from authentication
ACCESS_TOKEN="your_access_token"
REFRESH_TOKEN="your_refresh_token"

# 2. Make API request (works)
curl http://localhost:8000/api/me/ \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# 3. Wait 60+ minutes (or manually expire token)

# 4. API request fails with 401
curl http://localhost:8000/api/me/ \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# 5. Refresh the token
NEW_ACCESS_TOKEN=$(curl -X POST http://localhost:8000/api/token/refresh/ \
  -H "Content-Type: application/json" \
  -d "{\"refresh\":\"$REFRESH_TOKEN\"}" \
  | jq -r '.access')

# 6. Use new access token (works)
curl http://localhost:8000/api/me/ \
  -H "Authorization: Bearer $NEW_ACCESS_TOKEN"

Best Practices

✅ DO:

  • Refresh tokens automatically on 401 errors
  • Store refresh token securely
  • Clear tokens on logout
  • Handle refresh failures gracefully
  • Show "session expired" message to users

❌ DON'T:

  • Ignore 401 errors
  • Try to refresh an expired refresh token
  • Store tokens in localStorage
  • Refresh on every request (only when needed)

Error Codes

Status Error Code Meaning Solution
401 token_not_valid Access token expired Refresh token
401 token_not_valid Refresh token expired Re-authenticate with Google
400 invalid_request Missing or malformed request Check request format

Summary

  1. Access tokens expire after 60 minutes → Use refresh token to get new one
  2. Refresh tokens expire after 1 day → User must sign in again with Google
  3. Handle 401 errors → Automatically refresh and retry
  4. Clear tokens on refresh failure → Prompt user to sign in again

Next Steps