🔐 Sistema de login completo con JWT + PHP + MySQL (paso a paso)
Introducción
Crear una API está bien.
Usar JWT también.
Pero el verdadero salto profesional ocurre cuando puedes construir esto:
👉 registro de usuarios
👉 login
👉 generación de token
👉 protección de rutas
👉 conexión con base de datos
Todo funcionando como un sistema real.
En este tutorial vas a crear un backend completo de autenticación usando:
- PHP (sin frameworks, para entender todo)
- MySQL
- PDO
- JWT
Al terminar, tendrás:
- un sistema listo para APIs reales
- base para SaaS o proyectos
- código reutilizable
🧠 Qué vamos a construir
Endpoints:
POST /register POST /login GET /profile (protegido)
🗄️ Base de datos
Tabla usuarios
CREATE TABLE usuarios ( id INT AUTO_INCREMENT PRIMARY KEY, nombre VARCHAR(100), email VARCHAR(100) UNIQUE, password VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
📁 Estructura del proyecto
api/ │ ├── config/ │ └── db.php │ ├── controllers/ │ ├── auth.php │ └── user.php │ ├── middleware/ │ └── auth.php │ ├── index.php
🔌 Conexión PDO
config/db.php
<?php
$pdo = new PDO("mysql:host=localhost;dbname=mi_api", "root", "", [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
📦 Instalar JWT
composer require firebase/php-jwt
🔐 REGISTRO de usuario
controllers/auth.php
<?php
if ($uri[1] === 'register' && $method === 'POST') {
$data = json_decode(file_get_contents("php://input"), true);
if (!isset($data['email'], $data['password'])) {
http_response_code(400);
echo json_encode(["error" => "Datos incompletos"]);
exit;
}
$hash = password_hash($data['password'], PASSWORD_DEFAULT);
$stmt = $pdo->prepare("INSERT INTO usuarios (nombre, email, password) VALUES (?, ?, ?)");
$stmt->execute([
$data['nombre'],
$data['email'],
$hash
]);
echo json_encode(["message" => "Usuario registrado"]);
}
🔑 LOGIN (generar JWT)
<?php
use Firebase\JWT\JWT;
$key = "clave_secreta_super_segura";
if ($uri[1] === 'login' && $method === 'POST') {
$data = json_decode(file_get_contents("php://input"), true);
$stmt = $pdo->prepare("SELECT * FROM usuarios WHERE email = ?");
$stmt->execute([$data['email']]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$user || !password_verify($data['password'], $user['password'])) {
http_response_code(401);
echo json_encode(["error" => "Credenciales inválidas"]);
exit;
}
$payload = [
"user_id" => $user['id'],
"email" => $user['email'],
"exp" => time() + 3600
];
$token = JWT::encode($payload, $key, 'HS256');
echo json_encode([
"token" => $token
]);
}
🛡️ Middleware de autenticación
middleware/auth.php
<?php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$key = "clave_secreta_super_segura";
$headers = getallheaders();
if (!isset($headers['Authorization'])) {
http_response_code(401);
exit(json_encode(["error" => "Token requerido"]));
}
$token = str_replace("Bearer ", "", $headers['Authorization']);
try {
$decoded = JWT::decode($token, new Key($key, 'HS256'));
} catch (Exception $e) {
http_response_code(401);
exit(json_encode(["error" => "Token inválido"]));
}
👤 Ruta protegida (perfil)
controllers/user.php
<?php
require __DIR__ . '/../middleware/auth.php';
if ($uri[1] === 'profile' && $method === 'GET') {
$stmt = $pdo->prepare("SELECT id, nombre, email FROM usuarios WHERE id = ?");
$stmt->execute([$decoded->user_id]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
echo json_encode($user);
}
🧭 Router principal
index.php
<?php
header('Content-Type: application/json');
require 'config/db.php';
$method = $_SERVER['REQUEST_METHOD'];
$uri = explode('/', trim($_SERVER['REQUEST_URI'], '/'));
if ($uri[0] === 'register' || $uri[0] === 'login') {
require 'controllers/auth.php';
}
if ($uri[0] === 'profile') {
require 'controllers/user.php';
}
🧪 Cómo probar todo
Registro
curl -X POST http://localhost/api/register \
-d '{"nombre":"Irak","email":"irak@mail.com","password":"123456"}'
Login
curl -X POST http://localhost/api/login \
-d '{"email":"irak@mail.com","password":"123456"}'
Acceso protegido
curl http://localhost/api/profile \ -H "Authorization: Bearer TU_TOKEN"
⚠️ Errores comunes
❌ no usar password_hash
❌ no validar inputs
❌ no usar prepared statements
❌ clave JWT débil
❌ no manejar errores
🔐 Buenas prácticas
- usar HTTPS
- tokens cortos
- refresh tokens
- validar todo
- logs
🧠 Qué acabas de construir
👉 Sistema real de autenticación
👉 API segura
👉 Base para SaaS
👉 Backend profesional
🔗 Siguiente lectura recomendada:
Tags de este post
Preguntas frecuentes
Sí, en APIs modernas.
Frontend (localStorage o cookie segura).
Sí, es el estándar.