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:
- El usuario envía su nombre de usuario y contraseña a un endpoint especial (por ejemplo,
/login
). - El servidor los verifica. Si son correctos, genera un token único y cifrado (una cadena larga) y lo envía de vuelta.
- Con cada solicitud posterior a recursos protegidos (por ejemplo,
POST /planets
), el usuario debe adjuntar este token en el encabezadoAuthorization
. - 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.
- Publicación de la configuración (si aún no lo ha hecho):
- Ejecución de las migraciones (creará la tabla
personal_access_tokens
): - Adición del trait al modelo
User
: Abraapp/Models/User.php
y asegúrese de que utiliza el traitHasApiTokens
.
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
python-jose
: para crear y verificar tokens JWT.passlib
: para hashear y verificar contraseñas.python-multipart
: para procesar datos de formularios (username
ypassword
).
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.
-
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)
-
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; }
🚀 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 error401 Unauthorized
.- Haz una solicitud a
/login
, obtén un token, añádelo al encabezadoAuthorization
y repite la solicitudPOST
. Debería ejecutarse con éxito.