Skip to content

Conversation

@MrBurtyyy
Copy link
Collaborator

…thentication tokens via API

Copy link
Collaborator

@pn-santos pn-santos left a comment

Choose a reason for hiding this comment

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

I'm not that familiar with the current practices in the SDK nor any stylistic conventions in place so apologies in advance if any of the comments are out-of-sync with how stuff is done in the SDKs.

Also, given I'm not really involved with the SDKs don't take the comments as blockers. I'm not going to explicitly approve, I'll leave that to someone more attuned to the SDK code base to take that responsibility, but I don't see anything functional that would be a reason to hold it up.

public CreateAuthenticationTokenResponse generate(List<String> scopes) throws ResourceException, IOException {
String jwts = signedJwtGenerator.create(sdkId, keyPair, jwtIdSupplier, authApiUrl);

Map<String, String> formParams = new HashMap<>(DEFAULT_FORM_VALUES);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it really worth having a constant for DEFAULT_FORM_VALUES? It's only used here, why not inline the put here?

formParams.put("scope", String.join(" ", scopes));
formParams.put("client_assertion", jwts);
formParams.put("grant_type", "client_credentials");
formParams.put("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");

it's one less map instance to keep/copy and initialise on startup.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed 👍

private final SignedJwtGenerator signedJwtGenerator;

private final String authApiUrl;
private final ObjectMapper objectMapper;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is the ObjectMapper being injected/exposed?

The CreateAuthenticationTokenResponse is defined by this module, shouldn't the ObjectMapper (and it's config) also be owned/defined by this module? What would be the point of the client providing their own instance config if they do not control the annotations/layout of CreateAuthenticationTokenResponse?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We don't expose the ObjectMapper to the Relying Business, but it's done this way (and in other places in the SDK) to make testing with mocks easier in the AuthenticationTokenGenerator

private final FormRequestClient formRequestClient;
private final SignedJwtGenerator signedJwtGenerator;

private final String authApiUrl;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Wouldn't this be better as a URI or URL type?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed 👍

Comment on lines 149 to 151
ObjectMapper objectMapper = new ObjectMapper()
.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Copy link
Collaborator

Choose a reason for hiding this comment

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

If we do not expose the ObjectMapper then there's no point in making it an instance var. It should be declared as a static final var of AuthenticationTokenGenerator

.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

return new AuthenticationTokenGenerator(sdkId, loadKeyPair(keyPairSource), jwtIdSupplier, new FormRequestClient(), new SignedJwtGenerator(), objectMapper);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not all that familiar with the current conventions in this project, but I feel like with this many arguments this would be a bit more readable with one arg per line:

Suggested change
return new AuthenticationTokenGenerator(sdkId, loadKeyPair(keyPairSource), jwtIdSupplier, new FormRequestClient(), new SignedJwtGenerator(), objectMapper);
return new AuthenticationTokenGenerator(
sdkId,
loadKeyPair(keyPairSource),
jwtIdSupplier,
new FormRequestClient(),
new SignedJwtGenerator(),
objectMapper
);

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed 👍

Comment on lines 30 to 32
String urlParams = getDataString(formParams);

byte[] postData = urlParams.getBytes(StandardCharsets.UTF_8);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why return String and then do getBytes(...) rather than simply return byte[] from the get go? urlParams doesn't seem to be used anywhere else and what this (method) seems to need is the byte[] not String

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed 👍

Comment on lines 92 to 94
String responseBody = formRequestClient.performRequest(authApiUrl, "POST", formParams);

return objectMapper.readValue(responseBody, CreateAuthenticationTokenResponse.class);
Copy link
Collaborator

Choose a reason for hiding this comment

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

If all we are doing is de-serializing the response, why return String instead of the raw byte[]?

This ends up creating String instances from byte[] just to immediately convert them to a POJO instance. We could go directly from byte[] to POJO.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed 👍

Comment on lines 78 to 99
return scanner.get().hasNext() ? scanner.get().next() : "";
}
}

private static Scanner createScanner(InputStream inputStream) {
return new Scanner(inputStream, DEFAULT_CHARSET).useDelimiter("\\A");
}

private byte[] readBody(HttpURLConnection httpURLConnection) throws IOException {
try (QuietCloseable<InputStream> inputStream = new QuietCloseable<>(httpURLConnection.getInputStream())) {
return readChunked(inputStream.get());
}
}

private byte[] readChunked(InputStream inputStream) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] byteChunk = new byte[4096];
int n;
while ((n = inputStream.read(byteChunk)) > 0) {
byteArrayOutputStream.write(byteChunk, 0, n);
}
return byteArrayOutputStream.toByteArray();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why the difference in strategy for reading error payloads and non-error payloads?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This was taken from one of our other (old) components from the regular SDK module. I have fixed it 👍

this.signedJwtGenerator = signedJwtGenerator;
this.objectMapper = objectMapper;

authApiUrl = System.getProperty(Properties.PROPERTY_YOTI_AUTH_URL, Properties.DEFAULT_YOTI_AUTH_URL);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This class is the sole user of this Properties class... why not simply declare those values as constants here? i.e.

private static final String YOTI_AUTH_URL_KEY = "yoti.auth.url";

(...)

authApiUrl = System.getProperty(YOTI_AUTH_URL_KEY, "https://auth.api.yoti.com/v1/oauth/token");

and get rid of Properties altogether?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We have public properties in here, so the YOTI_AUTH_URL_KEY property can be accessed if we want to override the URL without knowing the actual key needed (like we do in the other modules).

Not sure if we want to change this, @bucky-boy what do you think?

Comment on lines 16 to 18
final class SignedJwtGenerator {

String create(String sdkId, KeyPair keyPair, Supplier<String> jwtIdSupplier, String authApiUrl) {
Copy link
Collaborator

@pn-santos pn-santos Dec 15, 2025

Choose a reason for hiding this comment

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

Seems kinda pointless to have a class that doesn't implement an interface, doesn't have instance member variables, and has a single method that is used in one place. Why not simply inline the method where it's used as a private method?

Copy link
Collaborator Author

@MrBurtyyy MrBurtyyy Dec 16, 2025

Choose a reason for hiding this comment

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

Fixed 👍 (this was originally done to make mocking easier, but I have since found we have utility libraries to help us create actual KeyPairs)

String jwts = signedJwtGenerator.create(sdkId, keyPair, jwtIdSupplier, authApiUrl);

Map<String, String> formParams = new HashMap<>(DEFAULT_FORM_VALUES);
formParams.put("scope", String.join(" ", scopes));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should this list be checked for null or empty string?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed 👍

* @throws IOException
*/
public CreateAuthenticationTokenResponse generate(List<String> scopes) throws ResourceException, IOException {
String jwts = signedJwtGenerator.create(sdkId, keyPair, jwtIdSupplier, authApiUrl);
Copy link
Collaborator

Choose a reason for hiding this comment

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

can't this be inlined?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed 👍

@sonarqubecloud
Copy link

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants