Pular para o conteúdo principal

Proteja sua API Fiber com RBAC e validação de JWT

Este guia irá ajudá-lo a implementar autorização para proteger suas APIs Fiber 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 Fiber.

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 Go instalada
  • Compreensão básica de Fiber 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 Go com Fiber, você pode seguir estes passos:

go mod init seu-nome-da-api
go get github.com/gofiber/fiber/v2

Em seguida, crie uma configuração básica de servidor Fiber:

main.go
package main

import (
"log"

"github.com/gofiber/fiber/v2"
)

func main() {
app := fiber.New()

log.Fatal(app.Listen(":3000"))
}
nota:

Consulte a documentação do Fiber para mais detalhes sobre como configurar rotas, middlewares 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_middleware.go
package main

import (
"fmt"
"net/http"
"strings"
)

const (
JWKS_URI = "https://your-tenant.logto.app/oidc/jwks"
ISSUER = "https://your-tenant.logto.app/oidc"
)

type AuthorizationError struct {
Message string
Status int
}

func (e *AuthorizationError) Error() string {
return e.Message
}

func NewAuthorizationError(message string, status ...int) *AuthorizationError {
statusCode := http.StatusForbidden // Padrão para 403 Proibido
if len(status) > 0 {
statusCode = status[0]
}
return &AuthorizationError{
Message: message,
Status: statusCode,
}
}

func extractBearerTokenFromHeaders(r *http.Request) (string, error) {
const bearerPrefix = "Bearer "

authorization := r.Header.Get("Authorization")
if authorization == "" {
return "", NewAuthorizationError("Cabeçalho Authorization está ausente", http.StatusUnauthorized)
}

if !strings.HasPrefix(authorization, bearerPrefix) {
return "", NewAuthorizationError(fmt.Sprintf("O cabeçalho Authorization deve começar com \"%s\"", bearerPrefix), http.StatusUnauthorized)
}

return strings.TrimPrefix(authorization, bearerPrefix), nil
}

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​

Utilizamos github.com/lestrrat-go/jwx para validar JWTs. Instale-o caso ainda não tenha feito isso:

go mod init seu-projeto
go get github.com/lestrrat-go/jwx/v3

Primeiro, adicione estes componentes compartilhados ao seu auth_middleware.go:

auth_middleware.go
import (
"context"
"strings"
"time"

"github.com/lestrrat-go/jwx/v3/jwk"
"github.com/lestrrat-go/jwx/v3/jwt"
)

var jwkSet jwk.Set

func init() {
// Inicializa o cache do JWKS
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

var err error
jwkSet, err = jwk.Fetch(ctx, JWKS_URI)
if err != nil {
panic("Falha ao buscar JWKS: " + err.Error())
}
}

// validateJWT valida o JWT e retorna o token analisado
func validateJWT(tokenString string) (jwt.Token, error) {
token, err := jwt.Parse([]byte(tokenString), jwt.WithKeySet(jwkSet))
if err != nil {
return nil, NewAuthorizationError("Token inválido: "+err.Error(), http.StatusUnauthorized)
}

// Verifica o emissor
if token.Issuer() != ISSUER {
return nil, NewAuthorizationError("Emissor inválido", http.StatusUnauthorized)
}

if err := verifyPayload(token); err != nil {
return nil, err
}

return token, nil
}

// Funções auxiliares para extrair dados do token
func getStringClaim(token jwt.Token, key string) string {
if val, ok := token.Get(key); ok {
if str, ok := val.(string); ok {
return str
}
}
return ""
}

func getScopesFromToken(token jwt.Token) []string {
if val, ok := token.Get("scope"); ok {
if scope, ok := val.(string); ok && scope != "" {
return strings.Split(scope, " ")
}
}
return []string{}
}

func getAudienceFromToken(token jwt.Token) []string {
return token.Audience()
}

Em seguida, implemente o middleware para verificar o token de acesso:

auth_middleware.go
import (
"net/http"
"github.com/gofiber/fiber/v2"
)

func VerifyAccessToken(c *fiber.Ctx) error {
// Converter requisição fiber para http.Request para compatibilidade
req := &http.Request{
Header: make(http.Header),
}
req.Header.Set("Authorization", c.Get("Authorization"))

tokenString, err := extractBearerTokenFromHeaders(req)
if err != nil {
authErr := err.(*AuthorizationError)
return c.Status(authErr.Status).JSON(fiber.Map{"error": authErr.Message})
}

token, err := validateJWT(tokenString)
if err != nil {
authErr := err.(*AuthorizationError)
return c.Status(authErr.Status).JSON(fiber.Map{"error": authErr.Message})
}

// Armazenar token em locals para uso genérico
c.Locals("auth", token)
return c.Next()
}

De acordo com seu modelo de permissão, pode ser necessário adotar uma lógica diferente para verifyPayload:

auth_middleware.go
func verifyPayload(token jwt.Token) error {
// Verifica se a reivindicação de público corresponde ao seu indicador de recurso de API
if !hasAudience(token, "https://your-api-resource-indicator") {
return NewAuthorizationError("Público inválido")
}

// Verifica os escopos necessários para recursos globais de API
requiredScopes := []string{"api:read", "api:write"} // Substitua pelos escopos necessários
if !hasRequiredScopes(token, requiredScopes) {
return NewAuthorizationError("Escopo insuficiente")
}

return nil
}

Adicione estas funções auxiliares para verificação do payload:

auth_middleware.go
// hasAudience verifica se o token possui o público especificado
func hasAudience(token jwt.Token, expectedAud string) bool {
audiences := token.Audience()
for _, aud := range audiences {
if aud == expectedAud {
return true
}
}
return false
}

// hasOrganizationAudience verifica se o token possui formato de público de organização
func hasOrganizationAudience(token jwt.Token) bool {
audiences := token.Audience()
for _, aud := range audiences {
if strings.HasPrefix(aud, "urn:logto:organization:") {
return true
}
}
return false
}

// hasRequiredScopes verifica se o token possui todos os escopos necessários
func hasRequiredScopes(token jwt.Token, requiredScopes []string) bool {
scopes := getScopesFromToken(token)
for _, required := range requiredScopes {
found := false
for _, scope := range scopes {
if scope == required {
found = true
break
}
}
if !found {
return false
}
}
return true
}

// hasMatchingOrganization verifica se o público do token corresponde à organização esperada
func hasMatchingOrganization(token jwt.Token, expectedOrgID string) bool {
expectedAud := fmt.Sprintf("urn:logto:organization:%s", expectedOrgID)
return hasAudience(token, expectedAud)
}

// hasMatchingOrganizationID verifica se o organization_id do token corresponde ao esperado
func hasMatchingOrganizationID(token jwt.Token, expectedOrgID string) bool {
orgID := getStringClaim(token, "organization_id")
return orgID == expectedOrgID
}

Aplique o middleware à sua API​

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

main.go
package main

import (
"github.com/gofiber/fiber/v2"
"github.com/lestrrat-go/jwx/v3/jwt"
)

func main() {
app := fiber.New()

// Aplique o middleware às rotas protegidas
app.Get("/api/protected", VerifyAccessToken, func(c *fiber.Ctx) error {
// Informações do token de acesso diretamente dos locals
tokenInterface := c.Locals("auth")
if tokenInterface == nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Token não encontrado"})
}

token := tokenInterface.(jwt.Token)

return c.JSON(fiber.Map{
"sub": token.Subject(),
"client_id": getStringClaim(token, "client_id"),
"organization_id": getStringClaim(token, "organization_id"),
"scopes": getScopesFromToken(token),
"audience": getAudienceFromToken(token),
})
})

app.Listen(":8080")
}

Ou usando grupos de rotas:

main.go
package main

import (
"github.com/gofiber/fiber/v2"
"github.com/lestrrat-go/jwx/v3/jwt"
)

func main() {
app := fiber.New()

// Crie um grupo de rotas protegidas
api := app.Group("/api", VerifyAccessToken)
api.Get("/protected", func(c *fiber.Ctx) error {
// Informações do token de acesso diretamente dos locals
token := c.Locals("auth").(jwt.Token)

return c.JSON(fiber.Map{
"sub": token.Subject(),
"client_id": getStringClaim(token, "client_id"),
"organization_id": getStringClaim(token, "organization_id"),
"scopes": getScopesFromToken(token),
"audience": getAudienceFromToken(token),
"message": "Dados protegidos acessados com sucesso",
})
})

app.Listen(":8080")
}

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