Skip to content

Commit 9d1586b

Browse files
committed
SDK-2771: Add new maven module for supporting the creation of Yoti authentication tokens via API
1 parent b1d4a4f commit 9d1586b

9 files changed

Lines changed: 596 additions & 0 deletions

File tree

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<modules>
1414
<module>yoti-sdk-parent</module>
1515
<module>yoti-sdk-api</module>
16+
<module>yoti-sdk-auth</module>
1617
<module>yoti-sdk-sandbox</module>
1718
<module>yoti-sdk-spring-boot-auto-config</module>
1819
<module>yoti-sdk-spring-security</module>

yoti-sdk-auth/pom.xml

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>com.yoti</groupId>
9+
<artifactId>yoti-sdk-parent</artifactId>
10+
<version>4.0.0-SNAPSHOT</version>
11+
<relativePath>../yoti-sdk-parent</relativePath>
12+
</parent>
13+
14+
<artifactId>yoti-sdk-auth</artifactId>
15+
16+
<properties>
17+
<jjwt.version>0.13.0</jjwt.version>
18+
</properties>
19+
20+
<dependencies>
21+
<dependency>
22+
<groupId>org.bouncycastle</groupId>
23+
<artifactId>bcpkix-jdk18on</artifactId>
24+
</dependency>
25+
<dependency>
26+
<groupId>com.yoti</groupId>
27+
<artifactId>yoti-sdk-api</artifactId>
28+
</dependency>
29+
<dependency>
30+
<groupId>io.jsonwebtoken</groupId>
31+
<artifactId>jjwt-api</artifactId>
32+
<version>${jjwt.version}</version>
33+
</dependency>
34+
<dependency>
35+
<groupId>com.fasterxml.jackson.core</groupId>
36+
<artifactId>jackson-databind</artifactId>
37+
</dependency>
38+
<dependency>
39+
<groupId>commons-logging</groupId>
40+
<artifactId>commons-logging</artifactId>
41+
<version>1.1.1</version>
42+
</dependency>
43+
<dependency>
44+
<groupId>io.jsonwebtoken</groupId>
45+
<artifactId>jjwt-impl</artifactId>
46+
<version>${jjwt.version}</version>
47+
<scope>runtime</scope>
48+
</dependency>
49+
<dependency>
50+
<groupId>io.jsonwebtoken</groupId>
51+
<artifactId>jjwt-jackson</artifactId>
52+
<version>${jjwt.version}</version>
53+
<scope>runtime</scope>
54+
</dependency>
55+
56+
<!-- Testing dependencies -->
57+
<dependency>
58+
<groupId>org.hamcrest</groupId>
59+
<artifactId>hamcrest-library</artifactId>
60+
<scope>test</scope>
61+
</dependency>
62+
<dependency>
63+
<groupId>junit</groupId>
64+
<artifactId>junit</artifactId>
65+
<scope>test</scope>
66+
</dependency>
67+
<dependency>
68+
<groupId>org.mockito</groupId>
69+
<artifactId>mockito-core</artifactId>
70+
<scope>test</scope>
71+
</dependency>
72+
73+
</dependencies>
74+
75+
<build>
76+
<plugins>
77+
<plugin>
78+
<groupId>org.owasp</groupId>
79+
<artifactId>dependency-check-maven</artifactId>
80+
</plugin>
81+
<plugin>
82+
<groupId>org.codehaus.mojo</groupId>
83+
<artifactId>animal-sniffer-maven-plugin</artifactId>
84+
</plugin>
85+
<plugin>
86+
<artifactId>maven-enforcer-plugin</artifactId>
87+
</plugin>
88+
<plugin>
89+
<artifactId>maven-compiler-plugin</artifactId>
90+
<version>${maven-compiler-plugin.version}</version>
91+
</plugin>
92+
<plugin>
93+
<artifactId>maven-source-plugin</artifactId>
94+
</plugin>
95+
<plugin>
96+
<artifactId>maven-javadoc-plugin</artifactId>
97+
</plugin>
98+
<plugin>
99+
<groupId>org.jacoco</groupId>
100+
<artifactId>jacoco-maven-plugin</artifactId>
101+
</plugin>
102+
</plugins>
103+
</build>
104+
105+
<reporting>
106+
<plugins>
107+
<plugin>
108+
<artifactId>maven-project-info-reports-plugin</artifactId>
109+
</plugin>
110+
</plugins>
111+
</reporting>
112+
113+
</project>
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package com.yoti.auth;
2+
3+
import static com.yoti.validation.Validation.notNull;
4+
import static com.yoti.validation.Validation.notNullOrEmpty;
5+
6+
import java.io.IOException;
7+
import java.security.KeyPair;
8+
import java.util.HashMap;
9+
import java.util.List;
10+
import java.util.Map;
11+
import java.util.UUID;
12+
import java.util.function.Supplier;
13+
14+
import com.yoti.api.client.InitialisationException;
15+
import com.yoti.api.client.KeyPairSource;
16+
import com.yoti.api.client.spi.remote.KeyStreamVisitor;
17+
import com.yoti.api.client.spi.remote.call.ResourceException;
18+
19+
import com.fasterxml.jackson.databind.DeserializationFeature;
20+
import com.fasterxml.jackson.databind.ObjectMapper;
21+
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
22+
23+
/**
24+
* The {@link AuthenticationTokenGenerator} is used for generation authorization tokens
25+
* that can be used for accessing Yoti services. An authorization token must have
26+
* a unique identifier, and an expiry timestamp. One or more scopes can be provided
27+
* to allow the authorization token access to different parts of Yoti systems.
28+
* <p>
29+
* The {@link AuthenticationTokenGenerator.Builder} can be accessed via {@code AuthorizationTokenGenerator.builder()}
30+
* method, and then configured via the fluent API.
31+
*/
32+
public class AuthenticationTokenGenerator {
33+
34+
private static final Map<String, String> DEFAULT_FORM_VALUES;
35+
36+
static {
37+
DEFAULT_FORM_VALUES = new HashMap<>();
38+
DEFAULT_FORM_VALUES.put("grant_type", "client_credentials");
39+
DEFAULT_FORM_VALUES.put("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
40+
}
41+
42+
private final String sdkId;
43+
private final KeyPair keyPair;
44+
private final Supplier<String> jwtIdSupplier;
45+
private final FormRequestClient formRequestClient;
46+
private final SignedJwtGenerator signedJwtGenerator;
47+
48+
private final String authApiUrl;
49+
private final ObjectMapper objectMapper;
50+
51+
AuthenticationTokenGenerator(
52+
String sdkId,
53+
KeyPair keyPair,
54+
Supplier<String> jwtIdSupplier,
55+
FormRequestClient formRequestClient,
56+
SignedJwtGenerator signedJwtGenerator,
57+
ObjectMapper objectMapper) {
58+
this.sdkId = sdkId;
59+
this.keyPair = keyPair;
60+
this.jwtIdSupplier = jwtIdSupplier;
61+
this.formRequestClient = formRequestClient;
62+
this.signedJwtGenerator = signedJwtGenerator;
63+
this.objectMapper = objectMapper;
64+
65+
authApiUrl = System.getProperty(Properties.PROPERTY_YOTI_AUTH_URL, Properties.DEFAULT_YOTI_AUTH_URL);
66+
}
67+
68+
/**
69+
* Creates a new instance of {@link AuthenticationTokenGenerator.Builder}
70+
*
71+
* @return the builder
72+
*/
73+
public static AuthenticationTokenGenerator.Builder builder() {
74+
return new AuthenticationTokenGenerator.Builder();
75+
}
76+
77+
/**
78+
* Creates a new authentication token, using the supplied scopes and comment.
79+
*
80+
* @param scopes a list of scopes to be used by the authentication token
81+
* @return a {@link CreateAuthenticationTokenResponse} containing information about the created token.
82+
* @throws Exception
83+
*/
84+
public CreateAuthenticationTokenResponse generate(List<String> scopes) throws ResourceException, IOException {
85+
String jwts = signedJwtGenerator.create(sdkId, keyPair, jwtIdSupplier, authApiUrl);
86+
87+
Map<String, String> formParams = new HashMap<>(DEFAULT_FORM_VALUES);
88+
formParams.put("scope", String.join(" ", scopes));
89+
formParams.put("client_assertion", jwts);
90+
91+
String responseBody = formRequestClient.performRequest(authApiUrl, "POST", formParams);
92+
93+
return objectMapper.readValue(responseBody, CreateAuthenticationTokenResponse.class);
94+
}
95+
96+
public static final class Builder {
97+
98+
private String sdkId;
99+
private KeyPairSource keyPairSource;
100+
private Supplier<String> jwtIdSupplier = () -> UUID.randomUUID().toString();
101+
102+
private Builder() {}
103+
104+
/**
105+
* Sets the SDK ID that the authorization token will be generated against.
106+
*
107+
* @param sdkId the SDK ID
108+
* @return the builder for method chaining.
109+
*/
110+
public Builder withSdkId(String sdkId) {
111+
this.sdkId = sdkId;
112+
return this;
113+
}
114+
115+
/**
116+
* Sets the {@link KeyPairSource} that will be used to load the {@link KeyPair}
117+
*
118+
* @param keyPairSource the key pair source that will be used to load the {@link KeyPair}
119+
* @return the builder for method chaining.
120+
*/
121+
public Builder withKeyPairSource(KeyPairSource keyPairSource) {
122+
this.keyPairSource = keyPairSource;
123+
return this;
124+
}
125+
126+
/**
127+
* Sets the supplier that will be used to generate a unique ID for the
128+
* authorization token. By default, this will be a UUID v4.
129+
*
130+
* @param jwtIdSupplier the supplier used for generating authorization token ID
131+
* @return the builder for method chaining.
132+
*/
133+
public Builder withJwtIdSupplier(Supplier<String> jwtIdSupplier) {
134+
this.jwtIdSupplier = jwtIdSupplier;
135+
return this;
136+
}
137+
138+
/**
139+
* Builds an {@link AuthenticationTokenGenerator} using the values supplied to the {@link Builder}.
140+
*
141+
* @return the configured {@link AuthenticationTokenGenerator}
142+
*/
143+
public AuthenticationTokenGenerator build() {
144+
notNullOrEmpty(sdkId, "sdkId");
145+
notNull(keyPairSource, "keyPairSource");
146+
notNull(jwtIdSupplier, "jwtIdSupplier");
147+
148+
ObjectMapper objectMapper = new ObjectMapper()
149+
.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
150+
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
151+
152+
return new AuthenticationTokenGenerator(sdkId, loadKeyPair(keyPairSource), jwtIdSupplier, new FormRequestClient(), new SignedJwtGenerator(), objectMapper);
153+
}
154+
155+
private KeyPair loadKeyPair(KeyPairSource kpSource) throws InitialisationException {
156+
try {
157+
return kpSource.getFromStream(new KeyStreamVisitor());
158+
} catch (IOException e) {
159+
throw new InitialisationException("Cannot load key pair", e);
160+
}
161+
}
162+
163+
}
164+
165+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.yoti.auth;
2+
3+
public final class CreateAuthenticationTokenResponse {
4+
5+
private String accessToken;
6+
private String tokenType;
7+
private Integer expiresIn;
8+
private String scope;
9+
10+
/**
11+
* Returns the Yoti Authentication token used to perform requests to other Yoti services.
12+
*
13+
* @return the newly created access token
14+
*/
15+
public String getAccessToken() {
16+
return accessToken;
17+
}
18+
19+
/**
20+
* Returns the type of the newly generated authentication token.
21+
*
22+
* @return the token type
23+
*/
24+
public String getTokenType() {
25+
return tokenType;
26+
}
27+
28+
/**
29+
* Returns the amount of time (in seconds) in which the newly generated Authentication Token
30+
* will expire in.
31+
*
32+
* @return the time (in seconds) of when the token will expire
33+
*/
34+
public Integer getExpiresIn() {
35+
return expiresIn;
36+
}
37+
38+
/**
39+
* A whitespace delimited string of scopes that the Authentication token has.
40+
*
41+
* @return the scopes of the token as a whitespace delimited string
42+
*/
43+
public String getScope() {
44+
return scope;
45+
}
46+
47+
}

0 commit comments

Comments
 (0)