Summary

Within a NAT / PAT environment where resource server is on internal network and clients are outside we need to configure the ID provider (keycloak for example) with a frontend url. This frontend url is then used as the issuer

This causes that address for discovery (as seen from the resource server) is different from the issuer

Actual Behavior

In this scenario the OIDC discovery will fail at the resource server due to issuer not match. To have this scenario working we need to manually set the issuer to the frontend url, and the jwk-set-uri to the backend url. The same happens for service accounts using the ClientRegistrations::fromIssuerLocation

Expected Behavior

Although we do have a workaround by manual configurations (see following examples), it doesn't feel the best approach once we have a Discovery support on the protocol.

I would expect to configure the OIDC Discovery URL, and not force it to be the same as the issuer URL.

Configuration

not working configuration

  "security": {
      "oauth2": {
          "resourceserver": {
              "jwt": {
                  "issuer-uri": "http://pdvmdev21:9420/auth/realms/ConfigService"
              }
          }
      }
  }

working configuration:

  "security": {
      "oauth2": {
          "resourceserver": {
              "jwt": {
                  "issuer-uri": "http://pdvmdev21:9420/auth/realms/ConfigService",
                  "jwk-set-uri": "http://pdvmdev21:9428/auth/realms/ConfigService/protocol/openid-connect/certs"
              }
          }
      }
  }

Version

Tested with Spring Security 5.2

Sample

not working ClientRegistations

      ClientRegistration clientRegistration = ClientRegistrations
              .fromIssuerLocation("http://pdvmdev21:9420/auth/realms/ConfigService")
              .clientId(theOpts.getClientId())
              .clientSecret(theOpts.getAuthorization())
              .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
              .registrationId(MY_REGISTRATION_ID)
              .build();

working solution:

      ClientRegistration clientRegistration2 = ClientRegistration.withRegistrationId(MY_REGISTRATION_ID)
              .userNameAttributeName(IdTokenClaimNames.SUB)
              .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
              .clientId(theOpts.getClientId())
              .clientSecret(theOpts.getAuthorization())
              .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
              .authorizationUri("http://pdvmdev21:9420/auth/realms/ConfigService/protocol/openid-connect/auth")
              .jwkSetUri("http://pdvmdev21:9428/auth/realms/ConfigService/protocol/openid-connect/certs")
              .tokenUri("http://pdvmdev21:9428/auth/realms/ConfigService/protocol/openid-connect/token")
              .clientName("http://pdvmdev21:9420/auth/realms/ConfigService")
              .build();

Comment From: jzheaux

@nkonev Thanks for the report.

I would expect to configure the OIDC Discovery URL, and not force it to be the same as the issuer URL.

This is actually according to spec:

If Issuer discovery is supported (see Section 2), this value MUST be identical to the issuer value returned by WebFinger. This also MUST be identical to the iss Claim value in ID Tokens issued from this Issuer.

So, even if you could specify the discovery URL in full, the spec doesn't allow it to be something not based on the issuer.

Comment From: joao-rebelo

Actually from the spec as well:

The returned Issuer location MUST be a URI RFC 3986 [RFC3986] with a scheme component that MUST be https, a host component, and optionally, port and path components and no query or fragment components. Note that since the Host and Resource values determined from the user input Identifier, as described in Section 2.1, are used as input to a WebFinger request, which can return an Issuer value using a completely different scheme, host, port, and path, no relationship can be assumed between the user input Identifier string and the resulting Issuer location.

I believe this sentence goes in the direction of what is being requesting. In an NAT / PAT environment the issuer would need to be a public well-known value understood by all parties, although the server providing its discovery should be located in an address known to the party

Comment From: jzheaux

@joao-rebelo I believe what you are referring to is the WebFinger request that can precede the Metadata request. For example, if my application doesn't know the issuer, it can make a WebFinger request to discover the issuer. If it already knows the issuer, then it can skip this step.

From earlier in Section 2:

Issuer discovery is OPTIONAL; if a Relying Party knows the OP's Issuer location through an out-of-band mechanism, it can skip this step and proceed to Section 4.

ClientRegistrations.fromIssuerLocation begins at Section 4 since the issuer is being provided in the method call.

Can you clarify the steps you are expecting the discovery mechanism to take?

Comment From: joao-rebelo

Straight to what I expect:

Be able to configure the url for the discovery, and that it doesn't require to be related to the issuer.

explanation: on the NAT / PAT the issuer must be the public url of the ID Manager. But if the Resource Server is inside the private network he knows the ID Manager on a different location.

On my working / not working example, the port 9420 is the public one, and 9428 is the private one. I would expect to have on the resource server the configuration similar to the "not working", but on the private port (9428). (probably with a different property name than issuer-uri) The discovery response would then provide the issuer on the public port (9420)

Hope this clarifies my expectations.