EnrichmentMiddleware, AuthzMiddleware, IdentityEnricher, PermissionProvider, and related types are removed from this module. They now live in code.nochebuena.dev/go/httpauth, the provider-agnostic middleware layer. AuthMiddleware is updated to call httpauth.SetTokenData, fulfilling the integration contract between provider-specific auth and generic middleware. This module now has a single responsibility: Firebase JWT verification. BREAKING CHANGE: IdentityEnricher, PermissionProvider, EnrichmentMiddleware, AuthzMiddleware, and WithTenantHeader are no longer exported from this package. Import code.nochebuena.dev/go/httpauth for those identifiers.
127 lines
5.4 KiB
Markdown
127 lines
5.4 KiB
Markdown
# httpauth-firebase
|
|
|
|
Firebase-backed HTTP middleware for authentication, identity enrichment, and
|
|
role-based access control.
|
|
|
|
## Purpose
|
|
|
|
`httpauth-firebase` provides three composable `net/http` middleware functions that
|
|
implement the full auth stack: JWT verification via Firebase, app-specific identity
|
|
enrichment via a caller-supplied `IdentityEnricher`, and permission enforcement via
|
|
`rbac`. The output contract is always `rbac.Identity` — downstream code and business
|
|
logic are decoupled from Firebase types entirely.
|
|
|
|
## Tier & Dependencies
|
|
|
|
**Tier:** 4 (transport auth layer; depends on Tier 3 `httpauth` and external Firebase SDK)
|
|
**Module:** `code.nochebuena.dev/go/httpauth-firebase`
|
|
**Direct imports:** `code.nochebuena.dev/go/httpauth`, `firebase.google.com/go/v4/auth`
|
|
**Transitive:** `code.nochebuena.dev/go/rbac` (indirect, pulled in by `httpauth`)
|
|
|
|
`httpauth-firebase` does not import `logz`, `httpmw`, or `httputil`. It has no
|
|
logger parameter — errors are returned as HTTP responses, not logged here.
|
|
|
|
`EnrichmentMiddleware`, `AuthzMiddleware`, `IdentityEnricher`, `WithTenantHeader`,
|
|
`Cache`, and permission providers all live in `code.nochebuena.dev/go/httpauth`.
|
|
This module only provides `AuthMiddleware` and the `TokenVerifier` interface.
|
|
|
|
## Key Design Decisions
|
|
|
|
- **Provider-specific naming** (ADR-001): The module is named `httpauth-firebase`
|
|
because it imports Firebase directly. Other providers live in sibling modules
|
|
(`httpauth-jwt`, etc.). All converge on `rbac.Identity` via the shared `httpauth`
|
|
contract. The `TokenVerifier` interface is retained for unit-test mockability only.
|
|
- **Integration contract via `httpauth.SetTokenData`**: `AuthMiddleware` stores
|
|
`uid` and `claims` using `httpauth.SetTokenData`, which writes to context keys
|
|
owned by the `httpauth` package. `EnrichmentMiddleware` (from `httpauth`) reads
|
|
them. This is the explicit integration point between provider-specific and
|
|
provider-agnostic middleware.
|
|
- **Single responsibility**: This module only verifies Firebase tokens. Identity
|
|
enrichment, RBAC, and caching all live in `code.nochebuena.dev/go/httpauth`.
|
|
|
|
## Patterns
|
|
|
|
**Full stack (most routes):**
|
|
|
|
```go
|
|
import (
|
|
httpauth "code.nochebuena.dev/go/httpauth-firebase"
|
|
httpauthmw "code.nochebuena.dev/go/httpauth"
|
|
)
|
|
|
|
r.Use(httpauth.AuthMiddleware(firebaseAuthClient, []string{"/health", "/metrics/*"}))
|
|
r.Use(httpauthmw.EnrichmentMiddleware(userEnricher, httpauthmw.WithTenantHeader("X-Tenant-ID")))
|
|
|
|
// Per-route RBAC:
|
|
r.With(httpauthmw.AuthzMiddleware(permProvider, "orders", rbac.Write)).
|
|
Post("/orders", httputil.Handle(v, svc.CreateOrder))
|
|
```
|
|
|
|
**Token-only (webhook verification):**
|
|
|
|
```go
|
|
r.Use(httpauth.AuthMiddleware(firebaseAuthClient, nil))
|
|
// No enrichment, no authz — just verify the token is valid
|
|
```
|
|
|
|
**Reading identity in a handler:**
|
|
|
|
```go
|
|
identity, ok := rbac.FromContext(r.Context())
|
|
if !ok {
|
|
// should not happen if EnrichmentMiddleware is in the chain
|
|
}
|
|
fmt.Println(identity.UID, identity.DisplayName, identity.TenantID)
|
|
```
|
|
|
|
**Public paths (bypass token check):**
|
|
|
|
```go
|
|
// path.Match patterns — supports * wildcard
|
|
publicPaths := []string{"/health", "/ready", "/metrics/*"}
|
|
r.Use(httpauth.AuthMiddleware(firebaseAuthClient, publicPaths))
|
|
```
|
|
|
|
**Interfaces the application must implement (defined in httpauth, not here):**
|
|
|
|
```go
|
|
// httpauthmw.IdentityEnricher: called by EnrichmentMiddleware
|
|
type MyEnricher struct{ db *sql.DB }
|
|
func (e *MyEnricher) Enrich(ctx context.Context, uid string, claims map[string]any) (rbac.Identity, error) {
|
|
user, err := e.db.LookupUser(ctx, uid)
|
|
if err != nil { return rbac.Identity{}, err }
|
|
return rbac.NewIdentity(uid, user.DisplayName, user.Email), nil
|
|
}
|
|
```
|
|
|
|
## What to Avoid
|
|
|
|
- Do not use `AuthMiddleware` alone and assume the request is fully authorised.
|
|
Token verification only confirms the token is valid; it does not enforce
|
|
application-level roles or permissions.
|
|
- Do not put `AuthzMiddleware` before `EnrichmentMiddleware` in the chain.
|
|
`AuthzMiddleware` reads `rbac.Identity` from context; if enrichment has not run,
|
|
it will return 401.
|
|
- Do not read `firebase.Token` fields directly in business logic. The token UID and
|
|
claims are stored in context by `httpauth.SetTokenData` under keys owned by the
|
|
`httpauth` package. Use `rbac.FromContext` to read the enriched identity.
|
|
- Do not store application-specific data in Firebase custom claims and bypass the
|
|
`IdentityEnricher`. Claims are read by `EnrichmentMiddleware` (from `httpauth`)
|
|
and passed to `Enrich` — the enricher is the correct place to resolve identity.
|
|
- Do not import `httpauth-firebase` from service or domain layers. It is a transport
|
|
package. Dependency should flow inward: handler → service, never service →
|
|
handler/middleware.
|
|
- Do not redefine `IdentityEnricher`, `Cache`, or permission providers here. They
|
|
live in `code.nochebuena.dev/go/httpauth`.
|
|
|
|
## Testing Notes
|
|
|
|
- `compliance_test.go` verifies at compile time that `*mockVerifier` satisfies
|
|
`TokenVerifier`. Interface checks for `IdentityEnricher`, `Cache`, and permission
|
|
providers live in `code.nochebuena.dev/go/httpauth`'s own compliance test.
|
|
- `AuthMiddleware` can be tested by injecting a `mockVerifier` that returns a
|
|
controlled `*auth.Token`. No real Firebase project is needed.
|
|
- `EnrichmentMiddleware` and `AuthzMiddleware` tests belong in the `httpauth` module,
|
|
not here.
|
|
- Use `httptest.NewRecorder()` and `httptest.NewRequest()` for all HTTP-level tests.
|