Table of Contents generated with DocToc
- Investigacion: Firebase Admin SDK - Verificacion de Tokens y Gestion de Usuarios
- 1. Resumen Ejecutivo
- 2. Descripcion del Admin SDK
- 3. Instalacion y Configuracion
- 4. Verificacion de ID Tokens
- 5. Custom Claims
- 6. Gestion de Usuarios
- 7. Revocacion de Tokens
- 8. Custom Tokens
- 9. Manejo de Errores
- 10. Integracion con MedTime Backend
- 11. Mejores Practicas
- 12. Referencias
Investigacion: Firebase Admin SDK - Verificacion de Tokens y Gestion de Usuarios¶
Identificador: MTS-INV-016 Version: 1.0.0 Fecha: 2025-12-07 Autor: SpecQueen + SecurityDrone Solicitado en: IT-02 (Seguridad Servidor) Estado: Completado
1. Resumen Ejecutivo¶
Investigacion tecnica del Firebase Admin SDK para implementacion server-side en MedTime. Cubre verificacion de ID tokens, creacion de custom tokens, gestion de usuarios, y mejores practicas de seguridad.
1.1. Conclusion Principal¶
| Aspecto | Evaluacion | Notas |
|---|---|---|
| Verificacion JWT | Nativa | Metodo verify_id_token() |
| Custom Claims | Soportado | Hasta 1000 bytes |
| Gestion Usuarios | Completa | CRUD sin rate limiting |
| Python SDK | Oficial | firebase-admin package |
| Setup | Sencillo | Service account JSON |
2. Descripcion del Admin SDK¶
2.1. Que es Firebase Admin SDK¶
El Firebase Admin SDK proporciona acceso privilegiado a los servicios de Firebase desde entornos de servidor. Permite operaciones administrativas sin las restricciones de los SDKs cliente.
| Caracteristica | Descripcion |
|---|---|
| Privilegios | Acceso completo a proyecto Firebase |
| Rate Limiting | No aplica (a diferencia de cliente) |
| Autenticacion | Via service account |
| Lenguajes | Node.js, Python, Go, Java, C# |
2.2. Capacidades Principales¶
Capacidades Admin SDK:
authentication:
- Verificar ID tokens
- Crear custom tokens
- Gestionar usuarios (CRUD)
- Asignar custom claims
- Revocar tokens
firestore:
- Acceso completo a documentos
- Bypass de security rules
realtime_database:
- Acceso administrativo completo
cloud_messaging:
- Enviar notificaciones push
cloud_storage:
- Generar URLs firmadas
3. Instalacion y Configuracion¶
3.1. Instalacion Python¶
3.2. Service Account¶
El Admin SDK requiere un service account para autenticarse:
Service Account Setup:
1_crear:
- Firebase Console > Project Settings > Service Accounts
- Generar nueva clave privada
- Descargar JSON
2_contenido_json:
project_id: "medtime-prod"
private_key_id: "abc123..."
private_key: "-----BEGIN PRIVATE KEY-----\n..."
client_email: "firebase-adminsdk-xxx@medtime-prod.iam.gserviceaccount.com"
client_id: "123456789"
# ... otros campos
3_seguridad:
- NUNCA commitear a git
- Almacenar en Secret Manager
- Rotar periodicamente
3.3. Inicializacion¶
import firebase_admin
from firebase_admin import credentials, auth
# Opcion 1: Archivo JSON (desarrollo)
cred = credentials.Certificate("path/to/service-account.json")
firebase_admin.initialize_app(cred)
# Opcion 2: Variable de entorno (produccion)
# GOOGLE_APPLICATION_CREDENTIALS=path/to/service-account.json
firebase_admin.initialize_app()
# Opcion 3: Contenido JSON desde Secret Manager
import json
from google.cloud import secretmanager
client = secretmanager.SecretManagerServiceClient()
name = "projects/medtime-prod/secrets/firebase-admin/versions/latest"
response = client.access_secret_version(request={"name": name})
cred_dict = json.loads(response.payload.data.decode("UTF-8"))
cred = credentials.Certificate(cred_dict)
firebase_admin.initialize_app(cred)
4. Verificacion de ID Tokens¶
4.1. Metodo Basico¶
from firebase_admin import auth
def verify_id_token(id_token: str) -> dict:
"""Verifica un ID token de Firebase.
Args:
id_token: JWT enviado por el cliente
Returns:
dict: Claims decodificados del token
Raises:
auth.InvalidIdTokenError: Token malformado
auth.ExpiredIdTokenError: Token expirado
auth.RevokedIdTokenError: Token revocado
"""
decoded_token = auth.verify_id_token(id_token)
return decoded_token
4.2. Verificacion con Check de Revocacion¶
def verify_id_token_with_revocation_check(id_token: str) -> dict:
"""Verifica token incluyendo chequeo de revocacion.
NOTA: Agrega latencia adicional (llamada a Firebase).
Usar solo para operaciones sensibles.
"""
decoded_token = auth.verify_id_token(
id_token,
check_revoked=True # Verifica si fue revocado
)
return decoded_token
4.3. Claims Disponibles en Token Decodificado¶
decoded_token = auth.verify_id_token(id_token)
# Claims estandar
uid = decoded_token['uid'] # ID unico del usuario
email = decoded_token.get('email') # Email (si existe)
email_verified = decoded_token.get('email_verified', False)
phone = decoded_token.get('phone_number')
# Claims de Firebase
auth_time = decoded_token['auth_time'] # Timestamp de autenticacion
iat = decoded_token['iat'] # Issued at
exp = decoded_token['exp'] # Expiration
iss = decoded_token['iss'] # Issuer
aud = decoded_token['aud'] # Audience (project ID)
# Claims de sign-in
firebase_claims = decoded_token.get('firebase', {})
sign_in_provider = firebase_claims.get('sign_in_provider')
# Valores: 'password', 'google.com', 'apple.com', 'phone', etc.
# Custom claims (definidos por MedTime)
tier = decoded_token.get('medtime_tier')
role = decoded_token.get('medtime_role')
4.4. Validaciones que Realiza verify_id_token()¶
Validaciones Automaticas:
header:
alg: "Debe ser RS256"
kid: "Debe corresponder a clave publica de Firebase"
payload:
exp: "Debe ser futuro"
iat: "Debe ser pasado"
aud: "Debe coincidir con project_id"
iss: "Debe ser https://securetoken.google.com/{project_id}"
sub: "Debe ser string no vacio (uid)"
auth_time: "Debe ser pasado"
signature:
verificacion: "Con clave publica de Google"
keys_url: "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com"
5. Custom Claims¶
5.1. Asignar Custom Claims¶
from firebase_admin import auth
def set_user_claims(uid: str, claims: dict) -> None:
"""Asigna custom claims a un usuario.
Args:
uid: ID del usuario
claims: Dict con claims (max 1000 bytes)
Example:
set_user_claims("uid123", {
"medtime_tier": "pro",
"medtime_role": "patient",
"medtime_permissions": ["read_own", "write_own"]
})
"""
auth.set_custom_user_claims(uid, claims)
5.2. Leer Custom Claims¶
def get_user_claims(uid: str) -> dict:
"""Obtiene los custom claims de un usuario."""
user = auth.get_user(uid)
return user.custom_claims or {}
5.3. Claims Especificos de MedTime¶
from dataclasses import dataclass
from enum import Enum
from typing import List
import json
class MedTimeTier(str, Enum):
FREE = "free"
PRO = "pro"
PERFECT = "perfect"
class MedTimeRole(str, Enum):
PATIENT = "patient"
CAREGIVER = "caregiver"
DEPENDENT = "dependent"
ADMIN = "admin"
@dataclass
class MedTimeClaims:
tier: MedTimeTier
role: MedTimeRole
device_id: str
permissions: List[str]
def to_dict(self) -> dict:
return {
"medtime_tier": self.tier.value,
"medtime_role": self.role.value,
"medtime_device_id": self.device_id,
"medtime_permissions": self.permissions
}
def validate_size(self) -> bool:
"""Verifica que claims no excedan 1000 bytes."""
return len(json.dumps(self.to_dict()).encode()) <= 1000
def assign_medtime_claims(
uid: str,
tier: MedTimeTier,
role: MedTimeRole,
device_id: str
) -> None:
"""Asigna claims de MedTime a un usuario."""
permissions = get_permissions_for_tier(tier)
claims = MedTimeClaims(
tier=tier,
role=role,
device_id=device_id,
permissions=permissions
)
if not claims.validate_size():
raise ValueError("Claims exceden 1000 bytes")
auth.set_custom_user_claims(uid, claims.to_dict())
def get_permissions_for_tier(tier: MedTimeTier) -> List[str]:
"""Retorna permisos segun tier."""
base = ["read_own", "write_own"]
if tier == MedTimeTier.FREE:
return base
if tier == MedTimeTier.PRO:
return base + ["read_dependents", "multi_device"]
if tier == MedTimeTier.PERFECT:
return base + [
"read_dependents",
"manage_dependents",
"multi_device",
"advanced_analytics"
]
return base
6. Gestion de Usuarios¶
6.1. Obtener Usuario¶
from firebase_admin import auth
# Por UID
user = auth.get_user(uid)
# Por email
user = auth.get_user_by_email(email)
# Por telefono
user = auth.get_user_by_phone_number(phone)
# Atributos disponibles
print(user.uid)
print(user.email)
print(user.email_verified)
print(user.phone_number)
print(user.display_name)
print(user.photo_url)
print(user.disabled)
print(user.custom_claims)
print(user.provider_data) # Lista de providers vinculados
print(user.tokens_valid_after_time) # Para revocacion
6.2. Listar Usuarios¶
def list_all_users():
"""Lista todos los usuarios (paginado)."""
page = auth.list_users()
while page:
for user in page.users:
print(f"User: {user.uid}, Email: {user.email}")
page = page.get_next_page()
# Con limite
def list_users_batch(max_results: int = 100):
"""Lista usuarios con limite."""
page = auth.list_users(max_results=max_results)
return [user for user in page.users]
6.3. Crear Usuario¶
def create_user(
email: str,
password: str,
display_name: str = None
) -> auth.UserRecord:
"""Crea un nuevo usuario."""
user = auth.create_user(
email=email,
email_verified=False,
password=password,
display_name=display_name,
disabled=False
)
return user
6.4. Actualizar Usuario¶
def update_user(uid: str, **kwargs) -> auth.UserRecord:
"""Actualiza propiedades de usuario.
Kwargs soportados:
- email: str
- email_verified: bool
- phone_number: str
- password: str
- display_name: str
- photo_url: str
- disabled: bool
"""
user = auth.update_user(uid, **kwargs)
return user
# Ejemplo: Deshabilitar usuario
auth.update_user(uid, disabled=True)
# Ejemplo: Verificar email manualmente
auth.update_user(uid, email_verified=True)
6.5. Eliminar Usuario¶
def delete_user(uid: str) -> None:
"""Elimina un usuario permanentemente."""
auth.delete_user(uid)
def delete_users_batch(uids: list) -> auth.DeleteUsersResult:
"""Elimina multiples usuarios."""
result = auth.delete_users(uids)
print(f"Deleted: {result.success_count}")
print(f"Failed: {result.failure_count}")
return result
7. Revocacion de Tokens¶
7.1. Revocar Refresh Tokens¶
from firebase_admin import auth
def revoke_user_tokens(uid: str) -> None:
"""Revoca todos los refresh tokens de un usuario.
Fuerza re-autenticacion en todos los dispositivos.
"""
auth.revoke_refresh_tokens(uid)
# Casos de uso:
# - Cambio de password
# - Compromiso de cuenta detectado
# - Logout de todos los dispositivos
# - Cambio de tier/permisos
7.2. Verificar Tiempo de Revocacion¶
def check_token_revocation(id_token: str) -> bool:
"""Verifica si un token fue emitido antes de revocacion."""
try:
decoded = auth.verify_id_token(id_token, check_revoked=True)
return True
except auth.RevokedIdTokenError:
return False
8. Custom Tokens¶
8.1. Crear Custom Token¶
def create_custom_token(uid: str, additional_claims: dict = None) -> bytes:
"""Crea un custom token para autenticacion.
Uso: Integrar sistemas de autenticacion externos.
Args:
uid: ID unico del usuario (puede ser de sistema externo)
additional_claims: Claims adicionales (max 1000 bytes)
Returns:
bytes: Token JWT que el cliente usa para signInWithCustomToken()
"""
custom_token = auth.create_custom_token(uid, additional_claims)
return custom_token
8.2. Flujo de Custom Token¶
Flujo Custom Token:
1_backend:
accion: "Valida credenciales externas"
resultado: "Obtiene UID del usuario"
2_create:
accion: "auth.create_custom_token(uid, claims)"
resultado: "Token JWT firmado por service account"
3_send:
accion: "Envia custom token al cliente"
transporte: "HTTPS"
4_client:
accion: "signInWithCustomToken(customToken)"
firebase: "Intercambia por ID token real"
5_result:
accion: "Cliente obtiene ID token"
uso: "Autenticar requests al backend"
9. Manejo de Errores¶
9.1. Excepciones Comunes¶
from firebase_admin import auth
from firebase_admin.exceptions import FirebaseError
def handle_auth_errors(id_token: str):
"""Ejemplo de manejo de errores."""
try:
decoded = auth.verify_id_token(id_token)
return decoded
except auth.InvalidIdTokenError as e:
# Token malformado o firma invalida
raise AuthenticationError(
code="INVALID_TOKEN",
message="Token de autenticacion invalido"
)
except auth.ExpiredIdTokenError as e:
# Token expirado (> 1 hora)
raise AuthenticationError(
code="EXPIRED_TOKEN",
message="Token expirado, por favor re-autentique"
)
except auth.RevokedIdTokenError as e:
# Token revocado (check_revoked=True)
raise AuthenticationError(
code="REVOKED_TOKEN",
message="Sesion revocada, por favor inicie sesion nuevamente"
)
except auth.CertificateFetchError as e:
# No se pudieron obtener claves publicas
raise AuthenticationError(
code="SERVICE_ERROR",
message="Error de servicio de autenticacion"
)
except auth.UserDisabledError as e:
# Usuario deshabilitado
raise AuthenticationError(
code="USER_DISABLED",
message="Cuenta deshabilitada"
)
except FirebaseError as e:
# Otros errores de Firebase
raise AuthenticationError(
code="FIREBASE_ERROR",
message=str(e)
)
9.2. Codigos de Error Comunes¶
| Error | Codigo | Causa | Accion |
|---|---|---|---|
| InvalidIdTokenError | INVALID_ARGUMENT | Token malformado | Solicitar nuevo login |
| ExpiredIdTokenError | EXPIRED | Token > 1 hora | Refresh automatico |
| RevokedIdTokenError | REVOKED | Token revocado | Forzar re-login |
| UserNotFoundError | NOT_FOUND | UID no existe | Crear cuenta o error |
| EmailAlreadyExistsError | ALREADY_EXISTS | Email duplicado | Login existente |
10. Integracion con MedTime Backend¶
10.1. Middleware de Autenticacion¶
from functools import wraps
from firebase_admin import auth
from flask import request, g
def require_auth(f):
"""Decorator para requerir autenticacion."""
@wraps(f)
def decorated(*args, **kwargs):
# Obtener token del header
auth_header = request.headers.get('Authorization', '')
if not auth_header.startswith('Bearer '):
return {"error": "Missing authorization"}, 401
id_token = auth_header.split('Bearer ')[1]
try:
# Verificar token
decoded = auth.verify_id_token(id_token)
g.user = decoded
g.uid = decoded['uid']
g.tier = decoded.get('medtime_tier', 'free')
g.role = decoded.get('medtime_role', 'patient')
except Exception as e:
return {"error": str(e)}, 401
return f(*args, **kwargs)
return decorated
@app.route('/api/v1/medications')
@require_auth
def get_medications():
# g.uid, g.tier, g.role disponibles
pass
10.2. Configuracion de RLS Context¶
async def set_rls_context_from_token(
db_conn,
decoded_token: dict
) -> None:
"""Configura contexto RLS basado en token verificado."""
uid = decoded_token['uid']
tier = decoded_token.get('medtime_tier', 'free')
role = decoded_token.get('medtime_role', 'patient')
# Configurar variables de sesion para RLS
await db_conn.execute(
"SET LOCAL app.current_user_id = $1",
uid
)
await db_conn.execute(
"SET LOCAL app.user_tier = $1",
tier
)
await db_conn.execute(
"SET LOCAL app.user_role = $1",
role
)
# Configurar permisos segun tier
max_devices = {"free": 1, "pro": 3, "perfect": 5}.get(tier, 1)
await db_conn.execute(
"SET LOCAL app.max_devices = $1",
max_devices
)
11. Mejores Practicas¶
11.1. Seguridad¶
Mejores Practicas Seguridad:
service_account:
- Nunca commitear a repositorio
- Almacenar en Secret Manager
- Rotar cada 90 dias
- Usar service account dedicado por ambiente
verificacion_tokens:
- Siempre verificar en servidor
- Nunca confiar en claims del cliente
- Usar check_revoked para operaciones sensibles
custom_claims:
- No almacenar datos sensibles
- Mantener claims pequenos (< 1000 bytes)
- Validar claims en backend
11.2. Performance¶
Mejores Practicas Performance:
caching:
- Cachear claves publicas (respeta max-age)
- verify_id_token ya cachea internamente
verificacion:
- check_revoked agrega latencia
- Usar solo cuando necesario
inicializacion:
- Inicializar Admin SDK una vez al startup
- Reusar instancia globalmente
11.3. Ejemplo Completo¶
# config/firebase.py
import firebase_admin
from firebase_admin import credentials
import os
def init_firebase():
"""Inicializa Firebase Admin SDK."""
if firebase_admin._apps:
return # Ya inicializado
if os.getenv('FIREBASE_CREDENTIALS'):
# Produccion: desde Secret Manager
import json
cred_dict = json.loads(os.getenv('FIREBASE_CREDENTIALS'))
cred = credentials.Certificate(cred_dict)
else:
# Desarrollo: desde archivo
cred = credentials.Certificate('service-account.json')
firebase_admin.initialize_app(cred)
# Llamar al iniciar la aplicacion
init_firebase()
12. Referencias¶
12.1. Documentacion Oficial¶
| Recurso | URL |
|---|---|
| Admin SDK Setup | https://firebase.google.com/docs/admin/setup |
| Verify ID Tokens | https://firebase.google.com/docs/auth/admin/verify-id-tokens |
| Custom Claims | https://firebase.google.com/docs/auth/admin/custom-claims |
| Manage Users | https://firebase.google.com/docs/auth/admin/manage-users |
| Python SDK Reference | https://firebase.google.com/docs/reference/admin/python |
12.2. Documentos Internos Relacionados¶
| ID | Documento | Relacion |
|---|---|---|
| INV-015 | Firebase Authentication | Contexto general |
| TECH-SEC-SRV-001 | 05-seguridad-servidor.md | Implementacion |
| MTS-AUTH-001 | Autenticacion | Requisitos funcionales |
Documento generado por SpecQueen + SecurityDrone Fuentes: Firebase Documentation, Google Cloud Identity Platform