fix(authentik): register the mobile OIDC redirect on every ABS origin

The mobile-redirect URI was registered on the LAN origin only, because the phone
had to be configured with the LAN address while the vhost was guarded. Now that the
app rides the public name it sends the public mobile-redirect URI, and the RP's
STRICT matching answers an unregistered one with "Redirect URI Error" — a phone
locked out at the last step of a login that looked healthy. Both callback URIs are
now emitted for every entry in BASES.
This commit is contained in:
dank committed 2026-11-03 01:54:08 -08:00
1 parent 2a48751283
commit aa57c1a894
1 file changed
+17 -15
+17 -15
View File
@@ -11,15 +11,17 @@
# dist-server/server/auth/OidcAuthStrategy.js + objects/settings/ServerSettings.js) # dist-server/server/auth/OidcAuthStrategy.js + objects/settings/ServerSettings.js)
# rather than a forum, because each of these is a dead-login trap: # rather than a forum, because each of these is a dead-login trap:
# #
# * The browser callback URI <base>/auth/openid/callback for EVERY origin ABS is # * Two URIs per origin ABS is reached at — <base>/auth/openid/callback and
# reached at, plus <base>/auth/openid/mobile-redirect on ABS_BASE only. ABS SWAPS # <base>/auth/openid/mobile-redirect — for EVERY entry in BASES (the LAN address
# the app's own audiobookshelf://oauth for the mobile-redirect URL before the IdP # and the public vhost). ABS SWAPS the app's own audiobookshelf://oauth for the
# ever sees a redirect_uri, so registering the app scheme with authentik — the # mobile-redirect URL before the IdP ever sees a redirect_uri, so registering the
# obvious move — is what makes phone logins fail. The app scheme belongs in ABS's # app scheme with authentik — the obvious move — is what makes phone logins fail.
# authOpenIDMobileRedirectURIs list (its built-in default), not here. The mobile # The app scheme belongs in ABS's authOpenIDMobileRedirectURIs list (its built-in
# URI is registered on the LAN origin only because the phone is configured with # default), not here. The mobile URI exists on every origin because the app sends
# the LAN address: it is a non-browser client and cannot solve the guard # the one belonging to the door it was configured with: the vhost started serving
# challenge that sits in front of the vhost. # non-browser clients on 2026-11-03 (it left the guard chain), so a phone pointed
# at the public name sends the public mobile-redirect URI, and STRICT matching
# answers an unregistered one with "Redirect URI Error".
# * <base> CARRIES THE BASE PATH. ABS builds redirect_uri as # * <base> CARRIES THE BASE PATH. ABS builds redirect_uri as
# <origin><authOpenIDSubfolderForRedirectURLs>/auth/openid/callback, and that subfolder is # <origin><authOpenIDSubfolderForRedirectURLs>/auth/openid/callback, and that subfolder is
# the ROUTER_BASE_PATH the container is mounted at — /audiobookshelf, which is also the only # the ROUTER_BASE_PATH the container is mounted at — /audiobookshelf, which is also the only
@@ -157,12 +159,12 @@ _ns = {}
exec(_src, _ns) exec(_src, _ns)
# --- provider -------------------------------------------------------------------- # --- provider --------------------------------------------------------------------
# One browser callback per origin (LAN address and the guarded vhost), because ABS # One callback pair per origin (LAN address and the public vhost): ABS derives the
# derives redirect_uri from the request origin; the mobile URI stays on the LAN origin. # browser callback from the request origin and the mobile callback from the door the
uris = [RedirectURI(matching_mode=RedirectURIMatchingMode.STRICT, # app was configured with, so each origin needs both or that door cannot sign in.
url="%s/auth/openid/callback" % b) for b in BASES] uris = [RedirectURI(matching_mode=RedirectURIMatchingMode.STRICT, url="%s%s" % (b, p))
uris.append(RedirectURI(matching_mode=RedirectURIMatchingMode.STRICT, for b in BASES
url="%s/auth/openid/mobile-redirect" % BASE)) for p in ("/auth/openid/callback", "/auth/openid/mobile-redirect")]
authz_flow = Flow.objects.get(slug="default-provider-authorization-implicit-consent") 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 # Three things beyond the name, each one a silent dead login: the grant-type list (2026.8