Skip to content

solidsystems/portfolio-auth-go

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

portfolio-auth-go

Supabase JWKS verification + go-chi/chi v5 HTTP middleware adapter for the Solid Systems adult-portfolio Go services (700 Days, MoT, Traced, Didja).

Status: pre-release / pre-tag

No v0.1.0 tag exists yet. The public API surface is captured below in "Planned public API" and will be implemented across the next two plans of the Didja Phase 1 milestone:

  • Plan 03 lands the Verifier (JWKS fetcher + kid-rotation + verify).
  • Plan 04 lands the chi middleware adapter (Middleware, ClaimsFromContext).
  • Plan 09 cuts the v0.1.0 tag once the API surface stops moving.

Until v0.1.0 ships, consumers MUST depend on this module via a replace directive against a local clone — see "Consuming pre-tag" below.

Purpose

Stateless Supabase JWT verification (JWKS fetch + cache + kid-rotation handling) plus a chi HTTP middleware adapter. Lifted from the verified-half of the 700-days pkg/auth/supabase.go so multiple portfolio services don't rediscover JWKS edge cases (kid-miss refetch, ECDSA P-256 parsing, kid-fallback boundary hole). Does NOT include Supabase admin operations — those remain in 700-days.

Scope

In scope at v0.1.0:

  • JWKS Verifier with TTL cache + kid-miss-refetch-once
  • Claims struct (typed UUID Sub, parsed at verification time)
  • chi middleware (func Middleware(*Verifier) func(http.Handler) http.Handler)
  • Fail-closed on empty issuer (per 700-days hardening)
  • Opt-in kid-fallback boundary hole, off by default

Out of scope at v0.1.0:

  • GitHub Actions CI (Didja's integration tests exercise this module end-to-end)
  • example/ binary (README + unit tests are sufficient docs)
  • Pluggable JWKSFetcher interface (httptest stub is enough for v0.1.0 tests)
  • Optional-auth routes (wiring the middleware = auth enforced, no per-route opt-in)
  • Supabase admin HTTP operations (GetUserByID, DeleteUser, etc. — stay in 700-days)

Planned public API

package auth

type Config struct {
    SupabaseURL      string
    Issuer           string
    Audience         string
    AllowKidFallback bool
    JWKSCacheTTL     time.Duration
    HTTPClient       *http.Client
}

type Verifier struct { /* unexported */ }

func NewVerifier(cfg Config) (*Verifier, error)
func (v *Verifier) Verify(tokenStr string) (*Claims, error)

type Claims struct {
    Sub    uuid.UUID
    Email  string
    Role   string
    Aud    []string
    Exp    time.Time
    RawJWT string
}

func Middleware(v *Verifier) func(http.Handler) http.Handler
func ClaimsFromContext(ctx context.Context) (*Claims, bool)
func MustClaims(ctx context.Context) *Claims
func UserIDFromContext(ctx context.Context) (uuid.UUID, bool)

Env-var contract

Consumer services configure the verifier from these environment variables. The Didja, 700-days, MoT, and Traced services all use the same set.

Env var Required Default Meaning
SUPABASE_URL yes Supabase project base URL. JWKS is fetched from ${SUPABASE_URL}/auth/v1/.well-known/jwks.json.
SUPABASE_JWT_ISSUER yes Expected iss claim. Fail-closed on empty. Empty string is a configuration error, not a permissive default.
SUPABASE_JWT_AUDIENCE no authenticated Expected aud claim.
SUPABASE_JWT_ALLOW_KID_FALLBACK no false If true, falls back to cachedKeys[0] when a token's kid is not in the cache even after refetch. DO NOT enable in production unless actively debugging a Supabase key-rotation incident — it's an opt-in boundary hole per 700-days hardening (C-4).
SUPABASE_JWKS_CACHE_TTL no 1h How long JWKS responses are cached before re-fetching.

Error envelope

The middleware writes {"error":"unauthorized","code":"<reason>"} with HTTP 401 on any auth failure and terminates the chain. Codes:

Code Meaning
missing_authorization No Authorization header, or not Bearer ....
invalid_token Token malformed, signature invalid, or kid not found post-refetch.
expired_token exp claim is in the past.
wrong_audience aud claim does not match SUPABASE_JWT_AUDIENCE.
wrong_issuer iss claim does not match SUPABASE_JWT_ISSUER.
jwks_unavailable JWKS endpoint returned non-2xx or unparseable JSON.

Consuming pre-tag

While v0.1.0 has not yet been tagged, consumer modules MUST use a local replace directive. From the consumer module root:

# 1. Clone portfolio-auth-go next to the consumer
git clone https://github.com/solidsystems/portfolio-auth-go.git ../portfolio-auth-go

# 2. Add a replace directive pointing at the local clone
go mod edit -replace github.com/solidsystems/portfolio-auth-go=../portfolio-auth-go

# 3. Add the require line (version is irrelevant under replace; v0.0.0 works)
go mod edit -require github.com/solidsystems/portfolio-auth-go@v0.0.0
go mod tidy

Once v0.1.0 is tagged (Didja Phase 1 Plan 09), swap to a standard module dependency:

go get github.com/solidsystems/portfolio-auth-go@v0.1.0
go mod edit -dropreplace github.com/solidsystems/portfolio-auth-go
go mod tidy

License

MIT — see LICENSE.

About

Supabase JWKS verification + chi middleware for the solidsystems portfolio. Consumed by 700days, mot, traced, didja.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages