From 8c51e37804ef97d22cd158c10ab73dcf12f0965c Mon Sep 17 00:00:00 2001 From: atlas Date: Sun, 16 Aug 2026 17:48:52 +0200 Subject: [PATCH] swarm-authelia: let a client declare its token-endpoint auth method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tuwunel authenticates at the token endpoint with the secret in the POST body. Authelia enforces the method a client is REGISTERED with rather than accepting whichever one arrives, and its default is client_secret_basic — so the matrix login completed, consent was granted, and the very last hop failed: Client authentication failed ... The request was determined to be using token_endpoint_auth_method client_secret_post, however the OAuth 2.0 client registration does not allow this method. The failure names neither the secret nor the redirect, and it lands three layers from its cause, which is why it read as a credential problem. Adds a per-client tokenEndpointAuthMethod, null by default so every existing client keeps authelia default (forgejo authenticates with basic and is unaffected), and sets client_secret_post on the matrix client only. --- nix/host-modules/hive-matrix.nix | 7 +++++++ nix/host-modules/swarm-authelia.nix | 32 +++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/nix/host-modules/hive-matrix.nix b/nix/host-modules/hive-matrix.nix index b532c5c6..dbcc34ae 100644 --- a/nix/host-modules/hive-matrix.nix +++ b/nix/host-modules/hive-matrix.nix @@ -665,6 +665,13 @@ in id = cfg.sso.clientId; description = "HyperHive matrix"; redirectUris = [ ssoCallbackUrl ]; + # tuwunel authenticates at the token endpoint by putting the + # secret in the POST body. Authelia enforces the *registered* + # method rather than accepting whichever one arrives, and its + # default is `client_secret_basic` — so without this the browser + # flow completes, consent is granted, and the very last hop fails + # with a 401 that names neither the secret nor the redirect. + tokenEndpointAuthMethod = "client_secret_post"; } ]; diff --git a/nix/host-modules/swarm-authelia.nix b/nix/host-modules/swarm-authelia.nix index 4aa98003..ca1a72e9 100644 --- a/nix/host-modules/swarm-authelia.nix +++ b/nix/host-modules/swarm-authelia.nix @@ -144,6 +144,9 @@ let printf -- " client_secret: '%s'\n" "$(cat ${lib.escapeShellArg "${clientsDir}/${c.id}.digest"})" printf -- ' authorization_policy: one_factor\n' '' + + lib.optionalString (c.tokenEndpointAuthMethod != null) '' + printf -- ' token_endpoint_auth_method: %s\n' ${lib.escapeShellArg c.tokenEndpointAuthMethod} + '' + ( if c.kind == "machine" then '' @@ -417,6 +420,35 @@ in rather than silently ignored. ''; }; + + tokenEndpointAuthMethod = lib.mkOption { + type = lib.types.nullOr ( + lib.types.enum [ + "client_secret_basic" + "client_secret_post" + "client_secret_jwt" + "private_key_jwt" + "none" + ] + ); + default = null; + example = "client_secret_post"; + description = '' + How this client proves its identity at the token + endpoint. `null` leaves authelia on its own default + (`client_secret_basic`), which is what every client that + does not say otherwise gets. + + Set it when the relying party's implementation differs, + because authelia enforces the registered method rather + than accepting whatever arrives. tuwunel sends + `client_secret_post`, and against a client registered for + basic the result is a 401 from `/api/oidc/token` **after + a successful consent** — the login looks like it worked + right up to the last hop, and neither the redirect nor + the secret is at fault. + ''; + }; }; } );