swarm-authelia: let a client declare its token-endpoint auth method

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.
This commit is contained in:
atlas 2026-08-16 17:48:52 +02:00
commit 8c51e37804
2 changed files with 39 additions and 0 deletions

View file

@ -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";
}
];

View file

@ -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.
'';
};
};
}
);