Pular para o conteúdo principal

Proteja sua API Sinatra com RBAC (controle de acesso baseado em papel) e validação de JWT

Este guia irá ajudá-lo a implementar autorização para proteger suas APIs Sinatra usando controle de acesso baseado em papel (RBAC) e JSON Web Tokens (JWTs) emitidos pelo Logto.

Antes de começar​

Seus aplicativos cliente precisam obter tokens de acesso (Access tokens) do Logto. Se você ainda não configurou a integração do cliente, confira nossos Guias rápidos para React, Vue, Angular ou outros frameworks de cliente, ou veja nosso Guia máquina para máquina para acesso servidor a servidor.

Este guia foca na validação no lado do servidor desses tokens em seu aplicativo Sinatra.

Uma figura mostrando o foco deste guia

O que você vai aprender​

  • Validação de JWT: Aprenda a validar tokens de acesso (Access tokens) e extrair informações de autenticação (Authentication)
  • Implementação de middleware: Crie middleware reutilizável para proteção de API
  • Modelos de permissão: Entenda e implemente diferentes padrões de autorização (Authorization):
    • Recursos de API globais para endpoints de aplicação
    • Permissões de organização para controle de funcionalidades específicas do locatário
    • Recursos de API em nível de organização para acesso a dados multi-inquilino
  • Integração com RBAC: Implemente permissões e escopos baseados em papel (Role-based access control (RBAC)) em seus endpoints de API

Pré-requisitos​

  • Última versão estável do Ruby instalada
  • Compreensão básica de Sinatra e desenvolvimento de API web
  • Um aplicativo Logto configurado (veja Guias rápidos se necessário)

Visão geral dos modelos de permissão​

Antes de implementar a proteção, escolha o modelo de permissão que se encaixa na arquitetura do seu aplicativo. Isso está alinhado com os três principais cenários de autorização do Logto:

RBAC de recursos globais de API
  • Caso de uso: Proteger recursos de API compartilhados em todo o seu aplicativo (não específicos de organização)
  • Tipo de token: Token de acesso (Access token) com público global (global audience)
  • Exemplos: APIs públicas, serviços principais do produto, endpoints de administração
  • Melhor para: Produtos SaaS com APIs usadas por todos os clientes, microsserviços sem isolamento de locatário
  • Saiba mais: Proteger recursos globais de API

💡 Escolha seu modelo antes de prosseguir – a implementação fará referência à abordagem escolhida ao longo deste guia.

Passos rápidos de preparação​

Configure recursos e permissões do Logto​

  1. Criar recurso de API: Vá para Console → Recursos de API e registre sua API (ex: https://api.seuapp.com)
  2. Definir permissões: Adicione escopos como read:products, write:orders – veja Definir recursos de API com permissões
  3. Criar papéis globais: Vá para Console → Papéis e crie papéis que incluam as permissões da sua API – veja Configurar papéis globais
  4. Atribuir papéis: Atribua papéis a usuários ou aplicativos M2M que precisam de acesso à API
Novo no RBAC?:

Comece com nosso guia de controle de acesso baseado em papel para instruções passo a passo de configuração.

Atualize seu aplicativo cliente​

Solicite os escopos apropriados em seu cliente:

O processo geralmente envolve atualizar a configuração do seu cliente para incluir um ou mais dos seguintes:

  • Parâmetro scope nos fluxos OAuth
  • Parâmetro resource para acesso a recursos de API
  • organization_id para contexto de organização
Antes de codificar:

Certifique-se de que o usuário ou app M2M que você está testando recebeu os papéis ou papéis de organização adequados que incluam as permissões necessárias para sua API.

Inicialize seu projeto de API​

Para inicializar um novo projeto Sinatra, crie um diretório e configure a estrutura básica:

mkdir seu-nome-da-api
cd seu-nome-da-api

Crie um Gemfile:

Gemfile
source 'https://rubygems.org'

gem 'sinatra'

Instale as dependências:

bundle install

Crie uma aplicação Sinatra básica:

app.rb
require 'sinatra'
require 'json'

get '/' do
content_type :json
{ message: 'Hello from Sinatra API' }.to_json
end

Inicie o servidor de desenvolvimento:

ruby app.rb
nota:

Consulte a documentação do Sinatra para mais detalhes sobre como configurar rotas, middleware e outros recursos.

Inicialize constantes e utilitários​

Defina as constantes e utilitários necessários em seu código para lidar com a extração e validação do token. Uma solicitação válida deve incluir um cabeçalho Authorization no formato Bearer <token de acesso (access token)>.

auth_constants.rb
module AuthConstants
JWKS_URI = 'https://your-tenant.logto.app/oidc/jwks'
ISSUER = 'https://your-tenant.logto.app/oidc'
end
auth_info.rb
class AuthInfo
attr_accessor :sub, :client_id, :organization_id, :scopes, :audience

def initialize(sub, client_id = nil, organization_id = nil, scopes = [], audience = [])
@sub = sub
@client_id = client_id
@organization_id = organization_id
@scopes = scopes
@audience = audience
end

def to_h
{
sub: @sub,
client_id: @client_id,
organization_id: @organization_id,
scopes: @scopes,
audience: @audience
}
end
end
authorization_error.rb
class AuthorizationError < StandardError
attr_reader :status

def initialize(message, status = 403)
super(message)
@status = status
end
end
auth_helpers.rb
module AuthHelpers
def extract_bearer_token(request)
authorization = request.headers['Authorization']

raise AuthorizationError.new('O cabeçalho Authorization está ausente (Authorization header is missing)', 401) unless authorization
raise AuthorizationError.new('O cabeçalho Authorization deve começar com "Bearer " (Authorization header must start with "Bearer ")', 401) unless authorization.start_with?('Bearer ')

authorization[7..-1] # Remove o prefixo 'Bearer '
end
end

Recupere informações sobre seu tenant Logto​

Você precisará dos seguintes valores para validar tokens emitidos pelo Logto:

  • URI do JSON Web Key Set (JWKS): A URL para as chaves públicas do Logto, usada para verificar assinaturas de JWT.
  • Emissor (Issuer): O valor esperado do emissor (URL OIDC do Logto).

Primeiro, encontre o endpoint do seu tenant Logto. Você pode encontrá-lo em vários lugares:

  • No Logto Console, em Configurações → Domínios.
  • Em qualquer configuração de aplicativo onde você configurou no Logto, Configurações → Endpoints & Credenciais.

Buscar no endpoint de descoberta do OpenID Connect​

Esses valores podem ser obtidos no endpoint de descoberta do OpenID Connect do Logto:

https://<seu-endpoint-logto>/oidc/.well-known/openid-configuration

Aqui está um exemplo de resposta (outros campos omitidos para brevidade):

{
"jwks_uri": "https://your-tenant.logto.app/oidc/jwks",
"issuer": "https://your-tenant.logto.app/oidc"
}

Como o Logto não permite personalizar o URI do JWKS ou o emissor, você pode definir esses valores manualmente no seu código. No entanto, isso não é recomendado para aplicações em produção, pois pode aumentar a sobrecarga de manutenção caso alguma configuração mude no futuro.

  • URI do JWKS: https://<seu-endpoint-logto>/oidc/jwks
  • Emissor: https://<seu-endpoint-logto>/oidc

Valide o token e as permissões​

Após extrair o token e buscar a configuração OIDC, valide o seguinte:

  • Assinatura: O JWT deve ser válido e assinado pelo Logto (via JWKS).
  • Emissor (Issuer): Deve corresponder ao emissor do seu tenant Logto.
  • Público (Audience): Deve corresponder ao indicador de recurso da API registrado no Logto, ou ao contexto da organização se aplicável.
  • Expiração: O token não pode estar expirado.
  • Permissões (escopos) (Permissions (scopes)): O token deve incluir os escopos necessários para sua API / ação. Os escopos são strings separadas por espaço na reivindicação scope.
  • Contexto da organização: Se estiver protegendo recursos de API em nível de organização, valide a reivindicação organization_id.

Veja JSON Web Token para saber mais sobre a estrutura e reivindicações do JWT.

O que verificar para cada modelo de permissão​

As reivindicações e regras de validação diferem conforme o modelo de permissão:

  • Reivindicação de público (aud): Indicador de recurso de API
  • Reivindicação de organização (organization_id): Não presente
  • Escopos (permissões) a verificar (scope): Permissões do recurso de API

Para permissões de organização que não são de API, o contexto da organização é representado pela reivindicação aud (por exemplo, urn:logto:organization:abc123). A reivindicação organization_id só está presente para tokens de recursos de API em nível de organização.

dica:

Sempre valide tanto as permissões (escopos) quanto o contexto (público, organização) para APIs multi-tenant seguras.

Adicione a lógica de validação​

Usamos a gem jwt para validar JWTs. Adicione-a ao seu Gemfile:

Gemfile
gem 'jwt'
# net-http faz parte da biblioteca padrão do Ruby desde o Ruby 2.7, não é necessário adicionar explicitamente

Depois, execute:

bundle install

Primeiro, adicione estas utilidades compartilhadas para lidar com JWKS e validação de token:

jwt_validator.rb
require 'jwt'
require 'net/http'
require 'json'

class JwtValidator
include AuthHelpers

def self.fetch_jwks
@jwks ||= begin
uri = URI(AuthConstants::JWKS_URI)
response = Net::HTTP.get_response(uri)
raise AuthorizationError.new('Falha ao buscar JWKS', 401) unless response.is_a?(Net::HTTPSuccess)

jwks_data = JSON.parse(response.body)
JWT::JWK::Set.new(jwks_data)
end
end

def self.validate_jwt(token)
jwks = fetch_jwks

# Deixe a biblioteca JWT lidar com a detecção do algoritmo a partir do JWKS
decoded_token = JWT.decode(token, nil, true, {
iss: AuthConstants::ISSUER,
verify_iss: true,
verify_aud: false, # Vamos verificar o público manualmente com base no modelo de permissão
jwks: jwks
})[0]

verify_payload(decoded_token)
decoded_token
end

def self.create_auth_info(payload)
scopes = payload['scope']&.split(' ') || []
audience = payload['aud'] || []

AuthInfo.new(
payload['sub'],
payload['client_id'],
payload['organization_id'],
scopes,
audience
)
end

def self.verify_payload(payload)
# Implemente sua lógica de verificação aqui com base no modelo de permissão
# Isso será mostrado na seção de modelos de permissão abaixo
end
end

Depois, implemente o middleware para verificar o token de acesso:

auth_middleware.rb
class AuthMiddleware
include AuthHelpers

def initialize(app)
@app = app
end

def call(env)
request = Rack::Request.new(env)

# Proteger apenas rotas específicas
if request.path.start_with?('/api/protected')
begin
token = extract_bearer_token(request)
decoded_token = JwtValidator.validate_jwt(token)

# Armazenar informações de autenticação no env para uso genérico
env['auth'] = JwtValidator.create_auth_info(decoded_token)

rescue AuthorizationError => e
return [e.status, { 'Content-Type' => 'application/json' }, [{ error: e.message }.to_json]]
rescue JWT::DecodeError, JWT::VerificationError, JWT::ExpiredSignature => e
return [401, { 'Content-Type' => 'application/json' }, [{ error: 'Token inválido' }.to_json]]
end
end

@app.call(env)
end
end

De acordo com seu modelo de permissão, implemente a lógica de verificação apropriada em JwtValidator:

jwt_validator.rb
def self.verify_payload(payload)
# Verifique se a reivindicação de público corresponde ao seu indicador de recurso de API
audiences = payload['aud'] || []
unless audiences.include?('https://your-api-resource-indicator')
raise AuthorizationError.new('Público inválido')
end

# Verifique os escopos necessários para recursos globais de API
required_scopes = ['api:read', 'api:write'] # Substitua pelos escopos necessários no seu caso
token_scopes = payload['scope']&.split(' ') || []

unless required_scopes.all? { |scope| token_scopes.include?(scope) }
raise AuthorizationError.new('Escopo insuficiente')
end
end

Aplique o middleware à sua API​

Agora, aplique o middleware às suas rotas de API protegidas.

app.rb
require 'sinatra'
require 'json'
require_relative 'auth_middleware'
require_relative 'auth_constants'
require_relative 'auth_info'
require_relative 'authorization_error'
require_relative 'auth_helpers'
require_relative 'jwt_validator'

# Aplicar middleware
use AuthMiddleware

get '/api/protected' do
content_type :json

# Acessar informações de autenticação a partir do env
auth = env['auth']
{ auth: auth.to_h }.to_json
end

# Endpoint público (não protegido pelo middleware)
get '/' do
content_type :json
{ message: "Endpoint público" }.to_json
end

Teste sua API protegida​

Obter tokens de acesso (Access tokens)​

Do seu aplicativo cliente: Se você configurou uma integração de cliente, seu aplicativo pode obter tokens automaticamente. Extraia o token de acesso e use-o nas requisições de API.

Para testes com curl / Postman:

  1. Tokens de usuário: Use as ferramentas de desenvolvedor do seu aplicativo cliente para copiar o token de acesso do localStorage ou da aba de rede.

  2. Tokens máquina para máquina: Use o fluxo de credenciais do cliente. Aqui está um exemplo não 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"

    Pode ser necessário ajustar os parâmetros resource e scope de acordo com seu recurso de API e permissões; um parâmetro organization_id também pode ser exigido se sua API for voltada para organização.

dica:

Precisa inspecionar o conteúdo do token? Use nosso decodificador de JWT para decodificar e verificar seus JWTs.

Testar endpoints protegidos​

Requisição com token válido
curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
http://localhost:3000/api/protected

Resposta 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

Resposta esperada (401):

{
"error": "Authorization header is missing"
}
Token inválido
curl -H "Authorization: Bearer invalid-token" \
http://localhost:3000/api/protected

Resposta esperada (401):

{
"error": "Invalid token"
}

Testes específicos do modelo de permissão​

Cenários de teste para APIs protegidas com escopos globais:

  • Escopos válidos: Teste com tokens que incluam os escopos de API necessários (por exemplo, api:read, api:write)
  • Escopos ausentes: Espere 403 Proibido quando o token não tiver os escopos necessários
  • Público errado: Espere 403 Proibido quando o público não corresponder ao recurso de API
# Token sem escopos necessários - espera-se 403
curl -H "Authorization: Bearer token-without-required-scopes" \
http://localhost:3000/api/protected

Leitura adicional​

RBAC na prática: Implementando autorização segura para seu aplicativo

Construa um aplicativo SaaS multi-inquilino: Um guia completo do design à implementação