In debug mode this outputs the oidc discovery
2026-07-07T20:51:14+02:00 DBG discover config={"authorization_endpoint":"http://192.168.178.2:5556/dex/auth","claims_supported":["iss","sub","aud","iat","exp","email","email_verified","locale","name","preferred_username","at_hash"],"code_challenge_methods_supported":["S256","plain"],"device_authorization_endpoint":"http://192.168.178.2:5556/dex/device/code","grant_types_supported":["authorization_code","refresh_token","urn:ietf:params:oauth:grant-type:device_code","urn:ietf:params:oauth:grant-type:token-exchange"],"id_token_signing_alg_values_supported":["RS256"],"introspection_endpoint":"http://192.168.178.2:5556/dex/token/introspect","issuer":"http://192.168.178.2:5556/dex","jwks_uri":"http://192.168.178.2:5556/dex/keys","request_uri_parameter_supported":false,"response_types_supported":["code"],"scopes_supported":["openid","email","groups","profile","offline_access"],"subject_types_supported":["public"],"token_endpoint":"http://192.168.178.2:5556/dex/token","token_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post"],"userinfo_endpoint":"http://192.168.178.2:5556/dex/userinfo"} rp.function=NewRelyingPartyOIDC
Add two new endpoints for native app OIDC authentication using the
PKCE relay pattern (similar to Vaultwarden's SSO implementation):
- POST /auth/oidc/external/authorize - accepts a PKCE code_challenge
from the client, forwards it to the IdP, and returns the authorize URL
- POST /auth/oidc/external/token - accepts the auth code and
code_verifier, relays them to the IdP for token exchange, and returns
a gotify client token
The server never generates its own PKCE pair for this flow. It then relays
the client's code_challenge to the IdP during authorization and the
code_verifier during token exchange. The IdP validates the binding.
Pending auth sessions are stored in memory with a 10-minute TTL.
CSRF protection is provided by the state parameter, which contains a
cryptographically random nonce and is validated on the token exchange.
The state is single-use (deleted from the pending session map on lookup),
preventing replay attacks. Even without single-use enforcement, replay
would be harmless since the IdP's authorization code can only be
exchanged once.