Skip to content

Capítulo 6.3: Autenticación Básica

Tiempo de estudio: 1 hora


1. Autenticación API: El pase al Centro de Control de Misión

La autenticación es el proceso de verificar la identidad del usuario. A diferencia de los sitios web con sesiones y cookies, las API sin estado (stateless) suelen utilizar tokens.

El proceso es el siguiente:

  1. El usuario envía su nombre de usuario y contraseña a un endpoint especial (por ejemplo, /login).
  2. El servidor los verifica. Si son correctos, genera un token único y cifrado (una cadena larga) y lo envía de vuelta.
  3. Con cada solicitud posterior a recursos protegidos (por ejemplo, POST /planets), el usuario debe adjuntar este token en el encabezado Authorization.
  4. El servidor verifica la validez del token y, si es correcto, ejecuta la solicitud.

💡 Analogía espacial:

  • Nombre de usuario/contraseña = Su escaneo biométrico para obtener un pase.
  • Token = El pase electrónico (tarjeta de identificación) que recibe al entrar al Centro de Control de Misión.
  • Encabezado Authorization: Bearer <token> = Usted acerca su pase al lector en cada puerta protegida.
  • Endpoints protegidos (POST, PUT, DELETE) = Puertas a la sala de servidores o al panel de control de lanzamiento.

2. Autenticación en Laravel: Sanctum

Laravel ofrece una solución elegante para la autenticación API: Laravel Sanctum. Es ideal para SPA (aplicaciones de una sola página), aplicaciones móviles y API simples basadas en tokens.

Paso 1: Instalación y configuración de Sanctum

Sanctum ya está instalado en una aplicación Laravel estándar, pero verificaremos la configuración.

  1. Publicación de la configuración (si aún no lo ha hecho):
    php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
    
  2. Ejecución de las migraciones (creará la tabla personal_access_tokens):
    php artisan migrate
    
  3. Adición del trait al modelo User: Abra app/Models/User.php y asegúrese de que utiliza el trait HasApiTokens.
    // app/Models/User.php
    use Laravel\Sanctum\HasApiTokens;
    
    class User extends Authenticatable
    {
        use HasApiTokens, HasFactory, Notifiable;
        // ...
    }
    

Paso 2: Creación de un endpoint para emitir tokens Necesitamos una ruta donde el usuario enviará su nombre de usuario/contraseña.

Añada a routes/api.php:

// routes/api.php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\Models\User;
use Illuminate\Validation\ValidationException;

Route::post('/login', function (Request $request) {
    $request->validate([
        'email' => 'required|email',
        'password' => 'required',
    ]);

    $user = User::where('email', $request->email)->first();

    if (! $user || ! Hash::check($request->password, $user->password)) {
        throw ValidationException::withMessages([
            'email' => ['Las credenciales son incorrectas.'],
        ]);
    }

    // Devolvemos el token
    return response()->json([
        'token' => $user->createToken('api-token')->plainTextToken
    ]);
});

Para la prueba, puede crear un usuario a través de un seeder o Tinker.

Paso 3: Protección de rutas Ahora protegeremos nuestras operaciones CRUD. Modificaremos routes/api.php:

// routes/api.php
use App\Http\Controllers\PlanetController;

// Ruta pública para ver planetas
Route::get('/planets', [PlanetController::class, 'index']);
Route::get('/planets/{planet}', [PlanetController::class, 'show']);

// Grupo de rutas protegidas
Route::middleware('auth:sanctum')->group(function () {
    Route::post('/planets', [PlanetController::class, 'store']);
    Route::put('/planets/{planet}', [PlanetController::class, 'update']);
    Route::delete('/planets/{planet}', [PlanetController::class, 'destroy']);

    // Ruta para cerrar sesión (eliminar el token)
    Route::post('/logout', function (Request $request) {
        $request->user()->currentAccessToken()->delete();
        return response()->json(['message' => 'Ha cerrado sesión correctamente'], 200);
    });
});

El middleware auth:sanctum verificará la presencia de un token válido en el encabezado Authorization.


3. Autenticación en FastAPI: OAuth2 y JWT

FastAPI no tiene un sistema de autenticación incorporado, pero sí herramientas potentes para implementarlo. El estándar de facto es OAuth2 con tokens JWT.

Paso 1: Instalación de dependencias

pip install "python-jose[cryptography]" "passlib[bcrypt]" "python-multipart"

  • python-jose: para crear y verificar tokens JWT.
  • passlib: para hashear y verificar contraseñas.
  • python-multipart: para procesar datos de formularios (username y password).

Paso 2: Creación del módulo de seguridad (security.py) Es una buena práctica mover toda la lógica de autenticación a un archivo separado.

Cree el archivo security.py:

# security.py
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta, timezone

# --- Configuración ---
SECRET_KEY = "your-super-secret-key-that-is-long-and-random" # ⚠️ ¡Reemplace con su propia clave!
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

# --- Utilidades ---
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/login")

# --- Funciones ---
def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password):
    return pwd_context.hash(password)

def create_access_token(data: dict):
    to_encode = data.copy()
    expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

# --- Función de dependencia para verificar el token ---
def get_current_user(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="No se pudieron validar las credenciales",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    # Aquí se podría devolver el usuario de la DB, por ahora solo devolvemos el nombre
    return {"username": username}

Paso 3: Integración en main.py Ahora lo conectaremos a nuestra aplicación.

  1. Crearemos el endpoint /login:

    # main.py
    from fastapi.security import OAuth2PasswordRequestForm
    from fastapi import Depends, APIRouter
    from . import security # Importamos nuestro módulo
    
    # ... su código FastAPI ...
    router = APIRouter(prefix="/api/v1")
    
    @router.post("/login")
    def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
        # Aquí debe haber una verificación del usuario en la DB
        # Por ejemplo, tenemos un usuario de prueba
        is_user_valid = (form_data.username == "testuser" and
                         security.verify_password("testpass", security.get_password_hash("testpass")))
    
        if not is_user_valid:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Nombre de usuario o contraseña incorrectos",
            )
        access_token = security.create_access_token(data={"sub": form_data.username})
        return {"access_token": access_token, "token_type": "bearer"}
    
    # ...
    app.include_router(router)
    

  2. Proteger endpoints:

    Usaremos nuestra dependencia get_current_user.

    # main.py o en su router de planetas
    
    @router.post("/planets", status_code=status.HTTP_201_CREATED)
    def create_planet(
        planet: PlanetCreate,
        current_user: dict = Depends(security.get_current_user) # <-- ¡Protección!
    ):
        # Lógica para crear el planeta...
        print(f"El usuario {current_user['username']} crea un planeta.")
        # ...
        return new_planet
    
    # También protegemos PUT y DELETE
    


4. Uso de tokens en el frontend

Nuestro frontend ahora debe primero obtener el token, guardarlo (por ejemplo, en localStorage) y adjuntarlo a cada solicitud protegida.

Ejemplo en JavaScript (fetch):

// 1. Iniciamos sesión
async function login(email, password) {
    const response = await fetch('http://localhost:8001/api/login', { // Dirección de la API de Laravel
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({email, password})
    });
    const data = await response.json();

    if (data.token) {
        localStorage.setItem('api_token', data.token); // Guardamos el token
    }
}

// 2. Realizamos una solicitud protegida
async function createPlanet(planetData) {
    const token = localStorage.getItem('api_token');

    const response = await fetch('http://localhost:8001/api/planets', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${token}` // <--- ¡Adjuntamos el token!
        },
        body: JSON.stringify(planetData)
    });
    // ...
}


Cuestionario de consolidación

margin-top: 20px; padding: 15px; border-radius: 5px; }

1. Las API sin estado más a menudo utilizan para la autenticación:

2. En Laravel, para proteger rutas con tokens se utiliza el middleware:

3. En FastAPI, para obtener datos del formulario de inicio de sesión se utiliza la dependencia:

4. ¿Cómo se transmite el token del cliente al servidor en una solicitud protegida?


🚀 Resumen del capítulo:

Has instalado un "sistema de control de acceso" en tus API. Ahora no cualquiera puede realizar cambios en tu "base de datos galáctica".

  • ✅ Comprendido el principio de autenticación basada en tokens.
  • 🔐 Implementada la emisión de tokens y la protección de rutas en Laravel Sanctum.
  • ⚙️ Configurada la autenticación basada en OAuth2 y JWT en FastAPI.
  • 🛰️ Aprendido cómo el frontend debe guardar y usar el token.

Tus API no solo son funcionales, sino también seguras. Sin embargo, para que otros desarrolladores puedan utilizarlas, necesitan "instrucciones de uso".

📌 Verificación:

  • Intenta hacer una solicitud POST a /api/planets (en Laravel) o /api/v1/planets (en FastAPI) sin un token usando Postman o Insomnia. Deberías recibir un error 401 Unauthorized.
  • Haz una solicitud a /login, obtén un token, añádelo al encabezado Authorization y repite la solicitud POST. Debería ejecutarse con éxito.