[Bug]: OIDC login does not work with Nextcloud #1598

Closed
opened 2026-04-24 23:51:15 +02:00 by adam · 5 comments
Owner

Originally created by @Shadow53 on GitHub (Dec 18, 2023).

Describe the issue

I am using a self-hosted Nextcloud server with the OIDC app for logins to other self-hosted services, currently Miniflux and Audiobookshelf. The server is hosted on NixOS, and I am using NixOS packages and configuration for both Nextcloud and Audiobookshelf.

OIDC logins work fine with Miniflux. With Audiobookshelf, I go through the login flow with Nextcloud, then get redirected back to the Audiobookshelf login page with a message "Unauthorized".

I did some digging, and I think the problem is due to openid-client Client's defaults combined with the limitations of the Nextcloud OIDC app. In short, the Nextcloud OIDC app does not support Basic authentication, and that is the default for openid-client.

I believe the fix is to support setting token_endpoint_auth_method to either "client_secret_jwt" or "private_key_jwt".

Regarding logs:

  • I cannot find any logs that seem relevant in the Audiobookshelf logs beyond the line Dec 17 22:59:02 nixos audiobookshelf[937502]: [2023-12-17 22:59:02.328] DEBUG: [Auth] Set oidc redirect_uri=https://my.audiobookshelf.site/auth/openid/callback (Auth.js:287)
  • The Nextcloud logs are confusing at best. I compared the logs for Audiobookshelf and Miniflux and the only difference seems to be that the OIDC process succeeds for Miniflux. I suspect this is related to the limitation in the OIDC app where Basic just isn't supported.

Steps to reproduce the issue

  1. Set up a Nextcloud instance with the OIDC app
  2. Set up an Audiobookshelf instance
  3. Set up OIDC authentication following the Audiobookshelf guide on the matter, using the Nextcloud instance as a provider.
  4. Attempt to log in to Audiobookshelf using OIDC

Audiobookshelf version

v2.6.0

How are you running audiobookshelf?

Other

Originally created by @Shadow53 on GitHub (Dec 18, 2023). ### Describe the issue I am using a self-hosted Nextcloud server with the [OIDC app](https://github.com/H2CK/oidc) for logins to other self-hosted services, currently Miniflux and Audiobookshelf. The server is hosted on NixOS, and I am using NixOS packages and configuration for both Nextcloud and Audiobookshelf. OIDC logins work fine with Miniflux. With Audiobookshelf, I go through the login flow with Nextcloud, then get redirected back to the Audiobookshelf login page with a message "Unauthorized". I did some digging, and I think the problem is due to [openid-client Client's defaults](https://github.com/panva/node-openid-client/blob/main/docs/README.md#new-clientmetadata-jwks-options) combined with the [limitations of the Nextcloud OIDC app](https://github.com/H2CK/oidc/wiki/User-Documentation#limitations). In short, the Nextcloud OIDC app does not support Basic authentication, and that is the default for openid-client. I believe the fix is to support setting `token_endpoint_auth_method` to either `"client_secret_jwt"` or `"private_key_jwt"`. Regarding logs: - I cannot find any logs that seem relevant in the Audiobookshelf logs beyond the line `Dec 17 22:59:02 nixos audiobookshelf[937502]: [2023-12-17 22:59:02.328] DEBUG: [Auth] Set oidc redirect_uri=https://my.audiobookshelf.site/auth/openid/callback (Auth.js:287)` - The Nextcloud logs are confusing at best. I compared the logs for Audiobookshelf and Miniflux and the only difference seems to be that the OIDC process succeeds for Miniflux. I suspect this is related to the limitation in the OIDC app where Basic just isn't supported. ### Steps to reproduce the issue 1. Set up a Nextcloud instance with the OIDC app 2. Set up an Audiobookshelf instance 3. Set up OIDC authentication following the Audiobookshelf guide on the matter, using the Nextcloud instance as a provider. 4. Attempt to log in to Audiobookshelf using OIDC ### Audiobookshelf version v2.6.0 ### How are you running audiobookshelf? Other
adam added the bug label 2026-04-24 23:51:15 +02:00
adam closed this issue 2026-04-24 23:51:15 +02:00
Author
Owner

@Sapd commented on GitHub (Dec 19, 2023):

I cannot find any logs that seem relevant in the Audiobookshelf logs beyond the line

You can try the latest :edge image which should give you more detailed logs

@Sapd commented on GitHub (Dec 19, 2023): > I cannot find any logs that seem relevant in the Audiobookshelf logs beyond the line You can try the latest `:edge` image which should give you more detailed logs
Author
Owner

@Sapd commented on GitHub (Jan 27, 2024):

Can you try this image: darnst/audiobookshelf:client_secret_post
I hardcoded client_secret_post there. If it works I might include it in a PR

@Sapd commented on GitHub (Jan 27, 2024): Can you try this image: `darnst/audiobookshelf:client_secret_post` I hardcoded client_secret_post there. If it works I might include it in a PR
Author
Owner

@DavidKarlas commented on GitHub (Jun 19, 2025):

FYI: I tried that image and it worked for me with Authelia(I had token_endpoint_auth_method: client_secret_post set in configuration because I copied from elsewhere)

@DavidKarlas commented on GitHub (Jun 19, 2025): FYI: I tried that image and it worked for me with Authelia(I had token_endpoint_auth_method: client_secret_post set in configuration because I copied from elsewhere)
Author
Owner

@Vitadek commented on GitHub (Apr 18, 2026):

Since the original ticket is specifically Nextcloud as the oidc provider, I figured I should comment what I did, because I had serious trouble with this. There's a specific use case on why I wanted Nextcloud to be my oidc provider. As setting up an authentik, authelia, or keycloak instance would be a hassle if you only want to extend your Nextcloud auth to one more instance.

I hand jammed a lot of this with Audiobookshelf and the Nextcloud php and used Claude to help me out on some of the manual nextcloud php overrides and sqlite3 manual configurations to force some things through the pipeline. I told it to summarize what I did.

Why Audiobookshelf is Inherently Incompatible with Nextcloud OIDC (Without Workarounds)
Expanding on this issue — after extensive debugging on my own setup, I've confirmed the incompatibility is real and sits at the intersection of two design decisions that each make sense in isolation but collide at the token endpoint.
The Core Conflict
Audiobookshelf uses openid-client (node), which defaults to token_endpoint_auth_method: "client_secret_basic" and doesn't expose a way to override it. Client credentials get sent as Authorization: Basic base64(client_id:client_secret).
Nextcloud core (lib/base.php → handleLogin → tryBasicAuthLogin) intercepts ANY request containing an Authorization: Basic header, regardless of route. It tries to log in the credentials as a Nextcloud user, fails (the client_id isn't a user), and returns 401 — the OIDC app never runs.
The Nextcloud OIDC Identity Provider app (H2CK/oidc) does support client_secret_post, but since openid-client sends both the Basic header AND body params simultaneously, Nextcloud's core interception wins every time.
Why Simple Toggles Would Fix This
Either side adding a boolean config makes the whole problem go away:
Option A — Audiobookshelf side (preferred, cleanest)
Expose token_endpoint_auth_method as a config field. openid-client already supports all the standard values — it's literally one line passed to the Client constructor:

How-To: Get Audiobookshelf OIDC Working with Nextcloud (Workaround)

Since ABS doesn't expose token_endpoint_auth_method, here's what I had to do to make it work. This involves modifying Nextcloud core, Apache, and nginx. Use at your own risk — the Nextcloud core patch will be overwritten on updates.

Environment Tested

  • Nextcloud: nextcloud:33-apache (Docker container named docker-app-1)
  • OIDC Identity Provider app: H2CK/oidc v1.16.5 (install from Nextcloud App Store — this is the provider app, not user_oidc)
  • Audiobookshelf: v2.33.1 (bare metal on LXC)
  • nginx reverse proxy on a separate host

Step 1 — Install the OIDC Identity Provider App

In Nextcloud admin: Apps → search "OpenID Connect" → install the one by H2CK. Confirm:

docker exec docker-app-1 php occ app:list | grep oidc
# Should show: - oidc: 1.16.5

Step 2 — Create the OIDC Client

Use opaque tokens. JWT tokens in this app version cause Could not find provided bearer token at the userinfo endpoint.

docker exec docker-app-1 php occ oidc:create \
  --client_id "YOUR_CLIENT_ID_32_TO_64_CHARS" \
  --client_secret "YOUR_CLIENT_SECRET_32_TO_64_CHARS" \
  --type confidential \
  --flow code \
  --token_type opaque \
  "Audiobookshelf" \
  "https://abs.yourdomain.com/auth/openid/callback" \
  "https://abs.yourdomain.com/auth/openid/mobile-redirect"

If ABS is served from a subpath (e.g., /audiobookshelf), add that redirect URI too:

"https://abs.yourdomain.com/audiobookshelf/auth/openid/callback"

Step 3 — Patch Nextcloud Core (lib/base.php)

This is the critical fix. It tells Nextcloud core to skip Basic auth interception on the OIDC token and userinfo endpoints.

Find the line (~1242):

docker exec docker-app-1 grep -n "tryBasicAuthLogin" /var/www/html/lib/base.php

You're looking for:

if ($userSession->tryBasicAuthLogin($request, Server::get(IThrottler::class))) {

Apply the patch:

docker exec docker-app-1 sed -i 's|if ($userSession->tryBasicAuthLogin($request, Server::get(IThrottler::class)))|if (!str_contains($request->getRequestUri(), "/apps/oidc/token") \&\& !str_contains($request->getRequestUri(), "/apps/oidc/userinfo") \&\& $userSession->tryBasicAuthLogin($request, Server::get(IThrottler::class)))|' /var/www/html/lib/base.php

Verify:

docker exec docker-app-1 sed -n '1240,1246p' /var/www/html/lib/base.php

The line should now read:

if (!str_contains($request->getRequestUri(), "/apps/oidc/token") && !str_contains($request->getRequestUri(), "/apps/oidc/userinfo") && $userSession->tryBasicAuthLogin($request, Server::get(IThrottler::class))) {

⚠️ This patch gets wiped on every Nextcloud update. Script it into a post-update hook or document it for yourself.

Step 4 — Apache: Enable CGIPassAuth

Apache strips the Authorization header from PHP's $_SERVER by default. The OIDC app's userinfo endpoint needs it for the Bearer token.

docker exec docker-app-1 bash -c 'cat > /etc/apache2/conf-available/oidc-auth.conf << EOF
<Directory /var/www/html>
    CGIPassAuth on
</Directory>
EOF
a2enconf oidc-auth && apachectl graceful'

Step 5 — nginx: Remove Duplicate Authorization Headers

If you have proxy_pass_request_headers on; AND proxy_set_header Authorization $http_authorization; in the same block, you get duplicate Authorization headers, which Apache/PHP can't parse correctly.

Check current config:

grep -E "Authorization|pass_request_headers|pass_header" /etc/nginx/conf.d/cloud.yourdomain.com.conf

Fix: Remove all explicit Authorization header lines. nginx passes the client's Authorization header through automatically.

Final nginx server block should look like this for the Nextcloud host:

server {
    listen 443 ssl;
    server_name cloud.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/cloud.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/cloud.yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://NEXTCLOUD_BACKEND_IP:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        client_max_body_size 10G;
    }
}

Do NOT add:

  • proxy_set_header Authorization $http_authorization;
  • proxy_pass_header Authorization;
  • proxy_pass_request_headers on;

Reload nginx:

nginx -t && nginx -s reload

Step 6 — Audiobookshelf Configuration

In ABS: Settings → Authentication → check "OpenID Connect Authentication".

Set Issuer URL to https://cloud.yourdomain.com and click Auto-populate. All URLs should fill in. Then set:

  • Client ID: matches what you used in occ oidc:create
  • Client Secret: matches what you used in occ oidc:create
  • Token Signing Algorithm: RS256
  • Match existing users by: username ← this is the important one if you want SSO to map to existing local ABS accounts
  • Auto Register: OFF (unless you want new users auto-created)
  • Auto Launch: your preference
  • Subfolder for Redirect URIs: /audiobookshelf if ABS is behind a subpath, otherwise blank

Mapping OIDC logins to existing ABS users (no duplicate accounts)

With "Match existing users by" set to username, the preferred_username claim from Nextcloud will match against your existing ABS username. The user logs in via Nextcloud, gets forwarded to their existing ABS account — no new account created, same permissions and history as their local login. Leave "Auto Register" off to prevent accidental account creation on username mismatches.

Step 7 — Test

Reset the brute force throttle (in case you've accumulated failed attempts):

docker exec docker-app-1 php occ security:bruteforce:reset YOUR_ABS_SERVER_IP

Click the login button in ABS. You should get redirected to Nextcloud, log in, and get sent back to ABS logged in as your existing user.

If it fails, watch:

docker exec docker-app-1 tail -f /var/www/html/data/nextcloud.log

and

journalctl -fu audiobookshelf

How to Verify Each Step Worked

Symptom Step that fixes it
Login failed: 'CLIENT_ID' in Nextcloud log, token endpoint returns 401 with 14-byte body Step 3 (base.php patch)
Token endpoint returns 200 but userinfo returns 400 No bearer token found Step 4 (CGIPassAuth)
Userinfo returns 400 Could not find provided bearer token despite Bearer header present Step 5 (duplicate header removal) AND Step 2 (opaque tokens, not JWT)
OPError: expected 200 OK, got: 401 Unauthorized in ABS logs Usually Step 3
OPError: invalid_client (Client authentication failed.) in ABS logs base.php patch partially applied — check the line again

This entire workaround is only necessary because ABS doesn't expose token_endpoint_auth_method. Proper upstream fix to ABS would reduce this whole guide to a single config field.

Ironically, the duct tape workaround took more time than it would be setting up authelia or authentik. But it's about the principle lol

@Vitadek commented on GitHub (Apr 18, 2026): Since the original ticket is specifically _Nextcloud_ as the oidc provider, I figured I should comment what I did, because I had serious trouble with this. There's a specific use case on why I wanted Nextcloud to be my oidc provider. As setting up an authentik, authelia, or keycloak instance would be a hassle if you only want to extend your Nextcloud auth to one more instance. I hand jammed a lot of this with Audiobookshelf and the Nextcloud php and used Claude to help me out on some of the manual nextcloud php overrides and sqlite3 manual configurations to force some things through the pipeline. I told it to summarize what I did. Why Audiobookshelf is Inherently Incompatible with Nextcloud OIDC (Without Workarounds) Expanding on this issue — after extensive debugging on my own setup, I've confirmed the incompatibility is real and sits at the intersection of two design decisions that each make sense in isolation but collide at the token endpoint. The Core Conflict Audiobookshelf uses openid-client (node), which defaults to token_endpoint_auth_method: "client_secret_basic" and doesn't expose a way to override it. Client credentials get sent as Authorization: Basic base64(client_id:client_secret). Nextcloud core (lib/base.php → handleLogin → tryBasicAuthLogin) intercepts ANY request containing an Authorization: Basic header, regardless of route. It tries to log in the credentials as a Nextcloud user, fails (the client_id isn't a user), and returns 401 — the OIDC app never runs. The Nextcloud OIDC Identity Provider app (H2CK/oidc) does support client_secret_post, but since openid-client sends both the Basic header AND body params simultaneously, Nextcloud's core interception wins every time. Why Simple Toggles Would Fix This Either side adding a boolean config makes the whole problem go away: Option A — Audiobookshelf side (preferred, cleanest) Expose token_endpoint_auth_method as a config field. openid-client already supports all the standard values — it's literally one line passed to the Client constructor: # How-To: Get Audiobookshelf OIDC Working with Nextcloud (Workaround) Since ABS doesn't expose `token_endpoint_auth_method`, here's what I had to do to make it work. This involves modifying Nextcloud core, Apache, and nginx. **Use at your own risk — the Nextcloud core patch will be overwritten on updates.** ## Environment Tested - **Nextcloud:** `nextcloud:33-apache` (Docker container named `docker-app-1`) - **OIDC Identity Provider app:** H2CK/oidc v1.16.5 (install from Nextcloud App Store — this is the *provider* app, not `user_oidc`) - **Audiobookshelf:** v2.33.1 (bare metal on LXC) - **nginx** reverse proxy on a separate host --- ## Step 1 — Install the OIDC Identity Provider App In Nextcloud admin: Apps → search "OpenID Connect" → install the one by H2CK. Confirm: ```bash docker exec docker-app-1 php occ app:list | grep oidc # Should show: - oidc: 1.16.5 ``` ## Step 2 — Create the OIDC Client Use **opaque** tokens. JWT tokens in this app version cause `Could not find provided bearer token` at the userinfo endpoint. ```bash docker exec docker-app-1 php occ oidc:create \ --client_id "YOUR_CLIENT_ID_32_TO_64_CHARS" \ --client_secret "YOUR_CLIENT_SECRET_32_TO_64_CHARS" \ --type confidential \ --flow code \ --token_type opaque \ "Audiobookshelf" \ "https://abs.yourdomain.com/auth/openid/callback" \ "https://abs.yourdomain.com/auth/openid/mobile-redirect" ``` If ABS is served from a subpath (e.g., `/audiobookshelf`), add that redirect URI too: ``` "https://abs.yourdomain.com/audiobookshelf/auth/openid/callback" ``` ## Step 3 — Patch Nextcloud Core (`lib/base.php`) **This is the critical fix.** It tells Nextcloud core to skip Basic auth interception on the OIDC token and userinfo endpoints. Find the line (~1242): ```bash docker exec docker-app-1 grep -n "tryBasicAuthLogin" /var/www/html/lib/base.php ``` You're looking for: ```php if ($userSession->tryBasicAuthLogin($request, Server::get(IThrottler::class))) { ``` Apply the patch: ```bash docker exec docker-app-1 sed -i 's|if ($userSession->tryBasicAuthLogin($request, Server::get(IThrottler::class)))|if (!str_contains($request->getRequestUri(), "/apps/oidc/token") \&\& !str_contains($request->getRequestUri(), "/apps/oidc/userinfo") \&\& $userSession->tryBasicAuthLogin($request, Server::get(IThrottler::class)))|' /var/www/html/lib/base.php ``` Verify: ```bash docker exec docker-app-1 sed -n '1240,1246p' /var/www/html/lib/base.php ``` The line should now read: ```php if (!str_contains($request->getRequestUri(), "/apps/oidc/token") && !str_contains($request->getRequestUri(), "/apps/oidc/userinfo") && $userSession->tryBasicAuthLogin($request, Server::get(IThrottler::class))) { ``` > ⚠️ **This patch gets wiped on every Nextcloud update.** Script it into a post-update hook or document it for yourself. ## Step 4 — Apache: Enable `CGIPassAuth` Apache strips the Authorization header from PHP's `$_SERVER` by default. The OIDC app's userinfo endpoint needs it for the Bearer token. ```bash docker exec docker-app-1 bash -c 'cat > /etc/apache2/conf-available/oidc-auth.conf << EOF <Directory /var/www/html> CGIPassAuth on </Directory> EOF a2enconf oidc-auth && apachectl graceful' ``` ## Step 5 — nginx: Remove Duplicate Authorization Headers If you have `proxy_pass_request_headers on;` AND `proxy_set_header Authorization $http_authorization;` in the same block, you get duplicate `Authorization` headers, which Apache/PHP can't parse correctly. **Check current config:** ```bash grep -E "Authorization|pass_request_headers|pass_header" /etc/nginx/conf.d/cloud.yourdomain.com.conf ``` **Fix:** Remove all explicit Authorization header lines. nginx passes the client's Authorization header through automatically. Final nginx server block should look like this for the Nextcloud host: ```nginx server { listen 443 ssl; server_name cloud.yourdomain.com; ssl_certificate /etc/letsencrypt/live/cloud.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/cloud.yourdomain.com/privkey.pem; location / { proxy_pass http://NEXTCLOUD_BACKEND_IP:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; client_max_body_size 10G; } } ``` **Do NOT add:** - `proxy_set_header Authorization $http_authorization;` - `proxy_pass_header Authorization;` - `proxy_pass_request_headers on;` Reload nginx: ```bash nginx -t && nginx -s reload ``` ## Step 6 — Audiobookshelf Configuration In ABS: Settings → Authentication → check "OpenID Connect Authentication". Set **Issuer URL** to `https://cloud.yourdomain.com` and click **Auto-populate**. All URLs should fill in. Then set: - **Client ID:** matches what you used in `occ oidc:create` - **Client Secret:** matches what you used in `occ oidc:create` - **Token Signing Algorithm:** `RS256` - **Match existing users by:** `username` ← this is the important one if you want SSO to map to existing local ABS accounts - **Auto Register:** OFF (unless you want new users auto-created) - **Auto Launch:** your preference - **Subfolder for Redirect URIs:** `/audiobookshelf` if ABS is behind a subpath, otherwise blank ### Mapping OIDC logins to existing ABS users (no duplicate accounts) With "Match existing users by" set to `username`, the `preferred_username` claim from Nextcloud will match against your existing ABS username. The user logs in via Nextcloud, gets forwarded to their existing ABS account — no new account created, same permissions and history as their local login. Leave "Auto Register" off to prevent accidental account creation on username mismatches. ## Step 7 — Test Reset the brute force throttle (in case you've accumulated failed attempts): ```bash docker exec docker-app-1 php occ security:bruteforce:reset YOUR_ABS_SERVER_IP ``` Click the login button in ABS. You should get redirected to Nextcloud, log in, and get sent back to ABS logged in as your existing user. If it fails, watch: ```bash docker exec docker-app-1 tail -f /var/www/html/data/nextcloud.log ``` and ```bash journalctl -fu audiobookshelf ``` --- ## How to Verify Each Step Worked | Symptom | Step that fixes it | |---|---| | `Login failed: 'CLIENT_ID'` in Nextcloud log, token endpoint returns 401 with 14-byte body | Step 3 (base.php patch) | | Token endpoint returns 200 but userinfo returns 400 `No bearer token found` | Step 4 (CGIPassAuth) | | Userinfo returns 400 `Could not find provided bearer token` despite Bearer header present | Step 5 (duplicate header removal) AND Step 2 (opaque tokens, not JWT) | | `OPError: expected 200 OK, got: 401 Unauthorized` in ABS logs | Usually Step 3 | | `OPError: invalid_client (Client authentication failed.)` in ABS logs | base.php patch partially applied — check the line again | This entire workaround is only necessary because ABS doesn't expose `token_endpoint_auth_method`. Proper upstream fix to ABS would reduce this whole guide to a single config field. Ironically, the duct tape workaround took more time than it would be setting up authelia or authentik. But _it's about the principle_ lol
Author
Owner

@Sapd commented on GitHub (Apr 18, 2026):

It is quite easy to fix. I can do so after https://github.com/advplyr/audiobookshelf/pull/5031 is merged. But this will take a while bc of the react rewrite afaik.

@Sapd commented on GitHub (Apr 18, 2026): It is quite easy to fix. I can do so after https://github.com/advplyr/audiobookshelf/pull/5031 is merged. But this will take a while bc of the react rewrite afaik.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/audiobookshelf#1598