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

Add support for access token in body parameter as per rfc 6750 Sec. 2.2 #15819

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

jonah1und1
Copy link

Currently, the reactive stack does not allow for authentication via parameter in body of post requests.
RFC-6750 Sec. 2.2 allows this. It is also support by mvc stack.

Related ticket: gh-15818.

@pivotal-cla
Copy link

@jonah1und1 Please sign the Contributor License Agreement!

Click here to manually synchronize the status of this Pull Request.

See the FAQ for frequently asked questions.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Sep 17, 2024
@sjohnr sjohnr self-assigned this Sep 19, 2024
@sjohnr sjohnr added type: enhancement A general enhancement in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) labels Sep 19, 2024
@pivotal-cla
Copy link

@jonah1und1 Thank you for signing the Contributor License Agreement!

@sjohnr sjohnr added this to the 6.5.x milestone Oct 24, 2024
Copy link
Member

@sjohnr sjohnr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks much for the PR @jonah1und1. Apologies for the delay. I have some feedback inline. Please note that I will hold off merging until after the GA release, since we are in the RC phase of 6.4 and will need to wait to introduce new APIs (setAllowFormEncodedBodyParameter()) until 6.5.

Comment on lines +81 to +83
return Flux.merge(resolveFromAuthorizationHeader(request.getHeaders()).map(s -> Tuples.of(s, TokenSource.HEADER)),
resolveAccessTokenFromRequest(request).map(s -> Tuples.of(s, TokenSource.QUERY_PARAMETER)),
resolveAccessTokenFromBody(exchange).map(s -> Tuples.of(s, TokenSource.BODY_PARAMETER)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to resolve without the creation of Tuples here? It is generally preferred to avoid creation of additional objects when possible (e.g. Stream) and I think Tuple would fall in the same category here. Instead of the TokenSource, I believe Mono#switchIfEmpty could be used.

Copy link
Author

@jonah1und1 jonah1und1 Oct 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am sorry, I fail to see how to resolve this without some kind of information where the token is from, to then check whether it is supported. Could you please give me an hint on how to use Mono#switchIfEmpty here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My mistake. Since you're using Flux.merge(), it would actually be Flux#switchIfEmpty.

The new resolveAccessTokenFromBody() checks a boolean, method and content-type before returning a Mono. The resolveAccessTokenFromRequest() method can be refactored to do the same. In that case, Flux.merge() will return some number of tokens (0-3) that don't require checking the source again. It would end up looking something like this:

@Override
public Mono<Authentication> convert(ServerWebExchange exchange) {
	return Mono.defer(() -> token(exchange));
}

private Mono<Authentication> token(ServerWebExchange exchange) {
	ServerHttpRequest request = exchange.getRequest();
	return Flux.merge(resolveFromAuthorizationHeader(request.getHeaders()),
			resolveAccessTokenFromRequest(exchange.getRequest()),
			resolveAccessTokenFromBody(exchange))
		.switchIfEmpty(Mono.error(new OAuth2AuthenticationException(invalidTokenError())))
		.collectList()
		.flatMap((accessTokens) -> {
			if (accessTokens.size() > 1) {
				BearerTokenError error = BearerTokenErrors
					.invalidRequest("Found multiple bearer tokens in the request");
				return Mono.error(new OAuth2AuthenticationException(error));
			}

			return Mono.just(new BearerTokenAuthenticationToken(accessTokens.get(0)));
		});
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your answer!
Unfortunately, I think there might be two problems with this approach:

Firstly, if no token can be found the current implementation of ServerBearerTokenAuthenticationConverter#convert() returns an empty mono. An invalidTokenError() is only raised if the String containing the token is empty, but not if it's null.
This could be mitigated with relative ease imo, by leaving convert() in its current state and replacing switchIfEmpty() by a second if-clause inside of .flatMap():

 if (accessTokens.isEmpty()) {
    return Mono.empty();
}

Secondly, the tests resolveWhenValidHeaderIsPresentTogetherWithQueryParameterThenAuthenticationExceptionIsThrown() and resolveWhenQueryParameterHasMultipleAccessTokensThenOAuth2AuthenticationException() fail because they do not set allowUriQueryParameter to true first. While I am not quite sure about the expected behaviour here and whether adding this.converter.setAllowUriQueryParameter(true) to both tests is acceptable, doing so might change the current behaviour.
resolveWhenValidQueryParameterIsPresentTogetherWithBodyParameterThenAuthenticationExceptionIsThrown(), which was added by me, also fails because the query parameter access token is filtered due to the POST-request.

Copy link
Member

@sjohnr sjohnr Oct 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Firstly, if no token can be found the current implementation of ServerBearerTokenAuthenticationConverter#convert() returns an empty mono. An invalidTokenError() is only raised if the String containing the token is empty, but not if it's null.

Hmm, fair point.

Secondly, the tests [...] do not set allowUriQueryParameter to true first.

Also a fair point. This is unfortunate, because it could be argued that leaving this false implies that such parameters should be ignored. The test and original implementation don't agree though and that makes this tricky. I think this comes down to whether it would be considered a bug to read the token from the request (e.g. query string or body parameters) and validate it when it is not explicitly enabled.

The spec states that

Resource servers MAY support this method.

for both body parameters and query string parameters. I don't see anything in the Error Codes section indicating that the request MUST be validated for these problems if the server doesn't support a particular method for resolving the token.

For those reasons, I'm inclined to open an bug to necessitate a change in behavior for both servlet and reactive implementations that would be a precursor to this enhancement. What do you think?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the late reply!
Sounds sound to open a dedicated bug ticket for that. Do you want me to implement a PR for that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jonah1und1 I opened gh-16038. I think it would be nice to get this fixed in the release this month. Feel free to open a PR if you have availability to work on it, or if you're busy let me know and I can submit a fix.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a PR: #16039

@sjohnr sjohnr removed the status: waiting-for-triage An issue we've not yet triaged label Oct 30, 2024
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
Status: In Progress
Development

Successfully merging this pull request may close these issues.

4 participants