spring-projects/spring-security · error · OAuth2AuthenticationException

invalid_client_metadata

invalid_client_metadata

Error message

Invalid Client Registration: jwks_uri

What it means

When dynamic client registration includes a jwks_uri, validateJwkSetUri requires it to use the https protocol; otherwise it throws invalid_client_metadata for the jwks_uri claim. This prevents clients from publishing signing keys over insecure transports where they could be tampered with.

Source

Thrown at oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientRegistrationAuthenticationValidator.java:208

			catch (URISyntaxException ex) {
				throw createException(OAuth2ErrorCodes.INVALID_REDIRECT_URI,
						OAuth2ClientMetadataClaimNames.REDIRECT_URIS);
			}
		}
	}

	private static void validateJwkSetUri(OAuth2ClientRegistrationAuthenticationContext authenticationContext) {
		OAuth2ClientRegistrationAuthenticationToken clientRegistrationAuthentication = authenticationContext
			.getAuthentication();
		URL jwkSetUrl = clientRegistrationAuthentication.getClientRegistration().getJwkSetUrl();
		if (jwkSetUrl == null) {
			return;
		}
		if (!"https".equalsIgnoreCase(jwkSetUrl.getProtocol())) {
			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug(LogMessage.format("Invalid request: jwks_uri does not use https ('%s')", jwkSetUrl));
			}
			throw createException("invalid_client_metadata", OAuth2ClientMetadataClaimNames.JWKS_URI);
		}
	}

	private static void validateJwkSetUriSimple(OAuth2ClientRegistrationAuthenticationContext authenticationContext) {
		// No validation. Preserves prior behavior.
	}

	private static void validateScope(OAuth2ClientRegistrationAuthenticationContext authenticationContext) {
		OAuth2ClientRegistrationAuthenticationToken clientRegistrationAuthentication = authenticationContext
			.getAuthentication();
		List<String> scopes = clientRegistrationAuthentication.getClientRegistration().getScopes();
		if (!CollectionUtils.isEmpty(scopes)) {
			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug(LogMessage.format(
						"Invalid request: scope must not be set during Dynamic Client Registration ('%s')", scopes));
			}
			throw createException(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ClientMetadataClaimNames.SCOPE);
		}

View on GitHub (pinned to 96852e8860)

Solutions

  1. Serve the JWKS over https and register the https URL (e.g. https://client.example.org/jwks.json)
  2. Put the JWKS behind a TLS-enabled reverse proxy or CDN
  3. If testing locally, use a tunnel (e.g. ngrok) to get an https URL, or inline keys via jwks instead of jwks_uri if supported

Example fix

// before
{"jwks_uri": "http://client.example.org/jwks.json"}
// after
{"jwks_uri": "https://client.example.org/jwks.json"}
Defensive patterns

Strategy: validation

Validate before calling

if (jwksUri != null && !jwksUri.startsWith("https://")) throw new IllegalArgumentException("jwks_uri must use https");

Type guard

boolean isHttpsUrl(String url) {
    try { return new java.net.URL(url).getProtocol().equalsIgnoreCase("https"); } catch (Exception e) { return false; }
}

Prevention

When it happens

Trigger: POSTing a client registration whose jwks_uri is 'http://...' (plain HTTP) or otherwise non-https, e.g. 'http://client.example.org/jwks.json'.

Common situations: Local development with http endpoints being registered against a production authorization server; self-hosted JWKS behind a TLS-terminating proxy exposed as http internally but registered as such; copying a localhost test URL into production registration.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/ff8d0cf8176ed095. Report an issue: GitHub.