opencloud icon indicating copy to clipboard operation
opencloud copied to clipboard

[Bug] OpenCloud OIDC Login Fails Due to Malformed Access Token (Opaque Token Sent Instead of JWT)

Open Jean28518 opened this issue 3 months ago • 3 comments

OIDC authentication fails after successful callback because the OpenCloud frontend (or an intermediate layer) sends a simple, opaque string in the Authorization: Bearer header instead of the expected JSON Web Token (JWT). The proxy service rejects this opaque token because it is missing the segment separators (dots), leading to an immediate 401 Unauthorized error on subsequent API calls.

Steps to reproduce

  1. Navigate to the OpenCloud application URL.
  2. Be redirected to the external Identity Provider (IDP), which is in my case a Django-OIDC-Provider instance.
  3. Successfully authenticate with the IDP.
  4. Be redirected back to OpenCloud, completing the OIDC callback successfully (as indicated by the 200 status for /web-oidc-callback).
  5. Observe that protected API endpoints (e.g., /api/v0/settings/roles-list, /graph/v1.0/me) immediately fail with a 401 Unauthorized status.

Expected behavior

After a successful OIDC callback, the OpenCloud frontend should use the full JWT Access Token received from the IDP to construct the Authorization: Bearer <JWT> header for all protected subsequent API calls, leading to a successful login.

Actual behavior

For protected API calls (e.g., /api/v0/settings/roles-list), the Authorization request header is observed to contain an opaque, single-segment string: Authorization: Bearer 568bc48e88ab4500ad830197efed93ac (my example from log analysis).

The proxy service then logs the following error and rejects the request:

{"level":"error","service":"proxy","error":"failed to verify access token: token is malformed: token contains an invalid number of segments", ... ,"message":"failed to authenticate the request"}

This indicates the client is sending a token that is not a three-segment JWT.

Setup

The external IDP being used is django-oidc-provider. The deployment uses the following composition and environment variables:

<details> <p>

COMPOSE_FILE=docker-compose.yml:weboffice/collabora.yml:external-proxy/opencloud.yml:external-proxy/collabora.yml:idm/external-idp.yml

OC_OIDC_ISSUER=[YOUR_IDP_URL]
OC_EXCLUDE_RUN_SERVICES=idp
PROXY_OIDC_REWRITE_WELLKNOWN=true
PROXY_USER_OIDC_CLAIM=preferred_username
PROXY_USER_CS3_CLAIM=username
PROXY_AUTOPROVISION_ACCOUNTS=true
GRAPH_ASSIGN_DEFAULT_USER_ROLE=false
GRAPH_USERNAME_MATCH=none
PROXY_ROLE_ASSIGNMENT_DRIVER=default
WEB_OPTION_ACCOUNT_EDIT_LINK_HREF=[YOUR_IDP_URL]/idm/user_settings
IDP_ISSUER_URL=[YOUR_IDP_URL]
IDP_DOMAIN=[YOUR_IDP_URL_DOMAIN]
IDP_ACCOUNT_URL=[YOUR_IDP_URL]/idm/user_settings

The OIDC Provider Setting:

{
        'client_type': 'public',
        'client_secret': '',
        'client_id': 'web',
        'response_types': ['1', '2', '3'],
        'redirect_uris': 'https://opencloud.DOMAIN/\nhttps://opencloud.DOMAIN/oidc-callback.html\nhttps://opencloud.DOMAIN/oidc-silent-redirect.html',
        'jwt_alg': 'HS256',
        'require_consent': False,
        'reuse_consent': False,
    }

Jwt_alg RS256 didn't change anything. Allowed response types are code, id_token and id_token token.

Log Messages that might help:

opencloud-1       | {"level":"info","service":"proxy","proto":"HTTP/1.1","request-id":"39b897c8d7d5/DawOsaazSK-000031","traceid":"61a9ffa9b204bdc792f2ebd12fdef846","remote-addr":"2a02:8084:4e60:d180:da5e:d3ff:fe8c:eb2e","method":"GET","status":200,"path":"/oidc-callback.html","duration":0.852317,"bytes":380,"time":"2025-09-29T10:51:40Z","line":"github.com/opencloud-eu/opencloud/services/proxy/pkg/middleware/accesslog.go:34","message":"access-log"}
opencloud-1       | {"level":"info","service":"proxy","proto":"HTTP/1.1","request-id":"39b897c8d7d5/DawOsaazSK-000033","traceid":"88adc427755467773f9c6c20dba58072","remote-addr":"2a02:8084:4e60:d180:da5e:d3ff:fe8c:eb2e","method":"GET","status":200,"path":"/web-oidc-callback","duration":1.798202,"bytes":3290,"time":"2025-09-29T10:51:40Z","line":"github.com/opencloud-eu/opencloud/services/proxy/pkg/middleware/accesslog.go:34","message":"access-log"}
opencloud-1       | {"level":"info","service":"proxy","proto":"HTTP/1.1","request-id":"43401982-6be7-4527-9181-c5cbbe022a07","traceid":"a36aa0693f826754013068ecd5579c30","remote-addr":"2a02:8084:4e60:d180:da5e:d3ff:fe8c:eb2e","method":"GET","status":200,"path":"/config.json","duration":0.794987,"bytes":355,"time":"2025-09-29T10:51:40Z","line":"github.com/opencloud-eu/opencloud/services/proxy/pkg/middleware/accesslog.go:34","message":"access-log"}
opencloud-1       | {"level":"info","service":"proxy","proto":"HTTP/1.1","request-id":"39b897c8d7d5/DawOsaazSK-000035","traceid":"174e5faa3a76ecb0d80721bfac99a2fd","remote-addr":"2a02:8084:4e60:d180:da5e:d3ff:fe8c:eb2e","method":"GET","status":200,"path":"/favicon.ico","duration":3.918533,"bytes":3290,"time":"2025-09-29T10:51:40Z","line":"github.com/opencloud-eu/opencloud/services/proxy/pkg/middleware/accesslog.go:34","message":"access-log"}
opencloud-1       | {"level":"info","service":"proxy","proto":"HTTP/1.1","request-id":"d34849bf-89ac-4b3e-8b8d-c304a86dd160","traceid":"231d6a4c88967ebac258b0faf6014b3b","remote-addr":"2a02:8084:4e60:d180:da5e:d3ff:fe8c:eb2e","method":"GET","status":200,"path":"/themes/opencloud/theme.json","duration":1.450314,"bytes":5493,"time":"2025-09-29T10:51:40Z","line":"github.com/opencloud-eu/opencloud/services/proxy/pkg/middleware/accesslog.go:34","message":"access-log"}
opencloud-1       | {"level":"info","service":"frontend","pkg":"rhttp","traceid":"d6accb1f2af35b4afdde897f76b829f4","time":"2025-09-29T10:51:40Z","line":"github.com/opencloud-eu/reva/[email protected]/internal/http/interceptors/auth/auth.go:195","message":"skipping auth check for: /app/list"}
opencloud-1       | {"level":"warn","service":"frontend","pkg":"rhttp","traceid":"d6accb1f2af35b4afdde897f76b829f4","time":"2025-09-29T10:51:40Z","line":"github.com/opencloud-eu/reva/[email protected]/internal/http/interceptors/auth/auth.go:248","message":"core access token not set"}
opencloud-1       | {"level":"info","service":"proxy","proto":"HTTP/1.1","request-id":"db64357f-d95d-41d6-9800-946a7f299d6e","traceid":"3b9ca30a77461c09e608b779c9e6a943","remote-addr":"2a02:8084:4e60:d180:da5e:d3ff:fe8c:eb2e","method":"GET","status":200,"path":"/app/list","duration":3.289107,"bytes":20417,"time":"2025-09-29T10:51:40Z","line":"github.com/opencloud-eu/opencloud/services/proxy/pkg/middleware/accesslog.go:34","message":"access-log"}
opencloud-1       | {"level":"error","service":"proxy","error":"failed to verify access token: token is malformed: token contains an invalid number of segments","authenticator":"oidc","path":"/ocs/v1.php/cloud/capabilities","user_agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36","client.address":"2a02:8084:4e60:d180:da5e:d3ff:fe8c:eb2e","network.peer.address":"","network.peer.port":"","time":"2025-09-29T10:51:41Z","line":"github.com/opencloud-eu/opencloud/services/proxy/pkg/middleware/oidc_auth.go:198","message":"failed to authenticate the request"}
opencloud-1       | {"level":"info","service":"frontend","pkg":"rhttp","traceid":"37e7a5cd10ededc1314aabe3df23eb5f","time":"2025-09-29T10:51:41Z","line":"github.com/opencloud-eu/reva/[email protected]/internal/http/interceptors/auth/auth.go:195","message":"skipping auth check for: /ocs/v1.php/cloud/capabilities"}
opencloud-1       | {"level":"warn","service":"frontend","pkg":"rhttp","traceid":"37e7a5cd10ededc1314aabe3df23eb5f","time":"2025-09-29T10:51:41Z","line":"github.com/opencloud-eu/reva/[email protected]/internal/http/interceptors/auth/auth.go:248","message":"core access token not set"}
opencloud-1       | {"level":"info","service":"proxy","proto":"HTTP/1.1","request-id":"e9206861-36d8-49ca-acf8-ce2d770aef99","traceid":"52221833d19fc291a37f864165ec4f94","remote-addr":"2a02:8084:4e60:d180:da5e:d3ff:fe8c:eb2e","method":"GET","status":200,"path":"/ocs/v1.php/cloud/capabilities","duration":2.023692,"bytes":3312,"time":"2025-09-29T10:51:41Z","line":"github.com/opencloud-eu/opencloud/services/proxy/pkg/middleware/accesslog.go:34","message":"access-log"}
opencloud-1       | {"level":"error","service":"proxy","error":"failed to verify access token: token is malformed: token contains an invalid number of segments","authenticator":"oidc","path":"/api/v0/settings/roles-list","user_agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36","client.address":"2a02:8084:4e60:d180:da5e:d3ff:fe8c:eb2e","network.peer.address":"","network.peer.port":"","time":"2025-09-29T10:51:41Z","line":"github.com/opencloud-eu/opencloud/services/proxy/pkg/middleware/oidc_auth.go:198","message":"failed to authenticate the request"}
opencloud-1       | {"level":"info","service":"proxy","proto":"HTTP/1.1","request-id":"7e6e2eeb-8cad-460e-b4ad-6a0f66cf6802","traceid":"64b570f623d7009a08a97bc154b94c9d","remote-addr":"2a02:8084:4e60:d180:da5e:d3ff:fe8c:eb2e","method":"POST","status":401,"path":"/api/v0/settings/roles-list","duration":0.241309,"bytes":0,"time":"2025-09-29T10:51:41Z","line":"github.com/opencloud-eu/opencloud/services/proxy/pkg/middleware/accesslog.go:34","message":"access-log"}
opencloud-1       | {"level":"error","service":"proxy","error":"failed to verify access token: token is malformed: token contains an invalid number of segments","authenticator":"oidc","path":"/graph/v1.0/me","user_agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36","client.address":"2a02:8084:4e60:d180:da5e:d3ff:fe8c:eb2e","network.peer.address":"","network.peer.port":"","time":"2025-09-29T10:51:41Z","line":"github.com/opencloud-eu/opencloud/services/proxy/pkg/middleware/oidc_auth.go:198","message":"failed to authenticate the request"}
opencloud-1       | {"level":"info","service":"proxy","proto":"HTTP/1.1","request-id":"c1856cba-c0ad-41a3-8466-20f3b5d90261","traceid":"d0c69c90714fbcc302f08eb99485a672","remote-addr":"2a02:8084:4e60:d180:da5e:d3ff:fe8c:eb2e","method":"GET","status":401,"path":"/graph/v1.0/me","duration":0.171389,"bytes":0,"time":"2025-09-29T10:51:41Z","line":"github.com/opencloud-eu/opencloud/services/proxy/pkg/middleware/accesslog.go:34","message":"access-log"}

</p> </details>

Additional context

  • The IDP (django-oidc-provider) works correctly with other applications, confirming that it generates valid JWT access tokens.
  • The issue appears to be specific to how the OpenCloud client (frontend) handles the Access Token after the OIDC flow, potentially storing or retrieving a non-JWT session identifier instead of the full JWT.
  • The presence of the Bearer scheme in the header suggests the client intends to send a Bearer token, but the token value is an opaque string.
  • The successful web-oidc-callback suggests token exchange was successful. The failure occurs during the use of the resulting token.

Jean28518 avatar Sep 29 '25 11:09 Jean28518

Please try PROXY_OIDC_ACCESS_TOKEN_VERIFY_METHOD=none

The default in opencloud is jwt because we expect a jwt token. If your IdP does not use JWT, then use none.

micbar avatar Sep 29 '25 11:09 micbar

Thank you very much for your quick reaction.

This setting didn't change anything (I put it into the .env next to the docker-compose.yml). I am getting the same error message. I am very sure my IdP uses JWT, as you see the OIDC Provider Setting. Other OIDC-Clients are working fine with JWT.

The callback URL after successful authentication at the IdP looks like, which should be right. /oidc-callback.html?code=935b918709464ca8a4a6723f315f7c4a&state=93c65ffe9d8542c3b3c77d21e6e708de (However the JWT should be retrieved via the Back-Channel Request)

If you need any further DEBUG information you're welcome.

Jean28518 avatar Sep 29 '25 11:09 Jean28518

Putting that variable in the .env changes nothing. It will only be picked by the docker-compose.yaml if there is a env substitution already in place.

micbar avatar Sep 29 '25 12:09 micbar