Backend Development 8 min read

Build a Full JWT Authentication System in Native PHP Without Frameworks

Learn how to implement a complete JWT authentication workflow in pure PHP—from generating and verifying tokens, creating a simple login system, protecting routes with middleware, to adding token refresh logic—while covering essential security best practices without relying on any framework.

php中文网 Courses
php中文网 Courses
php中文网 Courses
Build a Full JWT Authentication System in Native PHP Without Frameworks

In modern web development, JSON Web Tokens (JWT) are a popular method for authentication and authorization. This guide shows how to implement a complete JWT authentication flow in native PHP without any framework.

What is JWT?

JWT (JSON Web Token) is an open standard (RFC 7519) for securely transmitting information as a JSON object. It consists of three parts:

Header – contains token type and hashing algorithm.

Payload – contains claims (user information and other data).

Signature – used to verify the message has not been altered.

Preparation

Before starting, ensure your PHP environment meets the following requirements:

PHP 7.2 or higher.

OpenSSL extension enabled (for signing and verification).

Basic knowledge of PHP and HTTP protocol.

Step 1: Create JWT Class

First, create a class that handles JWT generation and verification:

class JWT {
    private $secretKey;

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

    // Generate JWT
    public function encode(array $payload, $expiry = 3600): string {
        $header = json_encode([
            'typ' => 'JWT',
            'alg' => 'HS256'
        ]);

        $now = time();
        $payload['iat'] = $now;
        $payload['exp'] = $now + $expiry;
        $payload = json_encode($payload);

        $base64UrlHeader = $this->base64UrlEncode($header);
        $base64UrlPayload = $this->base64UrlEncode($payload);

        $signature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $this->secretKey, true);
        $base64UrlSignature = $this->base64UrlEncode($signature);

        return $base64UrlHeader . "." . $base64UrlPayload . "." . $base64UrlSignature;
    }

    // Verify and decode JWT
    public function decode(string $token): ?array {
        $parts = explode('.', $token);
        if (count($parts) !== 3) {
            return null;
        }

        list($base64UrlHeader, $base64UrlPayload, $base64UrlSignature) = $parts;

        $signature = $this->base64UrlDecode($base64UrlSignature);
        $expectedSignature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $this->secretKey, true);

        if (!hash_equals($signature, $expectedSignature)) {
            return null;
        }

        $payload = json_decode($this->base64UrlDecode($base64UrlPayload), true);

        if (isset($payload['exp']) && $payload['exp'] < time()) {
            return null;
        }

        return $payload;
    }

    private function base64UrlEncode(string $data): string {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }

    private function base64UrlDecode(string $data): string {
        return base64_decode(strtr($data, '-_', '+/'));
    }
}

Step 2: User Authentication and Token Issuance

Create a simple user authentication system to issue JWTs:

// Simulated user database
$users = [
    'user1' => password_hash('password1', PASSWORD_BCRYPT),
    'user2' => password_hash('password2', PASSWORD_BCRYPT)
];

// Initialize JWT class
$jwt = new JWT('your-secret-key-here');

// Handle login request
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['username'], $_POST['password'])) {
    $username = $_POST['username'];
    $password = $_POST['password'];

    if (isset($users[$username]) && password_verify($password, $users[$username])) {
        // Authentication successful, generate JWT
        $token = $jwt->encode([
            'sub' => $username,
            'role' => 'user'
        ]);

        // Set as HTTP‑only cookie or return to client
        setcookie('jwt', $token, time() + 3600, '/', '', false, true);
        echo json_encode(['token' => $token]);
        exit;
    } else {
        http_response_code(401);
        echo json_encode(['error' => 'Invalid credentials']);
        exit;
    }
}

Step 3: Protect Restricted Routes

Create a middleware function to validate JWT and protect routes:

function authenticate() {
    $jwt = new JWT('your-secret-key-here');

    // Retrieve token from Cookie or Authorization header
    $token = $_COOKIE['jwt'] ?? null;
    if (!$token && isset($_SERVER['HTTP_AUTHORIZATION'])) {
        if (preg_match('/Bearer\s(\S+)/', $_SERVER['HTTP_AUTHORIZATION'], $matches)) {
            $token = $matches[1];
        }
    }

    if (!$token) {
        http_response_code(401);
        echo json_encode(['error' => 'Token not provided']);
        exit;
    }

    $payload = $jwt->decode($token);
    if (!$payload) {
        http_response_code(401);
        echo json_encode(['error' => 'Invalid or expired token']);
        exit;
    }

    return $payload;
}

// Example protected route
$payload = authenticate();
echo "Welcome, " . htmlspecialchars($payload['sub']) . "!";

Step 4: Refresh Token

Implement a token refresh mechanism to extend sessions:

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_COOKIE['jwt'])) {
    $payload = $jwt->decode($_COOKIE['jwt']);

    if ($payload && isset($payload['sub'])) {
        // Check if token is about to expire (e.g., within last 5 minutes)
        if ($payload['exp'] - time() < 300) {
            $newToken = $jwt->encode([
                'sub' => $payload['sub'],
                'role' => $payload['role']
            ]);

            setcookie('jwt', $newToken, time() + 3600, '/', '', false, true);
            echo json_encode(['token' => $newToken]);
            exit;
        }
    }

    http_response_code(400);
    echo json_encode(['error' => 'Token cannot be refreshed']);
    exit;
}

Security Considerations

Key security: ensure your JWT secret key is complex and stored securely.

HTTPS: always transmit JWTs over HTTPS to prevent man‑in‑the‑middle attacks.

Token expiration: set reasonable expiry times to reduce risk of theft.

Sensitive data: do not store sensitive information in JWTs, as they are only base64‑encoded, not encrypted.

Logout handling: implement token blacklisting or use short‑lived tokens for logout.

Conclusion

By following this guide, you have learned how to implement a full JWT authentication flow in native PHP without any framework, including token generation, verification, route protection, and token refresh. Understanding these low‑level details helps you grasp modern web authentication mechanisms and provides a solid foundation for using frameworks.

Remember, security is critical for authentication systems; always follow best practices and tailor the implementation to your specific needs.

BackendSecurityAuthenticationphpJWT
php中文网 Courses
Written by

php中文网 Courses

php中文网's platform for the latest courses and technical articles, helping PHP learners advance quickly.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.