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

Support Certificate-Bound (POP) Opaque Access Token Validation #14888

Open
jgrandja opened this issue Apr 11, 2024 · 6 comments
Open

Support Certificate-Bound (POP) Opaque Access Token Validation #14888

jgrandja opened this issue Apr 11, 2024 · 6 comments
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) type: enhancement A general enhancement

Comments

@jgrandja
Copy link
Contributor

This issue is related to gh-10538 and will address validating certificate-bound "opaque" access tokens for both the Servlet and Reactive stacks.

@jgrandja jgrandja added status: waiting-for-triage An issue we've not yet triaged type: enhancement A general enhancement in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) and removed status: waiting-for-triage An issue we've not yet triaged labels Apr 11, 2024
@CrazyParanoid
Copy link

CrazyParanoid commented Apr 16, 2024

Hi @jgrandja ! It seems to me that such validation is impossible for opaque tokens, for example, in my case, the token is an ordinary guid. But even if we consider the rare case - jwt-based opaque token, then the introspection endpoint call must perform all the checks on its own and return the correct result. Currently NimbusOpaqueTokenIntrospector does not perform default validations, at most it does this:

        @Override
	public OAuth2AuthenticatedPrincipal introspect(String token) {
		RequestEntity<?> requestEntity = this.requestEntityConverter.convert(token);
		if (requestEntity == null) {
			throw new OAuth2IntrospectionException("requestEntityConverter returned a null entity");
		}
		ResponseEntity<String> responseEntity = makeRequest(requestEntity);
		HTTPResponse httpResponse = adaptToNimbusResponse(responseEntity);
		TokenIntrospectionResponse introspectionResponse = parseNimbusResponse(httpResponse);
		TokenIntrospectionSuccessResponse introspectionSuccessResponse = castToNimbusSuccess(introspectionResponse);
		// relying solely on the authorization server to validate this token (not checking
		// 'exp', for example)
		if (!introspectionSuccessResponse.isActive()) {
			this.logger.trace("Did not validate token since it is inactive");
			throw new BadOpaqueTokenException("Provided token isn't active");
		}
		return convertClaimsSet(introspectionSuccessResponse);
	}

It's written in the comments: "relying solely on the authorization server to validate this token (not checking 'exp', for example)"

@jgrandja
Copy link
Contributor Author

@CrazyParanoid

It seems to me that such validation is impossible for opaque tokens

Anything is possible. But currently there is no OAuth2TokenValidator associated with OpaqueTokenAuthenticationProvider, which is likely what is needed. This is my initial thought but that might change.

Either way, let's see how many upvotes this issue gets before we prioritize it in a release.

@jzheaux
Copy link
Contributor

jzheaux commented Apr 25, 2024

It seems to me that such validation is impossible for opaque tokens

I think the API limitation is that OpaqueTokenIntrospector takes a string. This is because the token is opaque in the Introspection spec. Based on https://datatracker.ietf.org/doc/html/draft-ietf-oauth-pop-architecture-08#section-7.3, the certificate is verified directly by the resource server and not as part of the introspection handshake (See Figure 4).

In that case, it may be more appropriate to have the OpaqueTokenAuthenticationProvider validate the certificate, conditioned on a value in BearerTokenAuthenticationToken or a subclass.

@CrazyParanoid
Copy link

CrazyParanoid commented Apr 26, 2024

Maybe it would be better to make this task more global? For example, Support Opaque Token Validation. You can add an interface something like:

public interface OpaqueBearerTokenValidator<T extends BearerTokenAuthentication> {

	OAuth2TokenValidatorResult validate(T token);
}

Then in OpaqueTokenAuthenticationProvider :

        @Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		if (!(authentication instanceof BearerTokenAuthenticationToken bearer)) {
			return null;
		}
		OAuth2AuthenticatedPrincipal principal = getOAuth2AuthenticatedPrincipal(bearer);
		Authentication result = this.authenticationConverter.convert(bearer.getToken(), principal);
		if (result == null) {
			return null;
		}

		OAuth2TokenValidatorResult validationResult = validator.validate(result);
		if (validationResult.hasErrors()) {
			Collection<OAuth2Error> errors = validationResult.getErrors();
			String validationErrorString = getOpaqueTokenValidationMessage(errors);
			throw new OpaqueTokenValidationException(validationErrorString, errors);
		}

		if (AbstractAuthenticationToken.class.isAssignableFrom(result.getClass())) {
			final AbstractAuthenticationToken auth = (AbstractAuthenticationToken) result;
			if (auth.getDetails() == null) {
				auth.setDetails(bearer.getDetails());
			}
		}
		this.logger.debug("Authenticated token");
		return result;
	}

But even if this is implemented, it is necessary to make such validations optional, for example through setting setOpaqueTokenValidation(true).

@jzheaux
Copy link
Contributor

jzheaux commented Apr 26, 2024

I don't think this would be appropriate in this case since such validation is not part of the introspection spec and thus not a part of the token. Further, the information that would be needed (the certificate itself) is neither part of the access token being introspected (it's just a String in this case) nor is it part of the Authorization Server response.

As per the diagram, the certificate is handed by the client to the resource server, separately from the token. The token is introspected, and the certificate is verified separately from that.

@CrazyParanoid
Copy link

I probably expressed myself incorrectly when I said that this is impossible. This is not practical. Since the authorization server performs all validation during the introspection, there is no point in implementing such validations in the framework.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

3 participants