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.
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.
php中文网 Courses
php中文网's platform for the latest courses and technical articles, helping PHP learners advance quickly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.