From ef89f77ea78dafc8f2c5081c6a771df76e2ef5ec Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 3 Nov 2026 00:55:02 -0800 Subject: [PATCH] feat(authentik): register the ABS browser callback per origin ABS builds redirect_uri from the origin of the request, so a second door is a second URI: ABS_EXTRA_BASES (default the guarded vhost) now contributes a callback each, while the mobile-redirect URI stays on the LAN origin - the phone is a non-browser client and cannot solve the guard's challenge, so it is configured with the LAN address. Measured on the live provider: three URIs, grants and scopes unchanged. --- scripts/authentik-audiobookshelf-sso.sh | 38 ++++++++++++++++++------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/scripts/authentik-audiobookshelf-sso.sh b/scripts/authentik-audiobookshelf-sso.sh index caafca7..7dbba89 100755 --- a/scripts/authentik-audiobookshelf-sso.sh +++ b/scripts/authentik-audiobookshelf-sso.sh @@ -11,12 +11,15 @@ # dist-server/server/auth/OidcAuthStrategy.js + objects/settings/ServerSettings.js) # rather than a forum, because each of these is a dead-login trap: # -# * TWO redirect URIs on the provider: /auth/openid/callback (browser) and -# /auth/openid/mobile-redirect (mobile). ABS SWAPS the app's own -# audiobookshelf://oauth for the mobile-redirect URL before the IdP ever sees a -# redirect_uri, so registering the app scheme with authentik — the obvious move — -# is what makes phone logins fail. The app scheme belongs in ABS's -# authOpenIDMobileRedirectURIs list (its built-in default), not here. +# * The browser callback URI /auth/openid/callback for EVERY origin ABS is +# reached at, plus /auth/openid/mobile-redirect on ABS_BASE only. ABS SWAPS +# the app's own audiobookshelf://oauth for the mobile-redirect URL before the IdP +# ever sees a redirect_uri, so registering the app scheme with authentik — the +# obvious move — is what makes phone logins fail. The app scheme belongs in ABS's +# authOpenIDMobileRedirectURIs list (its built-in default), not here. The mobile +# URI is registered on the LAN origin only because the phone is configured with +# the LAN address: it is a non-browser client and cannot solve the guard +# challenge that sits in front of the vhost. # * CARRIES THE BASE PATH. ABS builds redirect_uri as # /auth/openid/callback, and that subfolder is # the ROUTER_BASE_PATH the container is mounted at — /audiobookshelf, which is also the only @@ -72,8 +75,17 @@ # (qa-e2e-admin — tests/e2e/specs/23-audiobookshelf-sso.spec.ts # signs in as this identity, so a re-run restores the fixture) # ABS_BASE ABS URL as the browser sees it, base path included -# (http://192.168.0.235:8000/audiobookshelf — LAN-only by policy: -# no vhost, no reverse proxy in front) +# (http://192.168.0.235:8000/audiobookshelf — the LAN +# origin, reached directly, which is also what the phone +# app is configured with) +# ABS_EXTRA_BASES space-separated additional origins, base path included +# (default: https://abs.franksdank.cloud/audiobookshelf — +# the guarded vhost, dankclaw/Caddyfile). +# ABS builds its redirect_uri from the ORIGIN of the +# request, so each origin the app is served at needs its +# own registered URI — an unregistered origin is a +# redirect_uri_mismatch at the IdP, before any credential. +# Set it empty to register the LAN origin alone. # AUTHENTIK_HOST ssh target (dankclaw) set -euo pipefail @@ -88,6 +100,7 @@ SSO_USER="${ABS_OIDC_USERNAME:-akadmin}" MEMBERS="${ABS_OIDC_USERS:-}" E2E_ADMIN="${ABS_E2E_ADMIN:-qa-e2e-admin}" BASE="${ABS_BASE:-http://192.168.0.235:8000/audiobookshelf}" +EXTRA_BASES="${ABS_EXTRA_BASES:-https://abs.franksdank.cloud/audiobookshelf}" HOST="${AUTHENTIK_HOST:-dankclaw}" # The claim expression: translate group membership into ABS role names. It is stored @@ -110,6 +123,7 @@ from authentik.providers.oauth2.models import ( ADMIN_GROUP, USER_GROUP = "$ADMIN_GROUP", "$USER_GROUP" CLAIM, CLIENT_ID, SECRET, BASE = "$CLAIM", "$CLIENT_ID", "$SECRET", "$BASE" +BASES = [BASE] + """$EXTRA_BASES""".split() SSO_USER, E2E_ADMIN = "$SSO_USER", "$E2E_ADMIN" MEMBERS = """$MEMBERS""".split() EXPR = '''$EXPR''' @@ -143,10 +157,12 @@ _ns = {} exec(_src, _ns) # --- provider -------------------------------------------------------------------- +# One browser callback per origin (LAN address and the guarded vhost), because ABS +# derives redirect_uri from the request origin; the mobile URI stays on the LAN origin. uris = [RedirectURI(matching_mode=RedirectURIMatchingMode.STRICT, - url="%s/auth/openid/callback" % BASE), - RedirectURI(matching_mode=RedirectURIMatchingMode.STRICT, - url="%s/auth/openid/mobile-redirect" % BASE)] + url="%s/auth/openid/callback" % b) for b in BASES] +uris.append(RedirectURI(matching_mode=RedirectURIMatchingMode.STRICT, + url="%s/auth/openid/mobile-redirect" % BASE)) authz_flow = Flow.objects.get(slug="default-provider-authorization-implicit-consent") # Three things beyond the name, each one a silent dead login: the grant-type list (2026.8