Skip to content

new proxy-auth-lib/ workspace for applications that sit behind an identity-aware proxy with JWKS-backed JWT verification#381

Draft
horner wants to merge 10 commits into
mainfrom
copilot/build-trusted-proxy-middleware
Draft

new proxy-auth-lib/ workspace for applications that sit behind an identity-aware proxy with JWKS-backed JWT verification#381
horner wants to merge 10 commits into
mainfrom
copilot/build-trusted-proxy-middleware

Conversation

@horner

@horner horner commented Jun 24, 2026

Copy link
Copy Markdown
Member

This introduces a new proxy-auth-lib/ workspace for applications that sit behind an identity-aware proxy and need to trust signed identity assertions instead of raw forwarded headers. It provides a consistent MVP across Node.js, Python, Rust, and Go for header extraction, JWKS-backed JWT verification, issuer/audience/expiration checks, and verified identity propagation.

  • Multi-language middleware scaffold

    • Added proxy-auth-lib/ with language-specific implementations for:
      • Node.js: Express-style middleware
      • Python: ASGI middleware for FastAPI / Starlette-style apps
      • Rust: Axum middleware
      • Go: net/http middleware
    • Standardized the shared config model across all four implementations:
      • TRUSTED_PROXY_ASSERTION_HEADER
      • TRUSTED_PROXY_JWKS_URL
      • TRUSTED_PROXY_ISSUER
      • TRUSTED_PROXY_AUDIENCE
  • Verification model

    • Each implementation now handles:
      • configured assertion header lookup
      • JWKS key resolution
      • JWT/JWS signature verification
      • issuer validation
      • audience validation
      • expiration validation
      • verified identity extraction (subject, email, name, raw claims)
    • Invalid, missing, expired, malformed, or wrongly scoped assertions are rejected with a 401 without leaking token contents.
  • Shared fixtures and coverage

    • Added shared JWKS/JWT fixtures under proxy-auth-lib/testdata/
    • Added per-language coverage for:
      • valid assertion
      • missing assertion
      • expired assertion
      • invalid signature
      • wrong issuer
      • wrong audience
      • malformed token
  • Docs and repository structure

    • Added a dedicated docs page for the trusted proxy auth pattern
    • Updated navigation and repository layout references
    • Extended architecture docs to explicitly warn against trusting unsigned identity headers and to point readers at the new middleware examples
    • Kept vendor references neutral: Cloudflare, Pomerium, OAuth2 Proxy, Envoy, Traefik, and NGINX are described as examples of the same upstream-auth pattern, not special cases
  • Example

    • Node.js usage is intentionally minimal:
app.use(createTrustedProxyAuth(loadConfigFromEnv()));

app.get('/me', (req, res) => {
  res.json(req.trustedProxyIdentity);
});

Updates since initial draft

  • Hostname-derived config defaults

    • When env vars are unset, the auth domain defaults to auth.<parent-domain> of the host FQDN (e.g. web1.os.example.orgauth.os.example.org), with issuer/JWKS/audience derived from it. All vars remain optional overrides.
  • Static public-key verification (JWKS still preferred)

    • Added TRUSTED_PROXY_PUBLIC_KEY (inline PEM) and TRUSTED_PROXY_PUBLIC_KEY_FILE (path) across all four languages.
    • When a public key is configured, verification uses it directly and performs no network calls; otherwise JWKS is used (the default and recommended path for key rotation / OIDC providers).
    • Config validation now requires either a JWKS URL or a public key.
  • Algorithm pinning

    • Verification is pinned to RS256 in every implementation.
  • Fixtures & tests

    • Added shared proxy-auth-lib/testdata/public-key.pem (matches existing token fixtures) plus offline static-key tests in each language.

Copilot AI and others added 10 commits June 23, 2026 22:21
…tname-derived config defaults

- Add Express/Fastify/Hono Node adapters and shared core module
- Add Flask/Django Python middleware
- Add mieweb:accounts-proxy-auth Meteor package (accounts-base login)
- Derive auth domain from host FQDN instead of hardcoding; all settings optional
- Update tests and docs
- Accept TRUSTED_PROXY_PUBLIC_KEY (inline PEM) or _FILE (path) across Node,
  Python, Go, and Rust; when set, verification skips JWKS and runs offline
- JWKS remains the preferred default for key rotation and OIDC providers
- Pin verification algorithm to RS256 in all implementations
- Require either a JWKS URL or a public key in config validation
- Add shared testdata/public-key.pem fixture and static-key tests
- Enable jsonwebtoken use_pem feature for PEM keys (Rust)
- Document the JWKS vs static-key choice
Addresses code-quality review: drop 'from unittest import mock' and
qualify usage as unittest.mock (with explicit submodule import).
Publish on a proxy-auth-lib-v<semver> tag to npm, JSR, PyPI (Trusted
Publishing), crates.io, Atmosphere (Meteor), and the Go module proxy as
independent jobs, plus a Bun/Deno install smoke test. Each job derives the
version from the tag and runs a matching script in proxy-auth-lib/scripts/
that is reproducible locally.

- Add scripts/ (stamp-version + per-registry publish + runtime smoke) and README
- Add nodejs/jsr.json for JSR publishing
- Fix Go module path to the monorepo subpath so it actually resolves; release
  tags proxy-auth-lib/go/v<version> and warms the Go proxy
- Add required crates.io metadata (license, description, repository)
- Ignore Python build artifacts
@runleveldev

Copy link
Copy Markdown
Collaborator

This is a large addition that is only orthogonally related to the work done in this repo. Due to that fact, I would like this moved to its own repo where the library lifecycle can be managed independently of the infrastructure.

@horner

horner commented Jun 24, 2026

Copy link
Copy Markdown
Member Author

I don't necessarily disagree but I want to test alongside the server config for now. I'd like to merge it and then I'll use subtree to fork it out. We need a test environment to prove it out.

@horner

horner commented Jun 24, 2026

Copy link
Copy Markdown
Member Author

Also. I want every container to default to proxy auth and have these libraries installed in the templates and be prompted to use them.

@runleveldev

Copy link
Copy Markdown
Collaborator

Also. I want every container to default to proxy auth and have these libraries installed in the templates and be prompted to use them.

Defaulting to proxy auth is very doable. There's no clean way to preinstall this (these?) libraries though. You'd want to handle that at a project template layer which we don't currently provide.

@runleveldev runleveldev marked this pull request as draft June 24, 2026 13:54
@runleveldev

runleveldev commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Per our discussion, moving this to draft until the header contract is stabilized. See #355 for progress.

@runleveldev

Copy link
Copy Markdown
Collaborator

@horner I think I've stablized the header contract we'll be supporting. It's described https://github.com/mieweb/opensource-server/blob/348-oauth2-proxy-forward-auth/mie-opensource-landing/docs/users/consuming-auth.md which will be going out with the next update. That document is suggesting the environment variables OIDC_ISSUER, OIDC_JWKS_URI and OIDC_AUDIENCE. As an admin, I can force these environment variables into all containers to be read from /etc/environment (or PID 1's environ per container standards). All 3 (even audience) is going to be identical across all containers because technically the audience is for the proxy, not each individual app.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants