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 JWT authenticator support for validating ID Tokens #13242

Merged
merged 8 commits into from
Mar 25, 2023

Conversation

a2l007
Copy link
Contributor

@a2l007 a2l007 commented Oct 19, 2022

Description

Expands the OIDC based auth in Druid by adding a JWT Authenticator that validates ID Tokens associated with a request. The existing pac4j authenticator works for authenticating web users while accessing the console, whereas this authenticator is for validating Druid API requests made by Direct clients. Services already supporting OIDC can attach their ID tokens to the Druid requests
under the Authorization request header.

This PR has:

  • been self-reviewed.
  • added documentation for new or modified features or behaviors.
  • been tested in a test Druid cluster.

@@ -26,6 +26,7 @@

public class OIDCConfig
{
private final String DEFAULT_SCOPE = "name";
Copy link
Contributor

Choose a reason for hiding this comment

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

The default claim to use for the identity is sub since name is not a standard OIDC claim

Copy link
Contributor Author

Choose a reason for hiding this comment

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

name is a standard claim according to the OIDC spec.

Copy link
Contributor

Choose a reason for hiding this comment

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

While it is a standard claim, it isn't mandatory. Some OIDC compliant auth servers don't include that claim in the ID token even when the profile scope is requested.

@JsonCreator
public OIDCConfig(
@JsonProperty("clientID") String clientID,
@JsonProperty("clientSecret") PasswordProvider clientSecret,
@JsonProperty("discoveryURI") String discoveryURI
@JsonProperty("discoveryURI") String discoveryURI,
@JsonProperty("oidcClaim") String oidcClaim
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this parameter is used to specify an arbitrary claim value as the Druid AuthenticationResult's identity, perhaps we can give it a name such as identityClaim

@abhishekagarwal87
Copy link
Contributor

@a2l007 - is there more work to be done for this PR?

@a2l007
Copy link
Contributor Author

a2l007 commented Feb 28, 2023

@a2l007 - is there more work to be done for this PR?

@abhishekagarwal87 No, this is good to go. There will be some work on this extension in a future PR when we upgrade the pac4j library dependencies. I haven't upgraded them yet since the newer versions are not backward compatible with JDK 8.

servletRequest.setAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT, authenticationResult);
} else {
LOG.error(
"Authentication failed! Please ensure the validity of the ID token and contains the configured claim.");
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
"Authentication failed! Please ensure the validity of the ID token and contains the configured claim.");
"Authentication failed! Please ensure that ID token is valid and it contains the configured claim.");

Copy link
Contributor

Choose a reason for hiding this comment

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

should we not reject the request at this point? If an id token is present, any error should result in an error. isn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My initial thought was to not break the authentication chain in case the user has any other custom authenticators further down the chain. But i see that if the request already has an id token, they have signaled intent to use that to authenticate.

{
String header = request.getHeader(HttpConstants.AUTHORIZATION_HEADER);
if (header == null || !header.startsWith(HttpConstants.BEARER_HEADER_PREFIX)) {
LOG.debug("Invalid prefix for Bearer token");
Copy link
Contributor

Choose a reason for hiding this comment

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

it doesn't really mean that prefix is invalid. It could be that this is not the right scheme.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed the error message here.

servletRequest.setAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT, authenticationResult);
} else {
LOG.error(
"Authentication failed! Please ensure the validity of the ID token and contains the configured claim.");
Copy link
Contributor

Choose a reason for hiding this comment

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

should we not reject the request at this point? If an id token is present, any error should result in an error. isn't it?

@a2l007
Copy link
Contributor Author

a2l007 commented Mar 24, 2023

Sorry for the delay @abhishekagarwal87
I've addressed your comments

@abhishekagarwal87 abhishekagarwal87 merged commit 19db32d into apache:master Mar 25, 2023
@clintropolis clintropolis added this to the 26.0 milestone Apr 10, 2023
// Parses the JWT and performs the ID Token validation specified in the OpenID spec: https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
IDTokenClaimsSet claims = tokenValidator.validate(JWTParser.parse(idToken.get()), null);
if (claims != null) {
Optional<String> claim = Optional.of(claims.getStringClaim(oidcConfig.getOidcClaim()));
Copy link
Member

Choose a reason for hiding this comment

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

Did you mean to use Optional.ofNullable here instead of Optional.of? This will currently throw an NPE if the claims are null.

if (claims != null) {
Optional<String> claim = Optional.of(claims.getStringClaim(oidcConfig.getOidcClaim()));

if (claim.isPresent()) {
Copy link
Member

Choose a reason for hiding this comment

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

this is always true since Optional.of will throw an NPE if claims.getStringClaim return null

Comment on lines +97 to +102
} else {
LOG.error(
"Authentication failed! Please ensure that the ID token is valid and it contains the configured claim.");
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
Copy link
Member

Choose a reason for hiding this comment

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

it sounds like we are missing a test case that handles the invalid claims code path, since this will never be executed as it is.

@a2l007
Copy link
Contributor Author

a2l007 commented Aug 18, 2023

@xvrl Thanks for the catch, I've raised a PR #14872 to fix it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants