Problem/Motivation

When upgrading from openid_connect 1.x to 3.x, some sites encounter a fatal error when attempting to log in:

Symfony\Component\Routing\Exception\InvalidParameterException: Parameter "openid_connect_client" for route
  "openid_connect.redirect_controller_redirect" must match "[^/]++" ("" given)

This happens because 3.x switched from plugin-based clients to config entities (see comment #4), and the redirect URL slug is now derived from the config entity machine name. Drupal machine names cannot contain dashes (-), but many OIDC providers (e.g. the Belgian government's ACM/IDM service) use dashes in redirect URIs that were registered during the 1.x era (e.g. /openid-connect/acm-idm). Those registrations cannot be changed on the provider side, so sites are unable to upgrade without breaking their redirect URI (see comment #5, comment #6, comment #17).

Note: a related symptom — parentEntityId being empty — is resolved simply by running drush updatedb after upgrading (see comment #6).

Proposed resolution

Add a separate "slug" field to the OpenID Connect client config entity that decouples the redirect URL identifier from the machine name (first proposed in comment #6, patch attached in comment #19). Operators can then set a slug containing dashes to match the redirect URI already registered at the provider, while the machine name stays valid per Drupal conventions. When no slug is set the machine name is used as before, so existing sites are unaffected.

A param converter (openid_connect_entity) resolves the {openid_connect_client} route parameter by slug or by machine name, on both /openid-connect/{client} and /openid-connect/{client}/initiate. Redirect URIs that already use the machine name keep working untouched.

Sites coming from 1.x with a dash-bearing plugin id are migrated automatically. openid_connect_update_8200() sanitizes the dash out of the new machine name (acm-idmacm_idm) and keeps the original plugin id as the provider slug, so the redirect URI does not change. Since the runtime provider name is derived from the entity id, openid_connect_update_8204() applies the same sanitization to the identity mappings it moves into the authmap table — without this, every existing user of a dash-bearing client would be silently unlinked from their account and treated as a first-time visitor on their next login.

This approach was reworked into MR !167 (see comment #22) and confirmed working in production by the original reporter (see comment #25). The branch is rebased on current 3.x.

Remaining tasks

  1. ✅ Add/ensure test coverage — functional coverage of the slug round-trip, slug validation and both routes; kernel coverage of the redirect URL preview; unit coverage of the param converter; and update-path coverage from a 1.x fixture that includes a dash-bearing client with an existing identity mapping
  2. ✅ Rebase with 3.x
  3. ⚠️ Review of MR !167 — an AI-assisted code review was run over these changes and surfaced the two bugs described above, but a human review is still wanted.
  4. ⚠️ Manual testing — not performed. Given the Drupal 10 end-of-life timeline, this is assumed covered by the automated tests above: the update-path test asserts that a 1.x upgrade of a dash-bearing client leaves the redirect URL unchanged and keeps the existing identity mapping pointing at the same account, and the functional tests cover a client whose slug differs from its machine name. Real-world confirmation is still welcome, particularly from sites integrating with a provider whose registered redirect URI contains a dash.

User interface changes

A new "Provider slug" field is added to the OpenID Connect client configuration form, allowing a custom string (including dashes) in the redirect URI path. The Redirect URL shown on the form updates to match the slug as it is typed, and falls back to the machine name when the field is left empty.

The slug must be unique across clients and may contain only letters, numbers, dots, hyphens and underscores; surrounding whitespace is trimmed. The restriction exists because the slug becomes a single path segment of the redirect URL — a slug containing / cannot be generated into a URL and would otherwise break both the login flow and the form needed to correct it.

API changes

OpenIDConnectClientInterface gains getProviderSlug() and setProviderSlug(string $provider_slug = ''). Client plugins extending OpenIDConnectClientBase inherit both and need no change; classes impctly must add them.

OpenIDConnectClientBase::getRedirectUrl() now uses the provider s back to the parent entity id.

Data model changes

A provider_slug key is added to the client plugin settings schemat.plugin.*), defaulting to an empty string.

  1. openid_connect_update_8200() (1.x → 3.x): sanitizes - to _ in the new config entity machine name and stores the original plugin id as provider_slug.
  2. openid_connect_update_8204() (1.x → 3.x): applies the same sanitization to the authmap.provider values it migrates, so existing account links survive the rename.
  3. openid_connect_update_30007(): backfills settings.provider_slug with an empty string on existing clients.
Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

sboden created an issue. See original summary.

sboden’s picture

Issue summary: View changes
sboden’s picture

I've been debugging a bit in openid_connect.

It seems when moving from v1 to v2 the config data changed names, anyway my config from v1 isn't visible anymore in v2.

So I recreated my config. After that. I have my own client that derives from OpenIDConnectClientBase and that doesn't seem to be able to get its config data, e.g. the parentEntityId (which seems to be filled in in plugin initialization) is "", while the config actually has a value for it.
Strange thing is that I can see my OIDC config data in the $configuration variable which is coming from OpenIDConnectClientBase.

jcnventura’s picture

You probably realized that the major change from 1 to 2 is that the openid plugins are no longer part of the module configuration, but are now "config entities". That means that you can actually re-use the same plugin for different remote services, which was impossible before.

This was a major refactoring of the code, but plugins should not change that much. Please see #2899185: Use config entities for clients.

Check https://git.drupalcode.org/project/openid_connect/-/compare/4b2c6bfe9bae... for the changes done to the Generic client until now.. Note that some of those (autodiscovery of endpoints and the end-session endpoint) are new functionalities that don't have anything to do with the changes needed to make the plugin compliant with the new functionality.

sboden’s picture

Thanks for the information... maybe an entry could be made in the readme file to explain upgrades.

I'm still not out of the woods with the problem but I pinpointed where it is.

But first, I need my own OIDC client since I need a small extension to support a "login_hint" feature. So what I do is make a new client that extends OpenIDConnectClientBase, which is empty besides a copy of the authorize() method from OpenIDConnectClientBase to which I add 2 lines. But for testing purposes I even just extended OpenIDConnectClientBase and removed all methods.

Then I get the error "Symfony\Component\Routing\Exception\InvalidParameterException: Parameter "openid_connect_client" for route "openid_connect.redirect_controller_redirect" must match "[^/]++" ("" given) to generate a corresponding URL. in Drupal\Core\Routing\UrlGenerator->doGenerate() (line 203 of /var/www/html/www/core/lib/Drupal/Core/Routing/UrlGenerator.php)." when I go to my site.
Reason for this is because in the method below the "$this->parentEntityId" is an empty string.

  public function authorize(string $scope = 'openid email', array $additional_params = []): Response {
    $language_none = \Drupal::languageManager()
      ->getLanguage(LanguageInterface::LANGCODE_NOT_APPLICABLE);

    $redirect_uri = Url::fromRoute(
      'openid_connect.redirect_controller_redirect',
      [
        'openid_connect_client' => $this->parentEntityId,
      ],
      [
        'absolute' => TRUE,
        'language' => $language_none,
      ]
    )->toString(TRUE);

    $url_options = [
      'query' => [
        'client_id' => $this->configuration['client_id'],
        'response_type' => 'code',
        'scope' => $scope,
        'redirect_uri' => $redirect_uri->getGeneratedUrl(),
        'state' => $this->stateToken->generateToken(),
      ],
    ];

    if (!empty($additional_params)) {
      $url_options['query'] = array_merge($url_options['query'], $additional_params);
    }

    $endpoints = $this->getEndpoints();
    // Clear _GET['destination'] because we need to override it.
    $this->requestStack->getCurrentRequest()->query->remove('destination');
    $authorization_endpoint = Url::fromUri($endpoints['authorization'], $url_options)->toString(TRUE);

    $this->loggerFactory->get('openid_connect_' . $this->pluginId)->debug('Send authorize request to @url', ['@url' => $authorization_endpoint->getGeneratedUrl()]);
    $response = new TrustedRedirectResponse($authorization_endpoint->getGeneratedUrl());
    // We can't cache the response, since this will prevent the state to be
    // added to the session. The kill switch will prevent the page getting
    // cached for anonymous users when page cache is active.
    $this->pageCacheKillSwitch->trigger();

    return $response;
  }

I compared what the URLs were in openid_connect v1.2.0 and what solves it for me in v3 is this. Replacing $this->parentEntityId by $this->getPluginId() in the authorize() method.

    $redirect_uri = Url::fromRoute(
      'openid_connect.redirect_controller_redirect',
      [
        'openid_connect_client' => $this->getPluginId(),
      ],
      [
        'absolute' => TRUE,
        'language' => $language_none,
      ]
    )->toString(TRUE);

$this->getPluginID() results in my case in "acm-idm" (the system name I used for the plugin, and also the id of the OIDC server). And the URLs I get out of it are:

redirect URI = "https://mysite.be/openid-connect/acm-idm"
authorization url = "https://authentication-server.be/op/v1/auth?client_id=e243df5e-9f0d-4977...

The strange thing is also that on the client config screen the Redirect URL is calculated as "https://mysite.be/openid-connect/acm_idm"... which in my case is wrong: the acm_idm is the system name of the client config (which can't have dashes, while the actual string should "https://mysite.be/openid-connect/acm-idm" (dash instead of underscore), BUT it does find "acm_idm" while during the actual execution that part is always empty. The reason for the discrepancy is that the formbase for the config screen has its own getRedirectUrl() method which calculates the string with the system name of the client config.

So I still have 2 problems:
1. The parentEntityId doesn't get filled in properly. I see it being filled in in the initializePlugin() method of OpenIDConnectClientCollection, but I see no evidence of it actually happening.
2. If parentEntityId would be filled in properly I can't make it work since I need "acm-id" as part of the URIs, and system names in Drupal can't contain a dash, so I can't make an OIDC client system name that I can use with my OIDC server. And I'm only a user of the OIDC server, so I can't change anything to the setup there.

sboden’s picture

Some more debugging.

Number 1. got solved by running "drush updatedb", my bad.

For number 2. I would suggest to introduce a new field and use that instead of the system name of the instance of the plugin in the redirect URL. For backwards compatibility: when the new field is empty the system name could be used anyway. Sole reason is that Drupal system names can't use '-', and some OIDC providers (e.g. the one of the Belgian government) do use a dash ("acm-idm").
For the name of the name field, no idea what it should be called. Right now the redirect parameter is filled in in "parentEntityId" and it gets taken from "client_id", which it not really is in OIDC terminology.

Short term work-around for me is to override the parameter for the redirect URL in my own client, which would actually revert it a bit more like v1.

vensires’s picture

Version: 2.0.0-beta3 » 3.x-dev
Category: Support request » Bug report
Status: Active » Needs work

Since we only have an issue with the dash, we should better only take care of it under the hood.
I'm going to open a MR for this based on v3 of this module. This should be easily applied to v2 though I suppose too.
I also mark it as a bug report too since it's trying to fix a functionality that was working previously - and of course for other developers to also be able to find it more easily.

vensires’s picture

Status: Needs work » Needs review

Setting as ready for review after committing to https://git.drupalcode.org/project/openid_connect/-/merge_requests/84.

kawaljeet singh’s picture

StatusFileSize
new579 bytes

Addding a patch as suggested in #5. This patch will fix this error : Symfony\Component\Routing\Exception\InvalidParameterException: Parameter "openid_connect_client" for route "openid_connect.redirect_controller_redirect" must match "[^/]++" ("" given) to generate a corresponding URL. in Drupal\Core\Routing\UrlGenerator->doGenerate() (line 203 of /var/www/html/www/core/lib/Drupal/Core/Routing/UrlGenerator.php).

maboresev’s picture

I've tested #10 and I can confirm that it works properly in combination with this Keycloak related issue.

johan den hollander’s picture

We're having the #10 patch in use for a while now. Works fine. Small change.

johan den hollander’s picture

Applied the patch against the latest 2.x version.

vensires’s picture

Just to sum up, since the MR is using a param_converter approach, do we all agree that what @sboden proposed in #5 is a better solution here?

So, the issue addressed is fixed by using patch in #13 (while also crediting @kawaljeet-singh for #10)?

jcnventura’s picture

I honestly would need to test this. The plugins changed from being config objects to being config entities. So now you can have multiple Okta plugins installed each with a different name. I'm not 100% sure, but I think that the above only works as long as the machine ID of the config entity is the default value (same as plugin machine name). So if you can, please create two config entities, both with names not being the default from the same plugin and check what this function returns for those entities.

If that is confirmed, then #10 and #13 are basically junk, and the solution should probably be closer to what is proposed in the MR.

johan den hollander’s picture

#13 is junk. I Agree 100% because the patch is malformed. Using the patch will give you errors. Updated the code which should work now.

pfrilling’s picture

Status: Needs review » Postponed (maintainer needs more info)

It's been over a year since the last update here. I'm going to mark this postponed for now. Is anyone able to confirm they are still using the proposed patches?

sboden’s picture

I'm still using patches for this one, but it's becoming a huge patch.

The problem is that we have some government sites that use "acm-idm" as slug in the openid_connect redirect URL, and you can't have a system name in Drupal with a "-" in it (in the very old openid_connect 1.x module, you could get the "right" redirect URL with a "-" in it because the redirect used the plugin-name and not the config system name). I have about 20 Drupal sites integrating with the same openid_connect server and I'm not in control of the redirect URLs.

So it's only a problem when you integrate with an openid_connect server using -'s in the redirect URL.

pfrilling’s picture

Status: Postponed (maintainer needs more info) » Needs work

Thanks @sboden. I'm moving this back to needs work. I'll review further and see about getting this fixed.

sboden’s picture

Attached patch works for me on openid_connect 3.0.0-alpha2 (I can reroll it).

It decouples the config name with the name used in the redirect controller.
Because of that some wiring magic is also needed to convert the other way around: to match config with incoming slug in the redirect controller.

soubi’s picture

Adapted #20 for 3.0.0-alpha6

sboden’s picture

Probably a coincidence. I also adapted #20 for 3.0.0-alpha6. I have been using this patch in production now on 1 site for 2 weeks, no-one is complaining so far.

I did add a small piece from another issue as the edits to the same code made me nervous. This is now the only patch I use for 3.0.0-alpha6

pfrilling’s picture

Status: Needs work » Needs review

Thanks @soubi and @sboden!

I re-worked your patches into a new merge request. Can you review/test these and report your findings? There is one failing test, but it doesn't apply to this MR and is associated with an update hook.

sboden’s picture

Which exact version do I test?

pfrilling’s picture

You would want to test the upgrade from 1.x to 3.x using the code from MR 167.

You can also just use the patch from MR 167 against the -dev version to confirm if everything is working.

sboden’s picture

Seems I was looking at a wrong branch.

I tested the 3 dev version. The provider slug is working. For the Belgian government we usually have 'acm-idm' as slug, you can change that slug when you request the integration; but all the old integrations still use 'acm-idm' of course. And I didn't want to go back and change old integrations.

steinmb’s picture

Title: Upgrade problems openid_connect 1 to 2 » Problems upgrading openid_connect 8.x-1.4 to 3.x

As 2.x no longer looks supported, I assume the supported upgrade path is from 8.x-1.4 to 3.x.

benjifisher’s picture

I am adding the tag for an issue summary update.

Browsing the comments on this issue and the code changes, I am not at all sure what problem this issue is trying to fix. Is it about custom code that integrates with this module or just the process of upgrading to the 3.x branch? If someone can give a clear description of what conditions lead to what problems, then I will be happy to help with this issue: review, test, whatever is needed.

pfrilling’s picture

Assigned: Unassigned » pfrilling
Issue summary: View changes
Status: Needs review » Needs work

I updated the issue summary to more clearly define what the issue was. I also moved to needs work for the required rebase. I want to also ensure test coverage is available.

Assigning to myself to complete that work.

pfrilling’s picture

Issue summary: View changes
pfrilling’s picture

Issue summary: View changes
steinmb’s picture

Thank your for updating the IS and I do not think we need the tag https://www.drupal.org/node/3359789/revisions/view/14321958/14321967.

pfrilling’s picture

Issue summary: View changes
Status: Needs work » Needs review

I rebased and added additional test coverage. My testing seems to be working well. I believe this is ready for another review.

pfrilling’s picture

Assigned: pfrilling » Unassigned
Issue summary: View changes
Status: Needs review » Reviewed & tested by the community

Rebased on current 3.x and updated the issue summary.

While going back over the upgrade path I found and fixed two problems:

  1. openid_connect_update_8200() renames a dash-bearing client (acm-idmacm_idm), but openid_connect_update_8204() was still writing the authmap provider under the original unsanitized name. Existing SSO users of such a client would have been silently unlinked from their accounts on update. 8204 now sanitizes the same way, covered by the 1.x update-path test with a dash-bearing client that has an existing identity mapping.
  2. provider_slug was free text validated only for uniqueness, so a slug containing / saved fine and then threw the same InvalidParameterException this issue is about — taking down both login and the client form needed to correct it. The slug is now trimmed and restricted to letters, numbers, dots, hyphens and underscores, and the form reports an unusable stored slug instead of fataling, so configuration created before the validation can still be fixed.

Both fixes came out of an AI-assisted code review. The automated tests are the evidence — the full module suite passes locally — but manual upgrade testing has not been done, so real-world confirmation is still welcome, particularly from a site whose registered redirect URI contains a dash.

pfrilling’s picture

Status: Reviewed & tested by the community » Fixed

Merged. Credit attributed.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

  • pfrilling committed c30e9ed9 on 3.x
    fix: #3359789 Problems upgrading openid_connect 8.x-1.4 to 3.x
    
    By:...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.