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

Comment From: franticticktick

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)"

Comment From: jgrandja

@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.

Comment From: jzheaux

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.

Comment From: franticticktick

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).

Comment From: jzheaux

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.

Comment From: franticticktick

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.