🔐 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: