Skip to content

[BUG] Token refresh fails with external OIDC provider (Authelia) - clientID is null #30

@sq3

Description

@sq3

When using an external OIDC provider (e.g., Authelia) with a pre-configured public client, token refresh fails because the clientID is not persisted to the authentication secret. The app logs show clientID=(null) during refresh attempts.

Steps to reproduce

  1. Configure OpenCloud server with Authelia as external OIDC provider (built-in IDP disabled via OC_EXCLUDE_RUN_SERVICES: "nats,idp,idm")
  2. Configure Authelia with OpenCloudIOS public client (PKCE, grant_types: [authorization_code, refresh_token], redirect_uri: oc://ios.opencloud.eu)
  3. Add account in iOS app and complete initial OIDC authentication - this works successfully
  4. Wait for access token to expire (~1 hour)
  5. Reopen app or trigger any API request

Expected behavior

The app should use the stored refresh token to obtain a new access token silently, like the OpenCloud Desktop client does with the same Authelia configuration.

Actual behavior

Token refresh fails. The app logs show clientID=(null) when attempting to load stored credentials:

[AUTH, Openid-Connect, …] Loaded from secret: clientID=(null), clientSecret=(null) [… ClientRegistration] [AUTH, Openid-Connect] Token can't be refreshed due to error=Error Domain=OCError Code=6 "Authorization failed because data was missing from the secret data for the authentication method."

The user sees "Authorization failed - No authentication data has been found for this connection."

Note: OpenCloud Desktop client works correctly with identical Authelia configuration.

Client

  • iOS version: 18.6.2
  • OpenCloud app version: 1.0.1 (3)
  • Device model: iPhone17,1

Server configuration

  • OpenCloud version: 4.0.1
  • OIDC Provider: Authelia (external)

Logs

iOS app log

[AUTH, Openid-Connect, …] Loaded from secret: clientID=(null), clientSecret=(null) [… ClientRegistration] [OCAuthenticationMethodOpenIDConnect.m:530|FULL] [AUTH, Openid-Connect] OAuth2 token expired (reason: 1:0, date: 2026-01-06 09:15:33 +0000, timeout period: 3599 secs) - refreshing token for connection.. [OCAuthenticationMethodOAuth2.m:691|FULL] [AUTH, Openid-Connect] Token refresh started [OCAuthenticationMethodOAuth2.m:787|FULL] [AUTH, Openid-Connect, …] Loaded from secret: clientID=(null), clientSecret=(null) [… ClientRegistration] [OCAuthenticationMethodOpenIDConnect.m:530|FULL] [AUTH, Openid-Connect] Token can't be refreshed due to error=Error Domain=OCError Code=6 "Authorization failed because data was missing from the secret data for the authentication method. (error 6)" (-[OCAuthenticationMethodOAuth2 __refreshTokenForConnection:availabilityHandler:] [OCAuthenticationMethodOAuth2.m:849])

Root cause analysis

After investigating the source code, the issue appears to be in OCAuthenticationMethodOpenIDConnect.m:

In ios-sdk/OpenCloudSDK/Authentication/OCAuthenticationMethodOpenIDConnect.m:

if (_clientRegistrationResponse != nil)
{
    authSecret.clientID = ...
    authSecret.clientSecret = ...
}

The clientID is only stored when _clientRegistrationResponse != nil. For external OIDC providers with pre-configured public clients, dynamic client registration never occurs, so _clientRegistrationResponse is always nil. This means clientID is never persisted to the authentication secret. In startTokenRefreshWithCompletionHandler: The code retrieves clientID from the stored secret, but since it was never persisted, it returns nil:

NSString *clientID = authSecret.clientID; // Returns nil
NSString *clientSecret = authSecret.clientSecret; // Returns nil

Suggested fix

In postProcessAuthenticationDataDict, store the clientID even when dynamic registration was not used:

// Always store the effective clientID, whether from dynamic registration or pre-configured
NSString *effectiveClientID = _clientRegistrationResponse.clientID ?: _clientID ?: [super clientID];
if (effectiveClientID != nil)
{
    authSecret.clientID = effectiveClientID;
}

// Only store clientSecret if we have one (from dynamic registration)
if (_clientRegistrationResponse.clientSecret != nil)
{
    authSecret.clientSecret = _clientRegistrationResponse.clientSecret;
}

Additional context

This issue probably affects all external OIDC providers that use pre-configured public clients (common for PKCE flows)
The Desktop client looks like it handles this correctly

Let me know if I'm on the right path. Sill puzzeling...

Related: #16, #4

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions