
JWT CON PHP
JWT (JSON Web Token) es un estándar abierto (RFC 7519) para crear tokens de acceso que permiten la autenticación y autorización seguras entre dos partes, como un cliente y un servidor. Es ampliamente utilizado en APIs para verificar la identidad de un usuario o sistema sin necesidad de almacenar sesiones en el servidor. En este post pongo un ejemplo de jwt con php
Estructura de un JWT
Un JWT es una cadena compacta formada por tres partes separadas por puntos (.):- Header: Contiene metadatos sobre el token, como el algoritmo de firma (ej. HMAC SHA256). Se codifica en Base64. Ejemplo: {"alg": "HS256", "typ": "JWT"}.
- Payload: Incluye los datos (claims) del usuario, como su ID, rol o tiempo de expiración. También se codifica en Base64. Ejemplo: {"sub": "123456", "name": "admin", "exp": 1625097600}.
- Signature: Una firma digital creada con el header, payload y una clave secreta para verificar la integridad del token. También se codifica en Base64.
¿Cómo funciona?
- Autenticación: El usuario envía credenciales (como usuario y contraseña) al servidor.
- Generación del token: Si las credenciales son válidas, el servidor genera un JWT con la información del usuario y lo firma con una clave secreta.
- Uso del token: El cliente envía el JWT en cada solicitud (normalmente en el encabezado Authorization: Bearer
). - Verificación: El servidor valida la firma y el contenido del token (por ejemplo, si no ha expirado) para otorgar acceso.
Características principales
- Sin estado: No requiere almacenar sesiones en el servidor, ya que el token contiene toda la información necesaria.
- Seguridad: La firma asegura que el token no ha sido alterado.
- Expiración: Los tokens suelen incluir un tiempo de expiración (exp) para limitar su validez.
- Portabilidad: Es compacto y puede usarse en diferentes plataformas y lenguajes.
Usos comunes
- Autenticación en APIs REST (por ejemplo, en aplicaciones web o móviles).
- Autorización para recursos protegidos.
- Intercambio seguro de información entre sistemas.
Ventajas
- Escalable, ideal para sistemas distribuidos.
- Reduce la carga en el servidor al evitar sesiones.
- Compatible con múltiples lenguajes y frameworks.
Desventajas
- No se puede revocar fácilmente un token una vez emitido (a menos que se implemente una lista de bloqueo).
- Si la clave secreta se compromete, los tokens pueden ser falsificados.
- El payload no es encriptado, solo codificado en Base64, por lo que no debe incluir datos sensibles a menos que se encripte.
// Clave secreta para firmar el JWT (mantenla segura en un entorno real)
$secretKey = 'tu_clave_secreta_123';
// Configuración de cabeceras para permitir CORS (opcional, para APIs)
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
// Función para codificar en base64url (según estándar JWT)
function base64UrlEncode($data) {
$base64 = base64_encode($data);
return str_replace(['+', '/', '='], ['-', '_', ''], $base64);
}
// Función para decodificar base64url
function base64UrlDecode($data) {
$base64 = str_replace(['-', '_'], ['+', '/'], $data);
return base64_decode($base64);
}
// Función para generar un token JWT
function generateJWT($userId, $secretKey) {
// Encabezado (header)
$header = [
'alg' => 'HS256',
'typ' => 'JWT'
];
$headerEncoded = base64UrlEncode(json_encode($header));
// Payload
$issuedAt = time();
$expirationTime = $issuedAt + 3600; // Token válido por 1 hora
$payload = [
'iat' => $issuedAt, // Tiempo de emisión
'exp' => $expirationTime, // Tiempo de expiración
'sub' => $userId, // ID del usuario
'iss' => 'mi_api', // Emisor
'aud' => 'mi_app' // Audiencia
];
$payloadEncoded = base64UrlEncode(json_encode($payload));
// Firma (HMAC-SHA256)
$signature = hash_hmac('sha256', "$headerEncoded.$payloadEncoded", $secretKey, true);
$signatureEncoded = base64UrlEncode($signature);
// Token JWT completo
return "$headerEncoded.$payloadEncoded.$signatureEncoded";
}
// Función para verificar un token JWT
function verifyJWT($token, $secretKey) {
// Dividir el token en sus partes
$parts = explode('.', $token);
if (count($parts) !== 3) {
return ['error' => 'Formato de token inválido'];
}
[$headerEncoded, $payloadEncoded, $signatureEncoded] = $parts;
// Decodificar las partes
$header = json_decode(base64UrlDecode($headerEncoded), true);
$payload = json_decode(base64UrlDecode($payloadEncoded), true);
$signature = base64UrlDecode($signatureEncoded);
// Verificar que el algoritmo sea HS256
if (!isset($header['alg']) || $header['alg'] !== 'HS256') {
return ['error' => 'Algoritmo no soportado'];
}
// Verificar la firma
$expectedSignature = hash_hmac('sha256', "$headerEncoded.$payloadEncoded", $secretKey, true);
if (!hash_equals($signature, $expectedSignature)) {
return ['error' => 'Firma inválida'];
}
// Verificar si el token ha expirado
if (isset($payload['exp']) && time() > $payload['exp']) {
return ['error' => 'Token expirado'];
}
return $payload; // Retorna el payload si todo es válido
}
// Simulación de un endpoint de login
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_GET['action']) && $_GET['action'] === 'login') {
// Leer datos de entrada (simulando un login)
$input = json_decode(file_get_contents('php://input'), true);
$username = $input['username'] ?? '';
$password = $input['password'] ?? '';
// Simulación de validación de usuario (en un caso real, consulta una base de datos)
if ($username === 'admin' && $password === '123456') {
$userId = 1; // ID del usuario
$token = generateJWT($userId, $secretKey);
echo json_encode(['token' => $token]);
} else {
http_response_code(401);
echo json_encode(['error' => 'Credenciales inválidas']);
}
exit;
}
// Simulación de un endpoint protegido
if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['action']) && $_GET['action'] === 'protected') {
// Obtener el token del encabezado Authorization
$headers = apache_request_headers();
$authHeader = $headers['Authorization'] ?? '';
if (preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
$token = $matches[1];
$decoded = verifyJWT($token, $secretKey);
if (isset($decoded['error'])) {
http_response_code(401);
echo json_encode($decoded);
} else {
echo json_encode([
'message' => 'Acceso autorizado',
'userId' => $decoded['sub'],
'data' => 'Contenido protegido'
]);
}
} else {
http_response_code(401);
echo json_encode(['error' => 'Token no proporcionado']);
}
exit;
}
// Respuesta por defecto para otras rutas
echo json_encode(['message' => 'Bienvenido a la API JWT']);
- Llamada para obtener el token:
// URL del endpoint $url = 'url_endpoint?action=login'; // Datos a enviar en el cuerpo de la solicitud $data = [ 'username' => 'admin', 'password' => '123456' ]; // Convertir los datos a formato JSON $jsonData = json_encode($data); // Inicializar cURL $ch = curl_init($url); // Configurar las opciones de cURL curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Retornar la respuesta como string curl_setopt($ch, CURLOPT_POST, true); // Método POST curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Content-Length: ' . strlen($jsonData) ]); curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData); // Datos JSON // Ejecutar la solicitud $response = curl_exec($ch); // Verificar si hubo errores if (curl_errno($ch)) { echo 'Error en cURL: ' . curl_error($ch); } else { // Mostrar el token, copiar para el siguiiente ejemplo echo $response; } // Cerrar la sesión cURL curl_close($ch);
- Llamada xon el token correcto:
// URL del endpoint $url = 'url_endpoint?action=protected'; // Token de autorización $token = 'token_devuelto en el ejemplo anterior'; // Reemplaza con tu token real // Inicializar cURL $ch = curl_init($url); // Configurar las opciones de cURL curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Retornar la respuesta como string curl_setopt($ch, CURLOPT_POST, true); // Método POST curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . $token ]); // Ejecutar la solicitud $response = curl_exec($ch); // Verificar si hubo errores if (curl_errno($ch)) { echo 'Error en cURL: ' . curl_error($ch); } else { // Mostrar la respuesta echo $response; //Se muestra Bienvenido a la API JWT } // Cerrar la sesión cURL curl_close($ch);