Skip to content

Client dynamic registration - issues when provider invalidates cached client_id #4177

@timgent

Description

@timgent

Search terms you've used

handleIncomingRedirect restorePreviousSession invalid_client, client_id expired session restore, prompt=none invalid client redirect, session restore blocking error

Impacted package

Which packages do you think might be impacted by the bug ?

  • solid-client-authn-browser
  • solid-client-authn-node
  • solid-client-authn-core
  • oidc-client-ext
  • Other (please specify): ...

Bug description

When handleIncomingRedirect({ restorePreviousSession: true }) is called with stored session data containing an invalidated/expired client_id, the library immediately redirects to the OAuth provider with the invalid credentials before our application can handle the error. This results in a 401 "invalid_client_id" error that completely blocks users from accessing the application, with no programmatic recovery path.

Users who return to our app after extended periods (when their dynamically registered client has been invalidated by the OAuth provider) are unable to use the application. The only recovery is manually clearing browser data, which most users don't know how to do.

To Reproduce

  1. Log in to a Solid Pod provider successfully (creates dynamic client registration, stores client_id in browser)
  2. Close the browser (session data remains in IndexedDB/localStorage)
  3. Wait for OAuth provider to invalidate the client registration (or manually delete it from provider's admin panel if possible for testing)
  4. Open the app again
  5. Observe: Immediate redirect to authorization endpoint with the now-invalid client_id
  6. Observe: 401 "invalid_client_id" error page from OAuth provider

Our code:

import { handleIncomingRedirect, getDefaultSession } from '@inrupt/solid-client-authn-browser';

useEffect(() => {
  const initSession = async () => {
    try {
      await handleIncomingRedirect({ restorePreviousSession: true });
      const session = getDefaultSession();
      setSession(session);
    } catch (error) {
      console.error("Session initialization error:", error);
      // This never fires for invalid client_id scenarios
      await solidLogout();
    }
  };
  initSession();
}, []);

Expected result

We're not sure what the intended behavior should be, but we would expect one of:

  1. The library validates stored client credentials before initiating silent auth, clears invalid session data automatically, and returns a logged-out session
  2. The error is catchable in our try/catch block so we can handle it programmatically
  3. A way to inspect/validate stored session data before calling handleIncomingRedirect() so we can clear corrupted data proactively

Ideally, users wouldn't be blocked from accessing the application due to expired OAuth credentials stored in their browser.

Actual result

When the user visits the app:

  1. handleIncomingRedirect({ restorePreviousSession: true }) is called
  2. The library immediately redirects to the OAuth provider (before our code continues):
    https://login.inrupt.com/authorization?
      client_id=22fe30ba-129c-46ed-843c-e8f28debdb16
      &prompt=none
      &redirect_uri=https%3A%2F%2Freact-packing-app.vercel.app%2Fpod-auth-callback.html
      &response_mode=query
      ...
    
  3. OAuth provider returns 401 with "invalid_client_id" error
  4. User is stuck on the error page
  5. Our try/catch block never catches any error (presumably because the redirect happens synchronously before the Promise is created)

The user cannot access the application. Manual browser data clearing is required to recover.

Environment

$ npx envinfo --system --npmPackages --binaries --npmGlobalPackages --browsers

System:
  OS: Linux (Vercel deployment + local development)
  Browser: Chrome/Firefox/Safari (reproducible across all)

Binaries:
  Node: 18.x
  npm: 9.x

npmPackages:
  @inrupt/solid-client: ^2.1.2
  @inrupt/solid-client-authn-browser: ^3.1.0
  @inrupt/vocab-common-rdf: ^1.0.5
  @inrupt/vocab-solid: ^1.0.4

Production app affected: https://react-packing-app.vercel.app

Additional information

Our Understanding (Please Correct If Wrong)

From our investigation, it appears that when restorePreviousSession: true, the library immediately initiates a silent auth redirect (prompt=none) before handleIncomingRedirect() returns its Promise. If the stored client_id has been invalidated, the redirect happens anyway with invalid credentials. Since the redirect is synchronous, our try/catch cannot intercept it.

We may be misunderstanding how this is intended to work.

Questions We're Hoping You Can Help With

  1. Is this the intended behavior when stored client credentials become invalid? Should applications expect this scenario?

  2. Is there a recommended pattern for handling invalidated client_ids that we're missing? Should we be doing something differently in our setup?

  3. Is there a way to detect or validate stored session data before calling handleIncomingRedirect() so we can clear it proactively if needed?

  4. Would it make sense for the library to handle this scenario internally (e.g., detecting the invalid client and clearing session data automatically), or is that something applications should manage?

  5. For restorePreviousSession: true to be safe in production, what assumptions should we make about client_id lifetime and validity?

Temporary Workaround We're Considering

await handleIncomingRedirect({ restorePreviousSession: false });

This would require users to explicitly log in after each page refresh, losing the seamless "stay logged in" experience, but prevents the blocking issue entirely.

Would this be the recommended approach, or is there a better pattern we should follow?

Related Issues/Discussions We Found

We appreciate any guidance you can provide on the recommended way to handle this scenario!

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions