Skip to content

Commit 91d8e8a

Browse files
authored
Add AWS CognitoAuthTokenProvider module (#536)
* Add AWS CognitoAuthTokenProvider module An HttpClient AuthTokenProvider for AWS Cognito * Packaging pom on aws-cognito * Release aws-cognito/http-client-authtoken independently
1 parent f371a20 commit 91d8e8a

File tree

7 files changed

+276
-0
lines changed

7 files changed

+276
-0
lines changed
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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+
<parent>
7+
<groupId>org.avaje</groupId>
8+
<artifactId>java11-oss</artifactId>
9+
<version>4.5</version>
10+
</parent>
11+
12+
<groupId>io.avaje.aws</groupId>
13+
<artifactId>avaje-cognito-client-token</artifactId>
14+
<version>1.0-RC1</version>
15+
16+
<dependencies>
17+
<dependency>
18+
<groupId>io.avaje</groupId>
19+
<artifactId>avaje-http-client</artifactId>
20+
<version>2.8</version>
21+
</dependency>
22+
23+
<dependency>
24+
<groupId>io.avaje</groupId>
25+
<artifactId>avaje-json-core</artifactId>
26+
<version>3.0-RC5</version>
27+
</dependency>
28+
29+
<!-- test dependencies -->
30+
<dependency>
31+
<groupId>io.avaje</groupId>
32+
<artifactId>avaje-json-node</artifactId>
33+
<version>3.0-RC5</version>
34+
<scope>test</scope>
35+
</dependency>
36+
37+
<dependency>
38+
<groupId>io.avaje</groupId>
39+
<artifactId>junit</artifactId>
40+
<version>1.5</version>
41+
<scope>test</scope>
42+
</dependency>
43+
</dependencies>
44+
45+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package io.avaje.aws.client.cognito;
2+
3+
import io.avaje.http.client.AuthToken;
4+
import io.avaje.http.client.AuthTokenProvider;
5+
import io.avaje.http.client.BasicAuthIntercept;
6+
import io.avaje.http.client.HttpClientRequest;
7+
import io.avaje.json.simple.SimpleMapper;
8+
9+
import java.net.http.HttpResponse;
10+
import java.time.Instant;
11+
12+
final class AmzCognitoAuthTokenProvider implements CognitoAuthTokenProvider.Builder {
13+
14+
private String url;
15+
private String clientId;
16+
private String clientSecret;
17+
private String scope;
18+
19+
@Override
20+
public CognitoAuthTokenProvider.Builder url(String url) {
21+
this.url = url;
22+
return this;
23+
}
24+
25+
@Override
26+
public CognitoAuthTokenProvider.Builder clientId(String clientId) {
27+
this.clientId = clientId;
28+
return this;
29+
}
30+
31+
@Override
32+
public CognitoAuthTokenProvider.Builder clientSecret(String clientSecret) {
33+
this.clientSecret = clientSecret;
34+
return this;
35+
}
36+
37+
@Override
38+
public CognitoAuthTokenProvider.Builder scope(String scope) {
39+
this.scope = scope;
40+
return this;
41+
}
42+
43+
@Override
44+
public AuthTokenProvider build() {
45+
return new Provider(url, clientId, clientSecret, scope);
46+
}
47+
48+
private static final class Provider implements AuthTokenProvider {
49+
50+
private static final SimpleMapper MAPPER = SimpleMapper.builder().build();
51+
52+
private final String url;
53+
private final String clientId;
54+
private final String scope;
55+
private final String authHeader;
56+
57+
public Provider(String url, String clientId, String clientSecret, String scope) {
58+
this.url = url;
59+
this.clientId = clientId;
60+
this.scope = scope;
61+
this.authHeader = "Basic " + BasicAuthIntercept.encode(clientId, clientSecret);
62+
}
63+
64+
@Override
65+
public AuthToken obtainToken(HttpClientRequest request) {
66+
HttpResponse<String> res = request
67+
.url(url)
68+
.header("Authorization", authHeader)
69+
.formParam("grant_type", "client_credentials")
70+
.formParam("client_id", clientId)
71+
.formParam("scope", scope)
72+
.POST()
73+
.asString();
74+
75+
if (res.statusCode() != 200) {
76+
throw new IllegalStateException("Error response getting access token statusCode:" + res.statusCode() + " res:" + res);
77+
}
78+
return decodeAuthToken(res.body());
79+
}
80+
81+
private AuthToken decodeAuthToken(String responseBody) {
82+
final var responseMap = MAPPER.fromJsonObject(responseBody);
83+
final var accessToken = (String) responseMap.get("access_token");
84+
final var expiresIn = (Long) responseMap.get("expires_in");
85+
86+
var validUntil = Instant.now()
87+
.plusSeconds(expiresIn)
88+
.minusSeconds(60);
89+
90+
return AuthToken.of(accessToken, validUntil);
91+
}
92+
}
93+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package io.avaje.aws.client.cognito;
2+
3+
import io.avaje.http.client.AuthTokenProvider;
4+
5+
/**
6+
* AuthTokenProvider for AWS Cognito providing Bearer access tokens.
7+
*
8+
* <pre>{@code
9+
*
10+
* AuthTokenProvider authTokenProvider = CognitoAuthTokenProvider.builder()
11+
* .url("https://foo.amazoncognito.com/oauth2/token")
12+
* .clientId("<something>")
13+
* .clientSecret("<something>")
14+
* .scope("default/default")
15+
* .build();
16+
*
17+
* // specify the authTokenProvider on the HttpClient ...
18+
*
19+
* HttpClient client = HttpClient.builder()
20+
* .authTokenProvider(authTokenProvider)
21+
* .baseUrl(myApplicationUrl)
22+
* .build();
23+
*
24+
* }</pre>
25+
*/
26+
public interface CognitoAuthTokenProvider extends AuthTokenProvider {
27+
28+
/**
29+
* Return a builder for the CognitoAuthTokenProvider.
30+
*/
31+
static Builder builder() {
32+
return new AmzCognitoAuthTokenProvider();
33+
}
34+
35+
/**
36+
* The builder for the AWS Cognito AuthTokenProvider.
37+
*/
38+
interface Builder {
39+
40+
/**
41+
* Set the url used to obtain access tokens.
42+
*/
43+
Builder url(String url);
44+
45+
/**
46+
* Set the clientId.
47+
*/
48+
Builder clientId(String clientId);
49+
50+
/**
51+
* Set the clientSecret.
52+
*/
53+
Builder clientSecret(String clientSecret);
54+
55+
/**
56+
* Set the scope.
57+
*/
58+
Builder scope(String scope);
59+
60+
/**
61+
* Build and return the AuthTokenProvider.
62+
*/
63+
AuthTokenProvider build();
64+
}
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module io.avaje.aws.client.cognito {
2+
3+
exports io.avaje.aws.client.cognito;
4+
5+
requires transitive io.avaje.http.client;
6+
requires transitive io.avaje.json;
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package io.avaje.aws.client.cognito;
2+
3+
import io.avaje.http.client.AuthTokenProvider;
4+
import io.avaje.http.client.HttpClientRequest;
5+
import io.avaje.http.client.HttpClientResponse;
6+
import org.junit.jupiter.api.Test;
7+
8+
import java.net.http.HttpResponse;
9+
10+
import static org.mockito.ArgumentMatchers.anyString;
11+
import static org.mockito.Mockito.*;
12+
13+
@SuppressWarnings("unchecked")
14+
class CognitoAuthTokenProviderTest {
15+
16+
@Test
17+
void obtainToken() {
18+
final String url = "https://<something>.amazoncognito.com/oauth2/token";
19+
final String clientId = "<something>";
20+
final String clientSecret = "<something>";
21+
22+
AuthTokenProvider authTokenProvider = CognitoAuthTokenProvider.builder()
23+
.url(url)
24+
.clientId(clientId)
25+
.clientSecret(clientSecret)
26+
.scope("default/default")
27+
.build();
28+
29+
HttpResponse<String> httpResponse = mock(HttpResponse.class);
30+
when(httpResponse.statusCode()).thenReturn(200);
31+
when(httpResponse.body()).thenReturn("{\"access_token\":\"1234\",\"expires_in\":3600}");
32+
33+
HttpClientResponse clientResponse = mock(HttpClientResponse.class);
34+
when(clientResponse.asString()).thenReturn(httpResponse);
35+
36+
HttpClientRequest httpClientRequest = mock(HttpClientRequest.class);
37+
when(httpClientRequest.url(anyString())).thenReturn(httpClientRequest);
38+
39+
when(httpClientRequest.header(anyString(), anyString())).thenReturn(httpClientRequest);
40+
when(httpClientRequest.formParam(anyString(), anyString())).thenReturn(httpClientRequest);
41+
when(httpClientRequest.POST()).thenReturn(clientResponse);
42+
43+
// act
44+
authTokenProvider.obtainToken(httpClientRequest);
45+
46+
// verify the Authorization header has been obtained and set
47+
verify(httpClientRequest).header("Authorization", "Basic PHNvbWV0aGluZz46PHNvbWV0aGluZz4=");
48+
}
49+
}

aws-cognito/pom.xml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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+
<parent>
7+
<groupId>org.avaje</groupId>
8+
<artifactId>java11-oss</artifactId>
9+
<version>4.5</version>
10+
</parent>
11+
12+
<artifactId>aws-cognito</artifactId>
13+
<packaging>pom</packaging>
14+
15+
</project>

pom.xml

+2
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@
7575
<profile>
7676
<id>test21</id>
7777
<modules>
78+
<!-- release http-client-authtoken on its own cadence -->
79+
<module>aws-cognito/http-client-authtoken</module>
7880
<module>htmx-nima</module>
7981
<module>htmx-nima-jstache</module>
8082
<module>http-generator-helidon</module>

0 commit comments

Comments
 (0)