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

JWT federated client authentication #3219

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from

Conversation

strehle
Copy link
Member

@strehle strehle commented Jan 6, 2025

Support integration of so called workload identities

Allow client authentication based on RFC 7523, see https://oauth.net/private-key-jwt/

Scenario: Integrate JWT from another systems to authentication within UAA / CF landscape.
e.g. github action token like https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect#understanding-the-oidc-token

* WIP: JWT federated client authentication

* Implement JWT credential

* Add documentation
Copy link
Contributor

@Kehrlann Kehrlann left a comment

Choose a reason for hiding this comment

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

First of all, great work, this is a complicated topic. I had a hard time wrapping my head around it 🤯

My main issue is that this JWT authentication + grant type is complex and hard to understand. There are three things handled in the same place (OAuth2 jwt-bearer grant type + OAuth2 client authentication + OIDC client authentication). It makes it really hard to understand what should happen and when.
[suggestion] While we are touching this, would be super helpful to decompose the whole flow a bit rather than add more code in the same place. Maybe have multiple ClientJwtAuthentication classes or methods that map to a specific flow with a link to the spec.
I don't have an easy suggestion of what should be done but we should at least consider it.

I also left a few suggestions (use assertJ, a typo here and there, suggestions for method names), some are easy fixes and some are just non-blocking suggestions.

@@ -71,10 +71,10 @@ protected void additionalAuthenticationChecks(UserDetails userDetails, UsernameP
setAuthenticationMethod(authentication, CLIENT_AUTH_NONE);
break;
} else if (isPrivateKeyJwt(authentication.getDetails())) {
setAuthenticationMethod(authentication, CLIENT_AUTH_PRIVATE_KEY_JWT);
Copy link
Contributor

Choose a reason for hiding this comment

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

[question, non-blocking] Is the order important here? If so, why?

if (!clientId.equals(getClientId(clientAssertion))) {
JWT clientJWT = parseClientAssertion(clientAssertion);
JWTClaimsSet clientClaims = parseClientJWT(clientJWT);
if (!clientId.equals(getClientId(clientClaims))) {
Copy link
Contributor

Choose a reason for hiding this comment

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

[suggestion] Worth clarifying that this is for "authorization grant" case of the OAuth2 flow, where the sub is the subject of the user who got the token, not the client ID.

@@ -37,7 +37,7 @@ void log_format_whenThereIsAnAuthType() {
ArgumentCaptor<String> stringCaptor = ArgumentCaptor.forClass(String.class);
verify(mockLogger).info(stringCaptor.capture());
String logMessage = stringCaptor.getValue();
assertThat(logMessage, is("PasswordChangeFailure ('theData'): principal=thePrincipalId, origin=[theOrigin], identityZoneId=[theZoneId], authenticationType=[theAuthType]"));
assertThat(logMessage, is("PasswordChangeFailure ('theData'): principal=thePrincipalId, origin=[theOrigin], identityZoneId=[theZoneId], authenticationType=[theAuthType], detailedDescription=[theDescription]"));
Copy link
Contributor

Choose a reason for hiding this comment

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

[suggestion] use assertJ instead, see other comments - there will probably be conflicts here with gh-3195

map.put("authorities", "uaa.none");
map.put("redirect-uri", "http://localhost/callback");
map.put("jwt_creds", "[{\"iss\":\"http://localhost:8080/uaa/oauth/token\",\"sub\":\"foo-jwt\"}]");
UaaClientDetails clientDetails = (UaaClientDetails) doSimpleTest(map, clientAdminBootstrap, multitenantJdbcClientDetailsService, clients);
Copy link
Contributor

Choose a reason for hiding this comment

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

[thought, non-blocking] doSimpleTest is a questionable name 😅

@@ -137,35 +150,121 @@ public boolean validateClientJwt(Map<String, String[]> requestParameters, Client
if (GRANT_TYPE.equals(UaaStringUtils.getSafeParameterValue(requestParameters.get(CLIENT_ASSERTION_TYPE)))) {
Copy link
Contributor

Choose a reason for hiding this comment

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

[suggestion] noticed that this is NOT the grant_type as per spec, it's the client_assertion_type ; the grant type exist and is defined in TokenConstants#GRANT_TYPE_JWT_BEARER. We should probably use this PR to rename this parameter, maybe CLIENT_ASSERTION_TYPE_JWT_BEARER or something similar.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In Progress
Status: Waiting for Changes | Open for Contribution
2 participants