<?php
/**
* JWT desde cero en PHP sin librerías externas
* Algoritmo HS256 (HMAC-SHA256)
* Compatible PHP 8.1+
*/
// ?????????????????????????????????????????????????????????????
// Funciones auxiliares: base64url
// JWT usa base64url (sin padding, - en lugar de +, _ en lugar de /)
// ?????????????????????????????????????????????????????????????
function base64url_encode(string $data): string
{
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
function base64url_decode(string $data): string|false
{
// Restaurar padding y caracteres estándar antes de decodificar
$padded = strtr($data, '-_', '+/');
$padded .= str_repeat('=', (4 - strlen($padded) % 4) % 4);
return base64_decode($padded);
}
// ?????????????????????????????????????????????????????????????
// Crear JWT
// Un JWT tiene 3 partes separadas por '.': header.payload.signature
// ?????????????????????????????????????????????????????????????
function jwt_encode(array $payload, string $secret): string
{
// Header: tipo de token y algoritmo
$header = ['typ' => 'JWT', 'alg' => 'HS256'];
$headerB64 = base64url_encode(json_encode($header));
$payloadB64 = base64url_encode(json_encode($payload));
// La firma se calcula sobre header.payload
$unsigned = $headerB64 . '.' . $payloadB64;
$signature = hash_hmac('sha256', $unsigned, $secret, true); // raw binary
$sigB64 = base64url_encode($signature);
return $unsigned . '.' . $sigB64;
}
// ?????????????????????????????????????????????????????????????
// Verificar y decodificar JWT
// ?????????????????????????????????????????????????????????????
function jwt_decode(string $token, string $secret): array
{
$parts = explode('.', $token);
if (count($parts) !== 3) {
throw new InvalidArgumentException('Formato de token inválido');
}
[$headerB64, $payloadB64, $sigB64] = $parts;
// 1. Recalcular la firma y comparar (timing-safe)
$unsigned = $headerB64 . '.' . $payloadB64;
$expectedSig = base64url_encode(hash_hmac('sha256', $unsigned, $secret, true));
if (!hash_equals($expectedSig, $sigB64)) {
throw new RuntimeException('Firma inválida token manipulado o secret incorrecto');
}
// 2. Decodificar header y payload
$header = json_decode(base64url_decode($headerB64), true);
$payload = json_decode(base64url_decode($payloadB64), true);
// 3. Validar tipo y algoritmo
if (($header['typ'] ?? '') !== 'JWT' || ($header['alg'] ?? '') !== 'HS256') {
throw new RuntimeException('Tipo o algoritmo de token no soportado');
}
// 4. Validar expiración (claim estándar 'exp')
if (isset($payload['exp']) && $payload['exp'] < time()) {
throw new RuntimeException('Token expirado');
}
return $payload;
}
// ?????????????????????????????????????????????????????????????
// Demo
// ?????????????????????????????????????????????????????????????
$secret = 'mi_secreto_super_seguro_32_chars!';
echo "=== JWT desde cero en PHP ===nn";
// Crear token de sesión con expiración en 1 hora
$payload = [
'iss' => 'miapp.com', // emisor
'sub' => '42', // ID de usuario
'name' => 'Ada Lovelace',
'role' => 'admin',
'iat' => time(), // issued at
'exp' => time() + 3600, // expira en 1 hora
];
$token = jwt_encode($payload, $secret);
echo "Token generado:n{$token}nn";
// Verificar token válido
try {
$decoded = jwt_decode($token, $secret);
echo "Token verificado correctamente.n";
echo "Usuario: {$decoded['name']} (ID {$decoded['sub']}, rol: {$decoded['role']})nn";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "nn";
}
// Intentar con secret incorrecto
echo "--- Prueba con secret incorrecto ---n";
try {
jwt_decode($token, 'secret_equivocado');
echo "ERROR: debería haber falladon";
} catch (Exception $e) {
echo "Detectado: " . $e->getMessage() . "nn";
}
// Token manipulado (cambiar un carácter del payload)
echo "--- Prueba con token manipulado ---n";
$parts = explode('.', $token);
$parts[1] = substr($parts[1], 0, -1) . 'X'; // alterar último char del payload
$tampered = implode('.', $parts);
try {
jwt_decode($tampered, $secret);
echo "ERROR: debería haber falladon";
} catch (Exception $e) {
echo "Detectado: " . $e->getMessage() . "nn";
}
// Token expirado
echo "--- Prueba con token expirado ---n";
$expiredPayload = ['sub' => '1', 'exp' => time() - 1]; // ya expiró
$expiredToken = jwt_encode($expiredPayload, $secret);
try {
jwt_decode($expiredToken, $secret);
echo "ERROR: debería haber falladon";
} catch (Exception $e) {
echo "Detectado: " . $e->getMessage() . "n";
}
JWT desde cero en PHP sin librerías
Implementación completa de JSON Web Tokens en PHP sin ninguna librería externa. Funciones jwt_encode y jwt_decode con algoritmo HS256, validación de expiración, emisor y tipo. Incluye demo de creación, validación y detección de tokens manipulados.
Descargar adjuntos
COMPARTE ESTE TUTORIAL
COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP