pip install fastapi uvicorn sqlalchemy sqlite3 passlib bcrypt pyjwt python-dotenv httpx
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from passlib.context import CryptContext
import jwt
import datetime
import os
from dotenv import load_dotenv
load_dotenv()
# Configuración de JWT
SECRET_KEY = os.getenv("SECRET_KEY", "mysecret")
ALGORITHM = "HS256"
# Configuración de la base de datos SQLite
DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Modelo de usuario y cliente
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True)
hashed_password = Column(String)
class Client(Base):
__tablename__ = "clients"
id = Column(Integer, primary_key=True, index=True)
client_id = Column(String, unique=True, index=True)
client_secret = Column(String)
redirect_uri = Column(String)
Base.metadata.create_all(bind=engine)
# Contexto de cifrado para contraseñas
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# OAuth2 esquema para autenticación
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
app = FastAPI()
# Dependencia de sesión de base de datos
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# Funciones para manejar contraseñas
def get_password_hash(password):
return pwd_context.hash(password)
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
# Función para generar tokens JWT
def create_access_token(data: dict, expires_delta: datetime.timedelta = None):
to_encode = data.copy()
expire = datetime.datetime.utcnow() + (expires_delta or datetime.timedelta(minutes=30))
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
# **Registro de usuarios**
@app.post("/register/")
def register(username: str, password: str, db: Session = Depends(get_db)):
hashed_password = get_password_hash(password)
db_user = User(username=username, hashed_password=hashed_password)
db.add(db_user)
db.commit()
return {"message": "Usuario registrado correctamente"}
# **Registro de clientes OAuth2**
@app.post("/register_client/")
def register_client(client_id: str, client_secret: str, redirect_uri: str, db: Session =
Depends(get_db)):
hashed_secret = get_password_hash(client_secret)
db_client = Client(client_id=client_id, client_secret=hashed_secret, redirect_uri=redirect_uri)
db.add(db_client)
db.commit()
return {"message": "Cliente registrado correctamente"}
# **Flujo de autorización OAuth2 (Authorization Code)**
@app.get("/authorize")
def authorize(client_id: str, redirect_uri: str, response_type: str = "code", db: Session =
Depends(get_db)):
client = db.query(Client).filter(Client.client_id == client_id, Client.redirect_uri ==
redirect_uri).first()
if not client:
raise HTTPException(status_code=400, detail="Cliente no válido")
auth_code = create_access_token({"client_id": client_id},
expires_delta=datetime.timedelta(minutes=5))
return {"authorization_code": auth_code}
# **Intercambio de authorization code por un token**
@app.post("/token")
def token(grant_type: str, client_id: str = None, client_secret: str = None, code: str = None, db:
Session = Depends(get_db)):
if grant_type == "authorization_code":
if not code:
raise HTTPException(status_code=400, detail="Código de autorización requerido")
try:
payload = jwt.decode(code, SECRET_KEY, algorithms=[ALGORITHM])
access_token = create_access_token({"client_id": payload["client_id"]})
return {"access_token": access_token, "token_type": "bearer"}
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=400, detail="Código expirado")
except jwt.PyJWTError:
raise HTTPException(status_code=400, detail="Código inválido")
elif grant_type == "client_credentials":
if not client_id or not client_secret:
raise HTTPException(status_code=400, detail="Credenciales requeridas")
client = db.query(Client).filter(Client.client_id == client_id).first()
if not client or not verify_password(client_secret, client.client_secret):
raise HTTPException(status_code=400, detail="Credenciales inválidas")
access_token = create_access_token({"client_id": client_id})
return {"access_token": access_token, "token_type": "bearer"}
raise HTTPException(status_code=400, detail="Tipo de grant inválido")
# **Ruta protegida**
@app.get("/protected/")
def protected_route(token: str = Depends(oauth2_scheme)):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return {"message": f"Acceso concedido a {payload['client_id']}"}
except jwt.PyJWTError:
raise HTTPException(status_code=401, detail="Token inválido")
A) Authorization Code Grant
1. Registrar un cliente OAuth2
sh
CopiarEditar
curl -X POST "http://127.0.0.1:8000/register_client/" \
-H "Content-Type: application/json" \
-d '{"client_id": "my_client", "client_secret": "secret123",
"redirect_uri": "http://localhost/callback"}'
2. Solicitar autorización (Obtener Authorization Code)
sh
CopiarEditar
curl -X GET "http://127.0.0.1:8000/authorize?
client_id=my_client&redirect_uri=http://localhost/callback"
Respuesta esperada:
json
CopiarEditar
{"authorization_code": "eyJhbGciOiJIUzI1NiIsInR5cC..." }
3. Intercambiar Authorization Code por un token
sh
CopiarEditar
curl -X POST "http://127.0.0.1:8000/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code&code=AUTHORIZATION_CODE"
Respuesta esperada:
json
CopiarEditar
{"access_token": "eyJhbGciOiJIUzI1NiIsInR5cC...", "token_type":
"bearer"}
B) Client Credentials Grant
1. Solicitar un token con client credentials
sh
CopiarEditar
curl -X POST "http://127.0.0.1:8000/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d
"grant_type=client_credentials&client_id=my_client&client_secret=se
cret123"
Respuesta esperada:
json
CopiarEditar
{"access_token": "eyJhbGciOiJIUzI1NiIsInR5cC...", "token_type":
"bearer"}
Este ejemplo implementa Authorization Code y Client Credentials en un servidor
OAuth2 en FastAPI. 🚀