Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Breaking changes in OpenIddict 4.0 impacting how URIs are handled #1613

Closed
kevinchalet opened this issue Dec 6, 2022 · 8 comments
Closed

Comments

@kevinchalet
Copy link
Member

kevinchalet commented Dec 6, 2022

Initially planned for OpenIddict 5.0, the modifications introducing behavior changes in the URIs handling will finally ship as part of 4.0.

Before detailing these changes, here's a bit of history to understand the motivations for changing the existing behavior:

  • OpenIddict 1.x/2.x exclusively used ASP.NET Core's PathString primitive type as a way to represent endpoint addresses. As a consequence, only relative paths were supported and, as required by PathString's constructor, paths had to start with a leading / (e.g /connect/authorize). This was simple and unambiguous, but sadly quite limited.

  • As part of the ASP.NET Core decoupling, OpenIddict 3.0 stopped using the ASP.NET Core-specific PathString type to represent endpoint addresses and started using System.Uri, which was also a good opportunity to support absolute URIs (e.g if you want to separate the userinfo endpoint from your main authorization server, you can simply call options.SetUserinfoEndpointUris("https://api.domain.com/userinfo") with an absolute URI and OpenIddict will return it as-is in the discovery document (userinfo requests won't be served by OpenIddict itself when the host of the specified address differs from the one specified in the received requests).

To avoid a breaking change at the time, the same semantics as the ones defined by PathString were preserved, so things worked similarly as in previous versions: relative URIs still had to start with a leading / and none of the typical canonicalization rules of URIs applied (e.g you couldn't start your endpoint address with ./ or have .. in them).

Retrospectively, not adopting that breaking change was not a good idea, as relative System.Uri instances starting with a / don't have the same meaning as PathString (that always starts with a /): unlike PathString, a leading / in a System.Uri always indicates a site/root-relative URI, which is the typical behavior you also see with URLs in a HTML document (e.g if the current URL is https://domain.com/path/, a relative link to document.html will point to https://domain.com/path/document.html while a relative link to /document.html will point to https://domain.com/document.html).

These inconsistencies contributed to confusing users - who often asked why things didn't work when they used options.SetAuthorizationEndpointUris("authorize") instead of options.SetAuthorizationEndpointUris("/authorize") - so the behavior in OpenIddict 4.0 will change to fully embrace the System.Uri rules for relative URIs and reduce the confusion:

Relative endpoint URIs will now follow the System.Uri rules

Two cases exist:

HttpRequest.PathBase is equal to string.Empty

It's the most common scenario. In this case, the application root is http://domain.com/ so if you keep using /connect/authorize as an endpoint address, nothing will change compared to 3.x as the resulting absolute URI will be http://domain.com/connect/authorize.

If you decide to use ./connect/authorize or connect/authorize or even ../connect/authorize, the resulting address will still be http://domain.com/connect/authorize.

HttpRequest.PathBase is not equal to string.Empty

It's typically the case when using IIS virtual application paths or when using certain ASP.NET Core middleware (built-in or not) that change HttpRequest.PathBase to a specific value (e.g for multi-tenant scenarios). In this case, the application root is http://domain.com/pathbase so if you use /endpoint (which is a root-relative address), the resulting address will be http://domain.com/endpoint and not http://domain.com/pathbase/endpoint.

To keep the same final address, users will have to change the endpoint address to endpoint or ./endpoint instead of /endpoint.

As a positive consequence, users are now able to specify relative addresses that are outside the current application. For instance, an HttpRequest.PathBase of /tenantA/authorization-server with an address of ../userinfo will result in an absolute URI of http://domain.com/tenantA/userinfo, which can be useful when the userinfo API is not in the same application as the authorization server.

The configuration endpoint (.well-known/openid-configuration) no longer uses the OpenIddictServerOptions.Issuer property as the base URI to compute absolute URIs

In 4.0, the absolute endpoints URIs returned by .well-known/openid-configuration will no longer use OpenIddictServerOptions.Issuer as the base URI. Instead, the computed value of HttpRequest.Scheme + Uri.SchemeDelimiter + HttpRequest.Host + HttpRequest.PathBase - which was already used when no explicit OpenIddictServerOptions.Issuer was set - will always be used, so that the addresses reflect the request details exposed by ASP.NET Core.

I took a look at the community projects that sponsor OpenIddict and here's how it will affect them:

Feel free if you have any question or remark.

@kevinchalet
Copy link
Member Author

Note: if you want to give it a try, the new behavior is present in the 4.0 rc1 bits, whose nightly builds can be found on the MyGet feed. It should land on NuGet.org very soon.

@danielleiszen
Copy link

danielleiszen commented Jan 5, 2023

Hi, we have tried to migrate our cloud instance from v4.0.0-preview4.22517.68 to v4.0.0 release.
We experiencing an error saying
The OpenID Connect request cannot be retrieved.
when our AuthorizationController calls
HttpContext.GetOpenIddictServerRequest()

I have checked the HttpRequest and I think the problem is that the endpoint is configured to be

"https://ourwebsite.com/connect/token" however the request contains "http://ourwebsite.com/connect/token".

Our service is behind an NGINX reverse proxy and we use TLS termination for every site. The client is a SPA.

We configure our endpoints using the SetAuthorizationEndpointUris(params Uri[] addresses) overload since we have to specify the schema (HTTPS) properly otherwise, the OIDC service description would contain false schema that is only used internally.

Header forwarding did not help as well.

How is it possible to configure the OpenIddict provider to match the request regardless of the schema?

Thank you in advance.

EDIT: forgot to mention that the preview worked without a problem for the same configuration.

@kevinchalet
Copy link
Member Author

@danielleiszen hi. The change you describe is expected and is caused by an invalid configuration of your ASP.NET application (if you check HttpRequest.Scheme, you'll see it doesn't have the necessary https value).

@danielleiszen
Copy link

thanks for the prompt response. if I understand correctly:

  • we should not configure Openiddict because the behavior is expected
  • we should configure aspnet.core to use the proper schema in the request

since https is changed to http due to TLS termination by NGINX, we can explicitly use a custom middleware to replace the schema in every request to https. am I right or do you see any other option?

thank you

@AmbroiseCouissin
Copy link

@kevinchalet @danielleiszen I have exactly the same issue and I'm not sure how to solve it.

As SetIssuer doesn't set the base URI anymore, I don't know how to override let's say SetTokenEndpointUris.

Any idea what I'm doing wrong?

Current code that was working in version 4 preview:

        options
            .SetAuthorizationEndpointUris("/connect/authorize")
            .SetIssuer("http://localhost:5000/auth") // This was overriden because behind the application is being proxy
            .SetLogoutEndpointUris($"connect/logout")
            .SetTokenEndpointUris($"connect/token")
            .SetUserinfoEndpointUris($"connect/userinfo")

@kevinchalet
Copy link
Member Author

@AmbroiseCouissin the root cause is the same: your ASP.NET Core application or the proxy is/are not correctly configured to flow the right headers up to ASP.NET Core. Read https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-7.0 for more information.

I'm going to close this ticket. If you still need help, consider sponsoring the project and a create a new dedicated ticket.

@Ranger1230
Copy link

How will this work for instance where the default port isn't being used? Like if we are running locally in IIS Express all apps trying to use this would fail because the .well-known/openid-configuration endpoint is missing the port for all the endpoints making running all apps locally fail.

@kevinchalet
Copy link
Member Author

Non-default ports are supported. If you’re seeing a different behavior, open a support ticket (sponsors-only).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants