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.

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.

Appendix: 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)