Protege tu API de Rocket con control de acceso basado en roles (RBAC) y validación de JWT
Esta guía te ayudará a implementar autorización para asegurar tus APIs de Rocket usando el control de acceso basado en roles (RBAC) y JSON Web Tokens (JWTs) emitidos por Logto.
Antes de comenzar
Tus aplicaciones cliente necesitan obtener tokens de acceso (Access tokens) de Logto. Si aún no has configurado la integración del cliente, consulta nuestras Guías rápidas para React, Vue, Angular u otros frameworks de cliente, o revisa nuestra Guía de máquina a máquina para el acceso de servidor a servidor.
Esta guía se centra en la validación del lado del servidor de esos tokens en tu aplicación Rocket.

Lo que aprenderás
- Validación de JWT: Aprende a validar tokens de acceso (Access tokens) y extraer información de autenticación (Authentication)
- Implementación de middleware: Crea middleware reutilizable para la protección de API
- Modelos de permisos: Comprende e implementa diferentes patrones de autorización (Authorization):
- Recursos de API globales para endpoints a nivel de aplicación
- Permisos de organización para el control de funciones específicas de inquilinos
- Recursos de API a nivel de organización para el acceso a datos multi-inquilino
- Integración de RBAC: Aplica permisos y alcances (Scopes) basados en roles (Roles) en tus endpoints de API
Requisitos previos
- Última versión estable de Rust instalada
- Conocimientos básicos de Rocket y desarrollo de API web
- Una aplicación Logto configurada (consulta las Guías rápidas si es necesario)
Resumen de modelos de permisos
Antes de implementar la protección, elige el modelo de permisos que se adapte a la arquitectura de tu aplicación. Esto se alinea con los tres principales escenarios de autorización de Logto:
- Recursos de API globales
- Permisos de organización (no API)
- Recursos de API a nivel de organización

- Caso de uso: Proteger recursos de API compartidos en toda tu aplicación (no específicos de una organización)
- Tipo de token: Token de acceso (Access token) con audiencia global
- Ejemplos: APIs públicas, servicios principales del producto, endpoints de administración
- Ideal para: Productos SaaS con APIs utilizadas por todos los clientes, microservicios sin aislamiento de inquilinos
- Más información: Proteger recursos de API globales

- Caso de uso: Controlar acciones específicas de la organización, características de la interfaz de usuario o lógica de negocio (no APIs)
- Tipo de token: Token de organización (Organization token) con audiencia específica de la organización
- Ejemplos: Control de características, permisos de panel de control, controles de invitación de miembros
- Ideal para: SaaS multi-inquilino con características y flujos de trabajo específicos de la organización
- Más información: Proteger permisos de organización (no API)

- Caso de uso: Proteger recursos de API accesibles dentro de un contexto de organización específico
- Tipo de token: Token de organización (Organization token) con audiencia de recurso de API + contexto de organización
- Ejemplos: APIs multi-inquilino, endpoints de datos con alcance organizacional, microservicios específicos de inquilino
- Ideal para: SaaS multi-inquilino donde los datos de la API tienen alcance organizacional
- Más información: Proteger recursos de API a nivel de organización
💡 Elige tu modelo antes de continuar: la implementación hará referencia a tu enfoque elegido a lo largo de esta guía.
Pasos rápidos de preparación
Configura recursos y permisos de Logto
- Recursos de API globales
- Permisos de organización (no API)
- Recursos de API a nivel de organización
- Crea un recurso de API: Ve a Consola → Recursos de API y registra tu API (por ejemplo,
https://api.yourapp.com
) - Define permisos: Añade alcances como
read:products
,write:orders
– consulta Definir recursos de API con permisos - Crea roles globales: Ve a Consola → Roles y crea roles que incluyan los permisos de tu API – consulta Configurar roles globales
- Asigna roles: Asigna roles a usuarios o aplicaciones M2M que necesiten acceso a la API
- Define permisos de organización: Crea permisos de organización que no sean de API como
invite:member
,manage:billing
en la plantilla de organización - Configura roles de organización: Configura la plantilla de organización con roles específicos de la organización y asígnales permisos
- Asigna roles de organización: Asigna usuarios a roles de organización dentro de cada contexto de organización
- Crea un recurso de API: Registra tu recurso de API como antes, pero se usará en el contexto de organización
- Define permisos: Añade alcances como
read:data
,write:settings
que estén limitados al contexto de organización - Configura la plantilla de organización: Configura roles de organización que incluyan los permisos de tu recurso de API
- Asigna roles de organización: Asigna usuarios o aplicaciones M2M a roles de organización que incluyan permisos de API
- Configuración multi-tenant: Asegúrate de que tu API pueda manejar datos y validaciones con alcance de organización
Comienza con nuestra Guía de control de acceso basado en roles (RBAC) para instrucciones paso a paso.
Actualiza tu aplicación cliente
Solicita los alcances apropiados en tu cliente:
- Autenticación de usuario: Actualiza tu app → para solicitar los alcances de tu API y/o el contexto de organización
- Máquina a máquina: Configura alcances M2M → para acceso de servidor a servidor
El proceso normalmente implica actualizar la configuración de tu cliente para incluir uno o más de los siguientes:
- Parámetro
scope
en los flujos OAuth - Parámetro
resource
para acceso a recursos de API organization_id
para el contexto de organización
Asegúrate de que el usuario o la aplicación M2M que estás probando tenga asignados los roles o roles de organización adecuados que incluyan los permisos necesarios para tu API.
Inicializa tu proyecto de API
Para inicializar un nuevo proyecto Rocket, crea un directorio y configura la estructura básica:
cargo new your-api-name
cd your-api-name
Agrega las dependencias de Rocket a tu Cargo.toml
:
[dependencies]
rocket = { version = "0.5", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Crea una aplicación básica de Rocket:
use rocket::{get, launch, routes, serde::json::Json};
use serde_json::{json, Value};
#[get("/")]
fn hello_handler() -> Json<Value> {
Json(json!({ "message": "Hello from Rocket" }))
}
#[launch]
fn rocket() -> _ {
rocket::build()
.mount("/", routes![hello_handler])
}
Inicia el servidor de desarrollo:
cargo run
Consulta la documentación de Rocket para más detalles sobre cómo configurar rutas, guards de solicitud y otras características.
Inicializa constantes y utilidades
Define las constantes y utilidades necesarias en tu código para manejar la extracción y validación de tokens. Una solicitud válida debe incluir un encabezado Authorization
en la forma Bearer <token de acceso (access token)>
.
use serde::{Deserialize, Serialize};
use std::fmt;
pub const JWKS_URI: &str = "https://your-tenant.logto.app/oidc/jwks";
pub const ISSUER: &str = "https://your-tenant.logto.app/oidc";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthInfo {
pub sub: String,
pub client_id: Option<String>,
pub organization_id: Option<String>,
pub scopes: Vec<String>,
pub audience: Vec<String>,
}
impl AuthInfo {
pub fn new(
sub: String,
client_id: Option<String>,
organization_id: Option<String>,
scopes: Vec<String>,
audience: Vec<String>,
) -> Self {
Self {
sub,
client_id,
organization_id,
scopes,
audience,
}
}
}
#[derive(Debug)]
pub struct AuthorizationError {
pub message: String,
pub status_code: u16,
}
impl AuthorizationError {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
status_code: 403,
}
}
pub fn with_status(message: impl Into<String>, status_code: u16) -> Self {
Self {
message: message.into(),
status_code,
}
}
}
impl fmt::Display for AuthorizationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for AuthorizationError {}
pub fn extract_bearer_token(authorization: Option<&str>) -> Result<&str, AuthorizationError> {
let auth_header = authorization.ok_or_else(|| {
AuthorizationError::with_status("Falta el encabezado de autorización (Authorization header is missing)", 401)
})?;
if !auth_header.starts_with("Bearer ") {
return Err(AuthorizationError::with_status(
"El encabezado de autorización debe comenzar con \"Bearer \" (Authorization header must start with \"Bearer \")",
401,
));
}
Ok(&auth_header[7..]) // Elimina el prefijo 'Bearer ' (Remove 'Bearer ' prefix)
}
Recupera información sobre tu tenant de Logto
Necesitarás los siguientes valores para validar los tokens emitidos por Logto:
- URI de JSON Web Key Set (JWKS): La URL a las claves públicas de Logto, utilizada para verificar las firmas de JWT.
- Emisor (Issuer): El valor esperado del emisor (la URL OIDC de Logto).
Primero, encuentra el endpoint de tu tenant de Logto. Puedes encontrarlo en varios lugares:
- En la Consola de Logto, en Configuración → Dominios.
- En cualquier configuración de aplicación que hayas configurado en Logto, Configuración → Endpoints y Credenciales.
Obtener desde el endpoint de descubrimiento de OpenID Connect
Estos valores pueden obtenerse desde el endpoint de descubrimiento de OpenID Connect de Logto:
https://<your-logto-endpoint>/oidc/.well-known/openid-configuration
Aquí tienes un ejemplo de respuesta (otros campos omitidos por brevedad):
{
"jwks_uri": "https://your-tenant.logto.app/oidc/jwks",
"issuer": "https://your-tenant.logto.app/oidc"
}
Codificar directamente en tu código (no recomendado)
Dado que Logto no permite personalizar la URI de JWKS ni el emisor, puedes codificar estos valores directamente en tu código. Sin embargo, esto no se recomienda para aplicaciones en producción, ya que puede aumentar la carga de mantenimiento si alguna configuración cambia en el futuro.
- URI de JWKS:
https://<your-logto-endpoint>/oidc/jwks
- Emisor:
https://<your-logto-endpoint>/oidc
Valida el token y los permisos
Después de extraer el token y obtener la configuración OIDC, valida lo siguiente:
- Firma: El JWT debe ser válido y estar firmado por Logto (a través de JWKS).
- Emisor (Issuer): Debe coincidir con el emisor de tu tenant de Logto.
- Audiencia (Audience): Debe coincidir con el indicador de recurso de la API registrado en Logto, o el contexto de la organización si corresponde.
- Expiración: El token no debe estar expirado.
- Permisos (Alcances / scopes): El token debe incluir los alcances requeridos para tu API / acción. Los alcances son cadenas separadas por espacios en el reclamo
scope
. - Contexto de organización: Si proteges recursos de API a nivel de organización, valida el reclamo
organization_id
.
Consulta JSON Web Token para aprender más sobre la estructura y los reclamos de JWT.
Qué verificar para cada modelo de permisos
- Recursos de API globales
- Permisos de organización (no API)
- Recursos de API a nivel de organización
- Reclamo de audiencia (
aud
): Indicador de recurso de API - Reclamo de organización (
organization_id
): No presente - Alcances (permisos) a verificar (
scope
): Permisos de recurso de API
- Reclamo de audiencia (
aud
):urn:logto:organization:<id>
(el contexto de organización está en el reclamoaud
) - Reclamo de organización (
organization_id
): No presente - Alcances (permisos) a verificar (
scope
): Permisos de organización
- Reclamo de audiencia (
aud
): Indicador de recurso de API - Reclamo de organización (
organization_id
): ID de la organización (debe coincidir con la solicitud) - Alcances (permisos) a verificar (
scope
): Permisos de recurso de API
Para los permisos de organización que no son de API, el contexto de la organización está
representado por el reclamo aud
(por ejemplo, urn:logto:organization:abc123
). El reclamo
organization_id
solo está presente para tokens de recursos de API a nivel de organización.
Valida siempre tanto los permisos (alcances) como el contexto (audiencia, organización) para APIs multi-tenant seguras.
Añade la lógica de validación
Usamos jsonwebtoken para validar JWTs. Añade las dependencias necesarias a tu Cargo.toml
:
[dependencies]
jsonwebtoken = "9.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1.0", features = ["full"] }
Primero, añade estas utilidades compartidas para manejar la validación de JWT:
use crate::{AuthInfo, AuthorizationError, ISSUER, JWKS_URI};
use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation};
use serde_json::Value;
use std::collections::HashMap;
pub struct JwtValidator {
jwks: HashMap<String, DecodingKey>,
}
impl JwtValidator {
pub async fn new() -> Result<Self, AuthorizationError> {
let jwks = Self::fetch_jwks().await?;
Ok(Self { jwks })
}
async fn fetch_jwks() -> Result<HashMap<String, DecodingKey>, AuthorizationError> {
let response = reqwest::get(JWKS_URI).await.map_err(|e| {
AuthorizationError::with_status(format!("Failed to fetch JWKS: {}", e), 401)
})?;
let jwks: Value = response.json().await.map_err(|e| {
AuthorizationError::with_status(format!("Failed to parse JWKS: {}", e), 401)
})?;
let mut keys = HashMap::new();
if let Some(keys_array) = jwks["keys"].as_array() {
for key in keys_array {
if let (Some(kid), Some(kty), Some(n), Some(e)) = (
key["kid"].as_str(),
key["kty"].as_str(),
key["n"].as_str(),
key["e"].as_str(),
) {
if kty == "RSA" {
if let Ok(decoding_key) = DecodingKey::from_rsa_components(n, e) {
keys.insert(kid.to_string(), decoding_key);
}
}
}
}
}
if keys.is_empty() {
return Err(AuthorizationError::with_status("No valid keys found in JWKS", 401));
}
Ok(keys)
}
pub fn validate_jwt(&self, token: &str) -> Result<AuthInfo, AuthorizationError> {
let header = decode_header(token).map_err(|e| {
AuthorizationError::with_status(format!("Invalid token header: {}", e), 401)
})?;
let kid = header.kid.ok_or_else(|| {
AuthorizationError::with_status("Token missing kid claim", 401)
})?;
let key = self.jwks.get(&kid).ok_or_else(|| {
AuthorizationError::with_status("Unknown key ID", 401)
})?;
let mut validation = Validation::new(Algorithm::RS256);
validation.set_issuer(&[ISSUER]);
validation.validate_aud = false; // Verificaremos la audiencia manualmente
let token_data = decode::<Value>(token, key, &validation).map_err(|e| {
AuthorizationError::with_status(format!("Invalid token: {}", e), 401)
})?;
let claims = token_data.claims;
self.verify_payload(&claims)?;
Ok(self.create_auth_info(claims))
}
fn verify_payload(&self, claims: &Value) -> Result<(), AuthorizationError> {
// Implementa aquí tu lógica de verificación basada en el modelo de permisos
// Esto se mostrará en la sección de modelos de permisos más abajo
Ok(())
}
fn create_auth_info(&self, claims: Value) -> AuthInfo {
let scopes = claims["scope"]
.as_str()
.map(|s| s.split(' ').map(|s| s.to_string()).collect())
.unwrap_or_default();
let audience = match &claims["aud"] {
Value::Array(arr) => arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect(),
Value::String(s) => vec![s.clone()],
_ => vec![],
};
AuthInfo::new(
claims["sub"].as_str().unwrap_or_default().to_string(),
claims["client_id"].as_str().map(|s| s.to_string()),
claims["organization_id"].as_str().map(|s| s.to_string()),
scopes,
audience,
)
}
}
Luego, implementa el middleware para verificar el token de acceso:
use crate::{AuthInfo, AuthorizationError, extract_bearer_token};
use crate::jwt_validator::JwtValidator;
use rocket::{
http::Status,
outcome::Outcome,
request::{self, FromRequest, Request},
State,
};
#[rocket::async_trait]
impl<'r> FromRequest<'r> for AuthInfo {
type Error = AuthorizationError;
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
let validator = match req.guard::<&State<JwtValidator>>().await {
Outcome::Success(validator) => validator,
Outcome::Failure((status, _)) => {
return Outcome::Failure((
status,
AuthorizationError::with_status("JWT validator not found", 500),
))
}
Outcome::Forward(()) => {
return Outcome::Forward(())
}
};
let authorization = req.headers().get_one("authorization");
match extract_bearer_token(authorization)
.and_then(|token| validator.validate_jwt(token))
{
Ok(auth_info) => Outcome::Success(auth_info),
Err(e) => {
let status = Status::from_code(e.status_code).unwrap_or(Status::Forbidden);
Outcome::Failure((status, e))
}
}
}
}
De acuerdo con tu modelo de permisos, implementa la lógica de verificación apropiada en JwtValidator
:
- Recursos de API globales
- Permisos de organización (no API)
- Recursos de API a nivel de organización
fn verify_payload(&self, claims: &Value) -> Result<(), AuthorizationError> {
// Verifica que el claim de audiencia coincida con tu indicador de recurso de API
let audiences = match &claims["aud"] {
Value::Array(arr) => arr.iter().filter_map(|v| v.as_str()).collect::<Vec<_>>(),
Value::String(s) => vec![s.as_str()],
_ => vec![],
};
if !audiences.contains(&"https://your-api-resource-indicator") {
return Err(AuthorizationError::new("Invalid audience"));
}
// Verifica los alcances requeridos para recursos de API globales
let required_scopes = vec!["api:read", "api:write"]; // Sustituye por tus alcances requeridos reales
let scopes = claims["scope"]
.as_str()
.map(|s| s.split(' ').collect::<Vec<_>>())
.unwrap_or_default();
for required_scope in &required_scopes {
if !scopes.contains(required_scope) {
return Err(AuthorizationError::new("Insufficient scope"));
}
}
Ok(())
}
fn verify_payload(&self, claims: &Value) -> Result<(), AuthorizationError> {
// Verifica que el claim de audiencia coincida con el formato de organización
let audiences = match &claims["aud"] {
Value::Array(arr) => arr.iter().filter_map(|v| v.as_str()).collect::<Vec<_>>(),
Value::String(s) => vec![s.as_str()],
_ => vec![],
};
let has_org_audience = audiences.iter().any(|aud| aud.starts_with("urn:logto:organization:"));
if !has_org_audience {
return Err(AuthorizationError::new("Invalid audience for organization permissions"));
}
// Verifica que el ID de la organización coincida con el contexto (puede que necesites extraer esto del contexto de la solicitud)
let expected_org_id = "your-organization-id"; // Extrae del contexto de la solicitud
let expected_aud = format!("urn:logto:organization:{}", expected_org_id);
if !audiences.contains(&expected_aud.as_str()) {
return Err(AuthorizationError::new("Organization ID mismatch"));
}
// Verifica los alcances requeridos de la organización
let required_scopes = vec!["invite:users", "manage:settings"]; // Sustituye por tus alcances requeridos reales
let scopes = claims["scope"]
.as_str()
.map(|s| s.split(' ').collect::<Vec<_>>())
.unwrap_or_default();
for required_scope in &required_scopes {
if !scopes.contains(required_scope) {
return Err(AuthorizationError::new("Insufficient organization scope"));
}
}
Ok(())
}
fn verify_payload(&self, claims: &Value) -> Result<(), AuthorizationError> {
// Verifica que el claim de audiencia coincida con tu indicador de recurso de API
let audiences = match &claims["aud"] {
Value::Array(arr) => arr.iter().filter_map(|v| v.as_str()).collect::<Vec<_>>(),
Value::String(s) => vec![s.as_str()],
_ => vec![],
};
if !audiences.contains(&"https://your-api-resource-indicator") {
return Err(AuthorizationError::new("Invalid audience for organization-level API resources"));
}
// Verifica que el ID de la organización coincida con el contexto (puede que necesites extraer esto del contexto de la solicitud)
let expected_org_id = "your-organization-id"; // Extrae del contexto de la solicitud
let org_id = claims["organization_id"].as_str().unwrap_or_default();
if expected_org_id != org_id {
return Err(AuthorizationError::new("Organization ID mismatch"));
}
// Verifica los alcances requeridos para recursos de API a nivel de organización
let required_scopes = vec!["api:read", "api:write"]; // Sustituye por tus alcances requeridos reales
let scopes = claims["scope"]
.as_str()
.map(|s| s.split(' ').collect::<Vec<_>>())
.unwrap_or_default();
for required_scope in &required_scopes {
if !scopes.contains(required_scope) {
return Err(AuthorizationError::new("Insufficient organization-level API scopes"));
}
}
Ok(())
}
Aplica el middleware a tu API
Ahora, aplica el middleware a tus rutas de API protegidas.
use rocket::{get, launch, routes, serde::json::Json};
use serde_json::{json, Value};
mod lib;
mod jwt_validator;
mod guards;
use lib::AuthInfo;
use jwt_validator::JwtValidator;
#[get("/api/protected")]
fn protected_handler(auth: AuthInfo) -> Json<Value> {
// Accede a la información de autenticación directamente desde el guardia de la solicitud
Json(json!({ "auth": auth }))
}
#[launch]
async fn rocket() -> _ {
let validator = JwtValidator::new().await.expect("No se pudo inicializar el validador de JWT");
rocket::build()
.manage(validator)
.mount("/", routes![protected_handler])
}
Prueba tu API protegida
Obtener tokens de acceso (Access tokens)
Desde tu aplicación cliente: Si has configurado una integración de cliente, tu aplicación puede obtener tokens automáticamente. Extrae el token de acceso y úsalo en las solicitudes a la API.
Para pruebas con curl / Postman:
-
Tokens de usuario: Usa las herramientas de desarrollador de tu aplicación cliente para copiar el token de acceso desde localStorage o la pestaña de red.
-
Tokens máquina a máquina: Utiliza el flujo de credenciales de cliente. Aquí tienes un ejemplo no normativo usando curl:
curl -X POST https://your-tenant.logto.app/oidc/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=your-m2m-client-id" \
-d "client_secret=your-m2m-client-secret" \
-d "resource=https://your-api-resource-indicator" \
-d "scope=api:read api:write"Puede que necesites ajustar los parámetros
resource
yscope
según tu recurso de API y permisos; también puede ser necesario un parámetroorganization_id
si tu API está asociada a una organización.
¿Necesitas inspeccionar el contenido del token? Usa nuestro decodificador de JWT para decodificar y verificar tus JWTs.
Probar endpoints protegidos
Solicitud con token válido
curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
http://localhost:3000/api/protected
Respuesta esperada:
{
"auth": {
"sub": "user123",
"clientId": "app456",
"organizationId": "org789",
"scopes": ["api:read", "api:write"],
"audience": ["https://your-api-resource-indicator"]
}
}
Token ausente
curl http://localhost:3000/api/protected
Respuesta esperada (401):
{
"error": "Authorization header is missing"
}
Token inválido
curl -H "Authorization: Bearer invalid-token" \
http://localhost:3000/api/protected
Respuesta esperada (401):
{
"error": "Invalid token"
}
Pruebas específicas del modelo de permisos
- Recursos de API globales
- Permisos de organización (no API)
- Recursos de API a nivel de organización
Escenarios de prueba para APIs protegidas con alcances globales:
- Alcances válidos: Prueba con tokens que incluyan los alcances de API requeridos (por ejemplo,
api:read
,api:write
) - Alcances ausentes: Espera 403 Prohibido cuando el token no tenga los alcances requeridos
- Audiencia incorrecta: Espera 403 Prohibido cuando la audiencia no coincida con el recurso de API
# Token sin los alcances requeridos - espera 403
curl -H "Authorization: Bearer token-without-required-scopes" \
http://localhost:3000/api/protected
Escenarios de prueba para control de acceso específico de organización:
- Token de organización válido: Prueba con tokens que incluyan el contexto correcto de organización (ID de organización y alcances)
- Alcances ausentes: Espera 403 Prohibido cuando el usuario no tenga permisos para la acción solicitada
- Organización incorrecta: Espera 403 Prohibido cuando la audiencia no coincida con el contexto de la organización (
urn:logto:organization:<organization_id>
)
# Token para organización incorrecta - espera 403
curl -H "Authorization: Bearer token-for-different-organization" \
http://localhost:3000/api/protected
Escenarios de prueba combinando validación de recursos de API con contexto de organización:
- Organización válida + alcances de API: Prueba con tokens que tengan tanto el contexto de organización como los alcances de API requeridos
- Alcances de API ausentes: Espera 403 Prohibido cuando el token de organización no tenga los permisos de API requeridos
- Organización incorrecta: Espera 403 Prohibido al acceder a la API con un token de otra organización
- Audiencia incorrecta: Espera 403 Prohibido cuando la audiencia no coincida con el recurso de API a nivel de organización
# Token de organización sin alcances de API - espera 403
curl -H "Authorization: Bearer organization-token-without-api-scopes" \
http://localhost:3000/api/protected
Lecturas adicionales
RBAC en la práctica: Implementando autorización segura para tu aplicación
Crea una aplicación SaaS multi-inquilino: Una guía completa desde el diseño hasta la implementación