🔄 Build a Secure JWT Auth API in PHP (Part 2: Refresh Tokens, Middleware & Token Rotation)

secure-jwt-auth-api-php-refresh-middleware

Welcome back to Part 2 of our in-depth JWT authentication API in PHP tutorial. In Part 1, we covered:

  • User registration
  • JWT generation
  • Refresh token issuance
  • Secure password and token storage

Now, in this second part, we’ll complete the system with:

  • A refresh token endpoint
  • Middleware to protect API routes
  • Token rotation & reuse detection
  • Logout endpoint and invalidation logic

Let’s dive right in — snippet by snippet!


🔄 Step 6: Refresh Token Endpoint (public/token.php)

<?php
require '../config/database.php';
require '../includes/jwt_utils.php';

$data = json_decode(file_get_contents("php://input"), true);
$refresh_token = $data['refresh_token'] ?? '';

if (!$refresh_token) {
    http_response_code(400);
    echo json_encode(["message" => "Refresh token required"]);
    exit;
}

$stmt = $pdo->prepare("SELECT id, refresh_token FROM users");
$stmt->execute();
$users = $stmt->fetchAll();

$found = false;
foreach ($users as $user) {
    if (password_verify($refresh_token, $user['refresh_token'])) {
        $found = true;
        $new_access_token = create_jwt($user['id']);
        $new_refresh_token = bin2hex(random_bytes(64));
        $hashed_refresh = password_hash($new_refresh_token, PASSWORD_DEFAULT);

        $update = $pdo->prepare("UPDATE users SET refresh_token = ? WHERE id = ?");
        $update->execute([$hashed_refresh, $user['id']]);

        echo json_encode([
            "access_token" => $new_access_token,
            "refresh_token" => $new_refresh_token
        ]);
        break;
    }
}

if (!$found) {
    http_response_code(401);
    echo json_encode(["message" => "Invalid refresh token"]);
}

🔍 What This Does:

  • Verifies the refresh token against all users (could be optimized).
  • Rotates the refresh token on success.
  • Issues a new access token.
  • Prevents reuse by invalidating the old token.

🛡️ Step 7: Create Authentication Middleware (includes/auth_middleware.php)

<?php
require 'jwt_utils.php';

function require_auth() {
    $headers = getallheaders();
    if (!isset($headers['Authorization'])) {
        http_response_code(401);
        echo json_encode(["message" => "Missing Authorization header"]);
        exit;
    }

    $token = trim(str_replace("Bearer", "", $headers['Authorization']));
    $decoded = validate_jwt($token);

    if (!$decoded) {
        http_response_code(401);
        echo json_encode(["message" => "Invalid or expired token"]);
        exit;
    }

    return $decoded->sub;
}

🧠 How It Works:

  • Extracts the Authorization: Bearer <token> header.
  • Validates the JWT.
  • Returns the user_id (sub) for use in protected endpoints.

🔐 Step 8: Protect a Route (public/protected.php)

<?php
require '../config/database.php';
require '../includes/auth_middleware.php';

$user_id = require_auth();

echo json_encode([
    "message" => "Welcome, user #$user_id! This is a protected resource."
]);

🔐 Explanation:

  • This endpoint is now protected.
  • Only requests with a valid JWT token in the header can access it.

🚪 Step 9: Logout / Revoke Refresh Token (public/logout.php)

<?php
require '../config/database.php';
require '../includes/auth_middleware.php';

$user_id = require_auth();

$stmt = $pdo->prepare("UPDATE users SET refresh_token = NULL WHERE id = ?");
$stmt->execute([$user_id]);

echo json_encode(["message" => "Logged out and token revoked"]);

🔐 Use Case:

  • Useful for frontend logout buttons.
  • Ensures refresh token can't be reused.

🧠 Best Practices Recap

  • 🔁 Rotate refresh tokens on every use.
  • ⛔ Invalidate refresh tokens on logout.
  • 🕵️‍♂️ Detect token reuse (log or block attempts from stolen refresh tokens).
  • 🔐 Store tokens in HttpOnly cookies on the frontend when possible.

🔍 SEO & Performance Insights

Using short-lived JWTs with refresh tokens enhances API security without constantly hitting the DB for session lookups. It also improves mobile app performance and fits perfectly with modern SPA frameworks like React, Vue, or Svelte.


✅ Conclusion

With this two-part tutorial, you’ve now built a full JWT-based auth system from scratch in PHP — complete with:

  • Registration
  • Secure login
  • Access & refresh tokens
  • Middleware protection
  • Token rotation & logout

🎯 Ready to take it further?

  • Add role-based access control
  • Track refresh token reuse attempts
  • Integrate OAuth or social logins

📣 Need help or want to showcase your version? Join our Facebook group to share and get feedback.


🤝 Stay Connected with Tech Talker 360

Post a Comment

0 Comments