Starship Rewards API

Token Management

Best practices for storing, refreshing, and managing authentication tokens

Token Management Best Practices

This guide covers production-ready token management strategies for secure and reliable authentication with the Starship Rewards API.

Token Storage Strategies

Web Applications (Browser)

# Secure token storage patterns with cURL
# Always use HTTPS in production

# Login and receive tokens via secure cookies
curl -X POST {{host}}/auth/login \
  -H "Content-Type: application/json" \
  -c cookies.txt \
  -d '{
    "email": "user@example.com",
    "password": "secure_password"
  }'

# Server sets secure HttpOnly cookies:
# Set-Cookie: access_token=...; HttpOnly; Secure; SameSite=Strict; Max-Age=3600
# Set-Cookie: refresh_token=...; HttpOnly; Secure; SameSite=Strict; Max-Age=2592000

# Use tokens from cookies for authenticated requests
curl -X GET {{host}}/api/protected-endpoint \
  -b cookies.txt \
  -H "Content-Type: application/json"

# Alternative: Use Authorization header with access token
curl -X GET {{host}}/api/protected-endpoint \
  -H "Authorization: Bearer your_access_token_here" \
  -H "Content-Type: application/json"
<?php
// Store tokens in secure HttpOnly cookies (recommended for web apps)
class TokenManager {
    public function setTokens($accessToken, $refreshToken) {
        // Store access token in memory/session
        $_SESSION['access_token'] = $accessToken;

        // Store refresh token in HttpOnly cookie
        setcookie('refresh_token', $refreshToken, [
            'expires' => time() + (30 * 24 * 60 * 60), // 30 days
            'httponly' => true,
            'secure' => true,
            'samesite' => 'Strict'
        ]);
    }

    public function getAccessToken() {
        return $_SESSION['access_token'] ?? null;
    }

    public function getRefreshToken() {
        return $_COOKIE['refresh_token'] ?? null;
    }

    public function clearTokens() {
        unset($_SESSION['access_token']);
        setcookie('refresh_token', '', [
            'expires' => time() - 3600,
            'httponly' => true,
            'secure' => true
        ]);
    }
}
?>

Automatic Token Refresh

# Automatic token refresh with cURL

# Initial login to get tokens
curl -X POST {{host}}/auth/login \
  -H "Content-Type: application/json" \
  -c cookies.txt \
  -d '{
    "email": "user@example.com",
    "password": "secure_password"
  }'

# Check if tokens need refreshing before API calls
# Server responds with token expiration info
curl -X GET {{host}}/auth/token-status \
  -b cookies.txt

# Response:
# {
#   "expires_in": 295,
#   "should_refresh": true,
#   "expires_at": "2024-01-15T10:35:00Z"
# }

# Refresh tokens when needed (5 minutes before expiration)
curl -X POST {{host}}/auth/refresh \
  -H "Content-Type: application/json" \
  -b cookies.txt \
  -c cookies.txt \
  -d '{
    "refresh_token": "stored_refresh_token"
  }'

# New tokens are set in cookies automatically

# Use refreshed tokens for API calls
curl -X GET {{host}}/api/protected-endpoint \
  -b cookies.txt
<?php
class TokenManager {
    private $accessToken;
    private $refreshToken;
    private $expirationTime;

    public function setTokens($accessToken, $refreshToken, $expiresIn) {
        $this->accessToken = $accessToken;
        $this->refreshToken = $refreshToken;
        $this->expirationTime = time() + $expiresIn;

        // Store in session
        $_SESSION['access_token'] = $accessToken;
        $_SESSION['token_expires_at'] = $this->expirationTime;
    }

    public function shouldRefreshToken($bufferSeconds = 300) {
        $currentTime = time();
        $expirationTime = $_SESSION['token_expires_at'] ?? 0;

        return ($expirationTime - $currentTime) <= $bufferSeconds;
    }

    public function refreshAccessToken() {
        $refreshToken = $_COOKIE['refresh_token'] ?? null;

        if (!$refreshToken) {
            throw new Exception('No refresh token available');
        }

        $data = json_encode(['refresh_token' => $refreshToken]);

        $context = stream_context_create([
            'http' => [
                'method' => 'POST',
                'header' => 'Content-Type: application/json',
                'content' => $data
            ]
        ]);

        $response = file_get_contents('{{host}}/auth/refresh', false, $context);

        if ($response === FALSE) {
            throw new Exception('Token refresh failed');
        }

        $tokenData = json_decode($response, true);

        $this->setTokens(
            $tokenData['access_token'],
            $tokenData['refresh_token'] ?? $refreshToken,
            $tokenData['expires_in']
        );

        return $tokenData;
    }

    public function getValidAccessToken() {
        if ($this->shouldRefreshToken()) {
            $this->refreshAccessToken();
        }

        return $_SESSION['access_token'] ?? null;
    }
}
?>

Token Validation

Client-side token validation helps prevent unnecessary API calls:

# Client-side token validation with cURL

# Decode JWT token payload (requires jq and base64)
# Note: This is for demonstration - normally done server-side

# Extract and decode JWT payload
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoxNjQyMjQ4MDAwfQ.signature"
PAYLOAD=$(echo $TOKEN | cut -d'.' -f2)

# Decode base64 payload (add padding if needed)
echo $PAYLOAD | base64 -d 2>/dev/null || echo "${PAYLOAD}===" | base64 -d

# Server-side token validation endpoint
curl -X POST {{host}}/auth/validate-token \
  -H "Content-Type: application/json" \
  -d '{
    "token": "your_access_token_here"
  }'

# Response:
# {
#   "valid": true,
#   "expired": false,
#   "expires_at": "2024-01-15T10:30:00Z",
#   "should_refresh": true,
#   "time_to_expiry": 295
# }

# Check token status before making API calls
curl -X GET {{host}}/auth/token-info \
  -H "Authorization: Bearer your_access_token"
<?php
class TokenValidator {
    public static function isTokenExpired($token) {
        if (empty($token)) return true;

        $parts = explode('.', $token);
        if (count($parts) !== 3) return true;

        try {
            $payload = json_decode(base64_decode(str_replace(['_', '-'], ['/', '+'], $parts[1])), true);
            $currentTime = time();
            return $payload['exp'] < $currentTime;
        } catch (Exception $e) {
            return true; // Invalid token format
        }
    }

    public static function shouldRefreshToken($token, $bufferMinutes = 5) {
        $expirationTime = self::getTokenExpirationTime($token);
        if ($expirationTime === null) return true;

        $currentTime = time() * 1000; // Convert to milliseconds
        $bufferTime = $bufferMinutes * 60 * 1000;
        return ($expirationTime - $currentTime) <= $bufferTime;
    }

    public static function getTokenExpirationTime($token) {
        if (empty($token)) return null;

        $parts = explode('.', $token);
        if (count($parts) !== 3) return null;

        try {
            $payload = json_decode(base64_decode(str_replace(['_', '-'], ['/', '+'], $parts[1])), true);
            return $payload['exp'] * 1000; // Convert to milliseconds
        } catch (Exception $e) {
            return null;
        }
    }
}
?>

Error Handling

Implement comprehensive error handling for authentication failures:

# Error handling and retry logic with cURL

# Function to handle authentication errors
handle_auth_error() {
    local status_code=$1
    local response=$2

    case $status_code in
        401)
            echo "Token expired or invalid - attempting refresh"
            # Attempt token refresh
            curl -X POST {{host}}/auth/refresh \
              -b cookies.txt -c cookies.txt \
              -H "Content-Type: application/json"

            if [ $? -eq 0 ]; then
                echo "Token refresh successful - retrying original request"
                return 0  # Success
            else
                echo "Token refresh failed - please log in again"
                return 1  # Failure
            fi
            ;;
        403)
            echo "Access denied - insufficient permissions"
            return 1
            ;;
        429)
            # Extract retry-after header if available
            retry_after=$(echo "$response" | grep -i "retry-after" | cut -d':' -f2 | tr -d ' ')
            echo "Rate limit exceeded - try again in ${retry_after:-60} seconds"
            return 1
            ;;
        *)
            echo "Request failed with status: $status_code"
            return 1
            ;;
    esac
}

# Example usage with error handling
response=$(curl -s -w "\n%{http_code}" {{host}}/api/protected \
  -b cookies.txt)

status_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | head -n -1)

if [ "$status_code" -eq 200 ]; then
    echo "Success: $body"
else
    handle_auth_error "$status_code" "$body"
fi
<?php
class AuthenticationManager {
    private $tokenManager;

    public function __construct($tokenManager) {
        $this->tokenManager = $tokenManager;
    }

    public function handleAuthError($httpCode, $response, $originalUrl, $originalData = null) {
        switch ($httpCode) {
            case 401:
                // Unauthorized - token invalid/expired
                if ($this->attemptTokenRefresh()) {
                    return $this->retryRequest($originalUrl, $originalData);
                } else {
                    $this->initiateLogin();
                    throw new Exception('Please log in again');
                }

            case 403:
                // Forbidden - valid token but insufficient permissions
                throw new Exception('Access denied. Contact support if this is unexpected.');

            case 429:
                // Rate limited
                $retryAfter = $this->getRetryAfterFromHeaders($response) ?: 60;
                throw new Exception("Rate limit exceeded. Try again in {$retryAfter} seconds.");

            default:
                throw new Exception("Request failed with status: {$httpCode}");
        }
    }

    public function attemptTokenRefresh() {
        try {
            $this->tokenManager->refreshAccessToken();
            return true;
        } catch (Exception $e) {
            error_log('Token refresh failed: ' . $e->getMessage());
            return false;
        }
    }
}
?>

Summary

  • Store tokens securely: Use HttpOnly cookies for web apps, OS keychain for native apps
  • Implement proactive refresh: Refresh tokens before they expire to avoid API interruptions
  • Validate tokens client-side: Check expiration before making API calls
  • Handle errors gracefully: Implement retry logic and fallback to login when necessary
  • Never store sensitive tokens in localStorage: This makes them vulnerable to XSS attacks

For more advanced topics, see Session Management and Security Considerations.