Skip to content

Commit

Permalink
Add AWS CognitoAuthTokenProvider module (#536)
Browse files Browse the repository at this point in the history
* Add AWS CognitoAuthTokenProvider module

An HttpClient AuthTokenProvider for AWS Cognito

* Packaging pom on aws-cognito

* Release aws-cognito/http-client-authtoken independently
  • Loading branch information
rbygrave authored Dec 17, 2024
1 parent f371a20 commit 91d8e8a
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 0 deletions.
45 changes: 45 additions & 0 deletions aws-cognito/http-client-authtoken/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.avaje</groupId>
<artifactId>java11-oss</artifactId>
<version>4.5</version>
</parent>

<groupId>io.avaje.aws</groupId>
<artifactId>avaje-cognito-client-token</artifactId>
<version>1.0-RC1</version>

<dependencies>
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-http-client</artifactId>
<version>2.8</version>
</dependency>

<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-json-core</artifactId>
<version>3.0-RC5</version>
</dependency>

<!-- test dependencies -->
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-json-node</artifactId>
<version>3.0-RC5</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.avaje</groupId>
<artifactId>junit</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package io.avaje.aws.client.cognito;

import io.avaje.http.client.AuthToken;
import io.avaje.http.client.AuthTokenProvider;
import io.avaje.http.client.BasicAuthIntercept;
import io.avaje.http.client.HttpClientRequest;
import io.avaje.json.simple.SimpleMapper;

import java.net.http.HttpResponse;
import java.time.Instant;

final class AmzCognitoAuthTokenProvider implements CognitoAuthTokenProvider.Builder {

private String url;
private String clientId;
private String clientSecret;
private String scope;

@Override
public CognitoAuthTokenProvider.Builder url(String url) {
this.url = url;
return this;
}

@Override
public CognitoAuthTokenProvider.Builder clientId(String clientId) {
this.clientId = clientId;
return this;
}

@Override
public CognitoAuthTokenProvider.Builder clientSecret(String clientSecret) {
this.clientSecret = clientSecret;
return this;
}

@Override
public CognitoAuthTokenProvider.Builder scope(String scope) {
this.scope = scope;
return this;
}

@Override
public AuthTokenProvider build() {
return new Provider(url, clientId, clientSecret, scope);
}

private static final class Provider implements AuthTokenProvider {

private static final SimpleMapper MAPPER = SimpleMapper.builder().build();

private final String url;
private final String clientId;
private final String scope;
private final String authHeader;

public Provider(String url, String clientId, String clientSecret, String scope) {
this.url = url;
this.clientId = clientId;
this.scope = scope;
this.authHeader = "Basic " + BasicAuthIntercept.encode(clientId, clientSecret);
}

@Override
public AuthToken obtainToken(HttpClientRequest request) {
HttpResponse<String> res = request
.url(url)
.header("Authorization", authHeader)
.formParam("grant_type", "client_credentials")
.formParam("client_id", clientId)
.formParam("scope", scope)
.POST()
.asString();

if (res.statusCode() != 200) {
throw new IllegalStateException("Error response getting access token statusCode:" + res.statusCode() + " res:" + res);
}
return decodeAuthToken(res.body());
}

private AuthToken decodeAuthToken(String responseBody) {
final var responseMap = MAPPER.fromJsonObject(responseBody);
final var accessToken = (String) responseMap.get("access_token");
final var expiresIn = (Long) responseMap.get("expires_in");

var validUntil = Instant.now()
.plusSeconds(expiresIn)
.minusSeconds(60);

return AuthToken.of(accessToken, validUntil);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package io.avaje.aws.client.cognito;

import io.avaje.http.client.AuthTokenProvider;

/**
* AuthTokenProvider for AWS Cognito providing Bearer access tokens.
*
* <pre>{@code
*
* AuthTokenProvider authTokenProvider = CognitoAuthTokenProvider.builder()
* .url("https://foo.amazoncognito.com/oauth2/token")
* .clientId("<something>")
* .clientSecret("<something>")
* .scope("default/default")
* .build();
*
* // specify the authTokenProvider on the HttpClient ...
*
* HttpClient client = HttpClient.builder()
* .authTokenProvider(authTokenProvider)
* .baseUrl(myApplicationUrl)
* .build();
*
* }</pre>
*/
public interface CognitoAuthTokenProvider extends AuthTokenProvider {

/**
* Return a builder for the CognitoAuthTokenProvider.
*/
static Builder builder() {
return new AmzCognitoAuthTokenProvider();
}

/**
* The builder for the AWS Cognito AuthTokenProvider.
*/
interface Builder {

/**
* Set the url used to obtain access tokens.
*/
Builder url(String url);

/**
* Set the clientId.
*/
Builder clientId(String clientId);

/**
* Set the clientSecret.
*/
Builder clientSecret(String clientSecret);

/**
* Set the scope.
*/
Builder scope(String scope);

/**
* Build and return the AuthTokenProvider.
*/
AuthTokenProvider build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module io.avaje.aws.client.cognito {

exports io.avaje.aws.client.cognito;

requires transitive io.avaje.http.client;
requires transitive io.avaje.json;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package io.avaje.aws.client.cognito;

import io.avaje.http.client.AuthTokenProvider;
import io.avaje.http.client.HttpClientRequest;
import io.avaje.http.client.HttpClientResponse;
import org.junit.jupiter.api.Test;

import java.net.http.HttpResponse;

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;

@SuppressWarnings("unchecked")
class CognitoAuthTokenProviderTest {

@Test
void obtainToken() {
final String url = "https://<something>.amazoncognito.com/oauth2/token";
final String clientId = "<something>";
final String clientSecret = "<something>";

AuthTokenProvider authTokenProvider = CognitoAuthTokenProvider.builder()
.url(url)
.clientId(clientId)
.clientSecret(clientSecret)
.scope("default/default")
.build();

HttpResponse<String> httpResponse = mock(HttpResponse.class);
when(httpResponse.statusCode()).thenReturn(200);
when(httpResponse.body()).thenReturn("{\"access_token\":\"1234\",\"expires_in\":3600}");

HttpClientResponse clientResponse = mock(HttpClientResponse.class);
when(clientResponse.asString()).thenReturn(httpResponse);

HttpClientRequest httpClientRequest = mock(HttpClientRequest.class);
when(httpClientRequest.url(anyString())).thenReturn(httpClientRequest);

when(httpClientRequest.header(anyString(), anyString())).thenReturn(httpClientRequest);
when(httpClientRequest.formParam(anyString(), anyString())).thenReturn(httpClientRequest);
when(httpClientRequest.POST()).thenReturn(clientResponse);

// act
authTokenProvider.obtainToken(httpClientRequest);

// verify the Authorization header has been obtained and set
verify(httpClientRequest).header("Authorization", "Basic PHNvbWV0aGluZz46PHNvbWV0aGluZz4=");
}
}
15 changes: 15 additions & 0 deletions aws-cognito/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.avaje</groupId>
<artifactId>java11-oss</artifactId>
<version>4.5</version>
</parent>

<artifactId>aws-cognito</artifactId>
<packaging>pom</packaging>

</project>
2 changes: 2 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@
<profile>
<id>test21</id>
<modules>
<!-- release http-client-authtoken on its own cadence -->
<module>aws-cognito/http-client-authtoken</module>
<module>htmx-nima</module>
<module>htmx-nima-jstache</module>
<module>http-generator-helidon</module>
Expand Down

0 comments on commit 91d8e8a

Please sign in to comment.