Problem/Motivation

Every OAuth2 client in Simple OAuth is a consumer content entity. Content entities can't be defined in code or deployed with configuration, so there is no reliable way to ship an "always available" client with a module or keep one in sync across environments. Sites that manage many API or agent clients end up creating them by hand or with deploy hooks.

The OAuth flows already go through one seam: the simple_oauth.repositories.client service (ClientRepository). The token controller, the authorize controller, and ScopeRepository all get the client from it. A module can decorate that service and return a ClientEntity built from an unsaved Consumer.

That works until a token is saved. Two places assume every client is a saved consumer row:

  • TokenEntityNormalizer::normalize() stores $client->getDrupalEntity()->id() in the oauth2_token.client entity reference. An unsaved consumer has no ID.
  • TokenAuthUser::__construct() reloads the consumer from $token->get('client')->entity on every authenticated request. If the reference doesn't load, it throws a TypeError.

I prototyped a module that defines clients as YAML plugins and decorates ClientRepository. To make tokens work today, it has to keep a consumer row for each plugin client, only so the token has an entity ID to reference. Once tokens stop depending on the consumer row, that workaround goes away.

Proposed resolution

Make ClientRepository the only way Simple OAuth resolves a client, so any implementation of ClientRepositoryInterface can serve clients.

  1. #3630880: Using an access token after its consumer is deleted throws a TypeError
  2. #3630881: Refresh tokens are saved without a client reference
  3. #3630882: Store the OAuth2 client identifier on tokens
  4. #3630883: Resolve the token's client through ClientRepository in TokenAuthUser

The first two are bugs that stand on their own. The last two are the actual change.

Out of scope: admin UIs that list consumers, like the client credentials token form and the token list builder. Clients served by another repository won't show up in those lists, and that's fine.

Remaining tasks

  • Agree on the direction.
  • Land the child issues.

AI disclosure

AI-Generated: Yes. I drafted this issue summary with Claude Code and reviewed it before filing.

Comments

mglaman created an issue. See original summary.

mglaman’s picture

Issue summary: View changes
mglaman’s picture

Issue summary: View changes
mglaman’s picture

Issue summary: View changes
mglaman’s picture

Issue summary: View changes
mglaman’s picture

Issue summary: View changes
mglaman’s picture

Issue summary: View changes
mglaman’s picture

Issue summary: View changes
mglaman’s picture

saving to refresh embedded links for status changes