Skip to content

feat(middleware): add M2M authentication gate#125

Merged
qnen merged 2 commits into
developfrom
feat/item-17-m2m-auth-middleware
Jul 20, 2026
Merged

feat(middleware): add M2M authentication gate#125
qnen merged 2 commits into
developfrom
feat/item-17-m2m-auth-middleware

Conversation

@qnen

@qnen qnen commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

O quê

Adiciona M2MAuthenticator — um middleware Fiber v3 (RequireM2M) que autentica chamadores machine-to-machine verificando um token de aplicação (M2M) emitido pelo Casdoor.

É a fundação do gate do endpoint de declarações (PUT /v1/declarations/{slug}) da inversão do access-manager: cada plugin passa a declarar suas próprias permissions via manifest, e esse endpoint precisa de uma trava que prove que quem chama é uma aplicação M2M legítima. O comportamento fica centralizado no lib-auth de propósito, pra qualquer serviço reusar.

Como

  • Verificação RS256 offline contra uma chave pública injetada (o certificado do issuer, ex.: token_jwt_key.pem do Casdoor). Sem round-trip de rede, sem dependência do SDK do Casdoor — cada consumidor injeta o cert do seu próprio issuer.
  • NewM2MAuthenticator(certificatePEM, enabled, logger): quando enabled=false é pass-through (dev/local); quando enabled=true exige um PEM parseável na construção (misconfig falha no boot, não em runtime).
  • RequireM2M() expõe a identidade autenticada (sub, azp) via M2MSubjectFromContext / M2MClientIDFromContext pros handlers downstream.

Postura de segurança (Opção B — decidida)

Autentica, não autoriza. RS256 é pinado (fecha alg-substitution none/HS256), exp é obrigatório, e o type tem que ser application. Não faz RBAC e não amarra o chamador ao {slug} — essa checagem de posse é intencionalmente fora de escopo: a application M2M é provisionada manualmente por um operador confiável, e possuir um token M2M válido é a fronteira de confiança aceita. Hardening de posse (app↔slug) fica anotado como evolução futura.

Base empírica: um token M2M real de dev carrega type=application, sub="admin/<uuid>", name="<uuid>", azp=<clientId>não carrega o slug legível em claim nenhum, o que inviabiliza um match direto claim==slug e motivou a Opção B.

Falhas (todas fail-closed)

Situação Resposta
Token ausente 401
Inparseável / assinatura inválida / expirado / sem exp 401
Token autêntico mas type != application (ex.: normal-user) 403
Application token sem sub 401

Testes & qualidade

  • Verificação RS256 real (par de chaves gerado no teste), cobrindo os vetores: chave errada, alg-confusion HS256, alg none, expirado, sem exp, tipo errado, sub ausente, malformado, nil-key, pass-through desabilitado, e o caminho completo via app.Test do Fiber. go build/vet/test/golangci-lint verdes.
  • Review adversarial (security + logic): PASS, zero Critical/High/Medium.

Próximo passo (outro repo)

Ao mergear, o semantic-release corta uma nova v3.x beta. O plugin-access-manager (componente identity) então faz o bump e pluga RequireM2M() como 1º handler da rota PUT /v1/declarations/:slug, passando o cert Casdoor embedado.


🤖 Generated with Claude Code

Add M2MAuthenticator, a Fiber v3 middleware (RequireM2M) that
authenticates machine-to-machine callers by verifying a Casdoor-issued
RS256 application token offline against an injected RSA public key (the
issuer certificate), so any service can reuse it by supplying its own
cert. No network round-trip and no Casdoor SDK dependency.

It authenticates only: RS256 is pinned (closing none/HS256 alg
substitution), expiry is required, and the token type must be
"application". It does not authorize and does not bind the caller to a
target slug; that ownership check is intentionally out of scope, with
possession of a valid, manually-provisioned M2M token as the trust
boundary. The authenticated identity (sub, azp) is exposed to downstream
handlers via M2MSubjectFromContext / M2MClientIDFromContext. Every
failure path fails closed (401/403); when disabled the middleware is a
pass-through, and an enabled authenticator requires a parseable cert at
construction.

X-Lerian-Ref: 0x1
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 53d31e9e-4ee5-4b7d-b02b-eaa354e72425

📥 Commits

Reviewing files that changed from the base of the PR and between 2034198 and 5afd305.

📒 Files selected for processing (2)
  • auth/middleware/m2m.go
  • auth/middleware/m2m_test.go

📝 Walkthrough

Walkthrough

Adds configurable Fiber middleware for offline Casdoor RS256 M2M token authentication. It validates JWT claims, returns 401 or 403, stores sub and azp in Fiber locals, adds tracing attributes, and tests configuration, verification, enforcement, and identity propagation.

Changes

M2M authentication

Layer / File(s) Summary
Authenticator setup and test fixtures
auth/middleware/m2m.go, auth/middleware/m2m_test.go
Defines authenticator state, parses RSA certificate PEM data when enabled, supports disabled pass-through configuration, and provides RSA/JWT test helpers.
JWT verification rules
auth/middleware/m2m.go, auth/middleware/m2m_test.go
Accepts valid RS256 application tokens with required exp and sub claims, optionally pins iss, and rejects invalid signatures, algorithm confusion, expired or malformed tokens, missing claims, wrong token types, and nil keys.
Middleware enforcement and identity propagation
auth/middleware/m2m.go, auth/middleware/m2m_test.go
Adds bearer-token enforcement, disabled-mode pass-through, Fiber-local identity storage and accessors, tracing attributes, and middleware response tests.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant RequireM2M
  participant verify
  participant DownstreamHandler
  Client->>RequireM2M: Send request with bearer token
  RequireM2M->>verify: Validate JWT
  verify-->>RequireM2M: Return verified sub and azp
  RequireM2M->>DownstreamHandler: Set Fiber locals and continue
  DownstreamHandler-->>Client: Return response with M2M identity
Loading
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/item-17-m2m-auth-middleware

Comment @coderabbitai help to get the list of available commands.

@qnen qnen self-assigned this Jul 20, 2026
The M2M gate verified the RS256 signature but ignored the "iss"
claim, so any application token signed by the same key passed regardless
of issuer. This is safe in a single-issuer deployment but becomes
cross-issuer token reuse if one signing key ever serves multiple
issuers/orgs.

NewM2MAuthenticator now takes an expectedIssuer: when non-empty it pins
the token issuer via jwt.WithIssuer; empty keeps the prior behavior
(no issuer check) for single-issuer/dev setups. Audience is deliberately
not pinned here — a Casdoor client_credentials token carries the caller's
own clientId as aud, so pinning specific audiences would be the app<->slug
ownership binding that is intentionally out of scope (Option B).

X-Lerian-Ref: 0x1
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@qnen
qnen merged commit b29e0e1 into develop Jul 20, 2026
3 checks passed
@qnen
qnen deleted the feat/item-17-m2m-auth-middleware branch July 20, 2026 18:40
@lerian-studio-midaz-push-bot

Copy link
Copy Markdown

🎉 This PR is included in version 3.0.0-beta.4 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

lerian-studio-midaz-push-bot Bot pushed a commit that referenced this pull request Jul 20, 2026
## [3.0.0-beta.5](v3.0.0-beta.4...v3.0.0-beta.5) (2026-07-20)

### Features

* **middleware:** expose M2M identity via request context ([c48393a](c48393a)), closes [#125](#125)

### Bug Fixes

* **middleware:** return zero M2MIdentity when reporting absent ([25fc4ec](25fc4ec)), closes [#126](#126)
lerian-studio-midaz-push-bot Bot pushed a commit that referenced this pull request Jul 22, 2026
## [3.0.0](v2.9.0...v3.0.0) (2026-07-22)

### ⚠ BREAKING CHANGES

* **middleware:** module path is now github.com/LerianStudio/lib-auth/v3 and the
Authorize middleware requires Fiber v3.

X-Lerian-Ref: 0x1

### Features

* **middleware:** add authz cache, circuit breaker, and bounded retry ([c0469e6](c0469e6)), closes [#107](#107) [#127](#127) [#127](#127) [#108](#108)
* **middleware:** add fail-closed AUTH_REQUIRED option ([f7fff6c](f7fff6c)), closes [#107](#107)
* **middleware:** add M2M authentication gate ([2034198](2034198))
* **middleware:** add opt-in local JWT signature verification ([#128](#128)) ([48b81a3](48b81a3)), closes [#106](#106)
* **middleware:** add optional issuer pinning to M2M gate ([5afd305](5afd305))
* **middleware:** expose M2M identity via request context ([c48393a](c48393a)), closes [#125](#125)
* **middleware:** forward product on M2M auth (flag-gated) ([93c44db](93c44db))
* **middleware:** migrate to Fiber v3, cut /v3 major ([024a909](024a909))
* real M2M subject and token type whitelist ([e31ac53](e31ac53))

### Bug Fixes

* **middleware:** return zero M2MIdentity when reporting absent ([25fc4ec](25fc4ec)), closes [#126](#126)
@lerian-studio-midaz-push-bot

Copy link
Copy Markdown

🎉 This PR is included in version 3.0.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant