# An OIDC Reverse Proxy, Written in Anger ## What is it? It's a multi-tentant non-caching tls-terminating [reverse proxy] that can terminate a hybrid of [OpenID Connect Core v1.0 with errata 2][oidc-v1] [^1] and [OAuth 2.1][oauth-v2.1] [^2]. It also generally reverse proxies HTTP 1.x, h2, websocket, and grpc traffic, as it's built (right now) on [pingora]. ## ... what? You stick in front of webapps that need either tls added or don't support checking oauth2 state themselves, and it does that for you. Think [oauth2-proxy], but with proper multitenancy support and significantly less battle-tested. ## whyyyy???? I [was](https://bsky.app/profile/directxman12.dev/post/3me6doan42k2t) [frustrated](https://bsky.app/profile/directxman12.dev/post/3merwp3aik22u) with the state of oauth2/oidc-terminating proxies. Particularly, at the time of writing, [oauth2-proxy] does not support multitenancy properly (it encourages the insecure practice of sharing client_ids if you use it for forward auth). Other things, like [caddy-security], felt overcomplicated for what i needed to do, and tbh i don't trust security tools that feel like they have everything and the the kitchen sink shoved in, unless they've been written by experts and/or extensively auditted. [caddy-security]: https://github.com/greenpau/caddy-security ## how do i use it? The configuration is done in [textproto] format [^3]. The format is in , and the inline docs serve as the documentation, so i'd suggest starting there. Here's a quick example config: ```textproto # bind on ipv6 bind_to_tcp { addr: '[::]:8443' } # serve these domains # serve copyparty w/ oidc termination on domains { key: "files.example.com" value: { # serve to a (single, here, but this is a `repeated` field) # uds backend, without tls uds { path: "/run/copyparty/party.sock" } # enable oidc termination on this domain oidc_auth { # standard oidc/oauth stuff discovery_url_base: "https://sso.example.com/oauth2/openid/files/" client_id: "files" # this is where we redirect after logout logout_url: "https://sso.example.com/" # put your client secret here client_secret_path: '/var/lib/secrets/files.client-secret' # these scopes are required, and sent in the request scopes { required: "profile" required: "group_names" required: "party" } # these claims are mapped to headers claims { claim_to_header { claim: "files_groups" header: "X-Idp-Groups" # since this is an array, concatenate it with commans # before sending it to fileparty serialize_as { join_array_items_with: "," } } claim_to_header { claim: "name" header: "X-Idp-User" } }, } # serve tls with the following certs tls { cert_path: "/var/lib/secrets/serving1.cert" key_path: "/var/lib/secrets/serving1.key" } } } # serve your sso server, with oidc termination off domains { key: "sso.example.com" value: { # your sso server might have it's own tls termination too, so # connect to it with tls https { addr: "127.0.0.1:1309" } } # serve tls with the following certs tls { cert_path: "/var/lib/secrets/serving2.cert" key_path: "/var/lib/secrets/serving2.key" } } ``` [textproto]: https://protobuf.dev/reference/protobuf/textformat-spec/ [^3]: **Q**: wait, seriously? **A**: yes, seriously. i'm putting my money where my mouth is, so to speak, when i say that textproto is nice, actually. ## should i trust this? **_no, probably not._** consider the following points if you chose to run this: - i make no claims about it's security. - i'm not a security professional, just a dev with a hobby of writing oauth implementations in anger, apparently [^4]. - if you're often targetted by script kiddies or nation-state actors, or you're working for a company or organization, please don't use this. - it'll probably be fine for low-stress homelab situations? that's what i'm using it for. [^4]: last year i wrote (but haven't published yet) a cute little oauth server that uses exclusively passkeys from scratch. ## but does it support...
...serving forward auth?
no, not right now. forward auth is an [underspecified mess][forward-auth-mess], so it's unlikely that i'll add support.
...authorization?
nope, this was written for reverse-proxying apps that either a) can make their own auth decisions (like [copyparty]), or b) don't need to make complex auth decisions
...other types of authn?
nope, and it's unlikely that it will -- i prefer to keep things well-scoped. unless it's something odic/oauth2-adjacent, in which case i'd consider it.
For anything else, check the [issue tracker] and/or file an issue. ## omg the code is messsyyyyyy! yeah, it probably needs further refactoring. ## Contributing On the off-chance that you'd like to submit code, i'll take a look at it and review it, but i'm likely going to be picky about what new features i accept. it's probably worth filing an issue to discuss first. any code submitted should be your own, and you should understand it fully. [forward-auth-mess]: https://github.com/kanidm/kanidm/issues/2774 [issue tracker]: https://tangled.org/directxman12.dev/proxy-in-anger/issues [copyparty]: https://github.com/9001/copyparty [reverse proxy]: https://en.wikipedia.org/wiki/Reverse_proxy [oidc-v1]: https://openid.net/specs/openid-connect-core-1_0.html [oauth-v2.1]: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1 [pingora]: https://github.com/cloudflare/pingora [oauth2-proxy]: https://github.com/oauth2-proxy/oauth2-proxy [^1]: the uuuh... reasonable parts? yeesh, there's a lotta junk in oidc core v1 [^2]: specifically, the authorization code grant flow