diff --git a/pom.xml b/pom.xml
index ce3bf6fc9..5679810ab 100644
--- a/pom.xml
+++ b/pom.xml
@@ -44,6 +44,7 @@
spring-cloud-aws-sqs
spring-cloud-aws-dynamodb
spring-cloud-aws-s3
+ spring-cloud-aws-cognito
spring-cloud-aws-testcontainers
spring-cloud-aws-starters/spring-cloud-aws-starter
spring-cloud-aws-starters/spring-cloud-aws-starter-dynamodb
@@ -54,6 +55,7 @@
spring-cloud-aws-starters/spring-cloud-aws-starter-ses
spring-cloud-aws-starters/spring-cloud-aws-starter-sns
spring-cloud-aws-starters/spring-cloud-aws-starter-sqs
+ spring-cloud-aws-starters/spring-cloud-aws-starter-cognito
spring-cloud-aws-samples
spring-cloud-aws-test
spring-cloud-aws-modulith
diff --git a/spring-cloud-aws-autoconfigure/pom.xml b/spring-cloud-aws-autoconfigure/pom.xml
index cd6f485c7..a3b0c1d60 100644
--- a/spring-cloud-aws-autoconfigure/pom.xml
+++ b/spring-cloud-aws-autoconfigure/pom.xml
@@ -86,6 +86,11 @@
spring-cloud-aws-s3
true
+
+ io.awspring.cloud
+ spring-cloud-aws-cognito
+ true
+
software.amazon.dax
amazon-dax-client
diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cognito/CognitoAutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cognito/CognitoAutoConfiguration.java
new file mode 100644
index 000000000..55d4151fe
--- /dev/null
+++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cognito/CognitoAutoConfiguration.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud.autoconfigure.cognito;
+
+import io.awspring.cloud.autoconfigure.AwsSyncClientCustomizer;
+import io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer;
+import io.awspring.cloud.autoconfigure.core.AwsConnectionDetails;
+import io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration;
+import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration;
+import io.awspring.cloud.cognito.CognitoTemplate;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient;
+
+/**
+ * {@link AutoConfiguration Auto-Configuration} for AWS Cognito integration.
+ *
+ * @author Oleh Onufryk
+ * @since 3.3.0
+ */
+
+@AutoConfiguration
+@EnableConfigurationProperties(CognitoProperties.class)
+@ConditionalOnClass({ CognitoIdentityProviderClient.class })
+@AutoConfigureAfter({ CredentialsProviderAutoConfiguration.class, RegionProviderAutoConfiguration.class })
+@ConditionalOnProperty(name = "spring.cloud.aws.cognito.enabled", havingValue = "true", matchIfMissing = true)
+public class CognitoAutoConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public CognitoIdentityProviderClient cognitoIdentityProviderClient(CognitoProperties cognitoProperties,
+ AwsClientBuilderConfigurer awsClientBuilderConfigurer, ObjectProvider customizers,
+ ObjectProvider awsSyncClientCustomizers,
+ ObjectProvider connectionDetails) {
+ return awsClientBuilderConfigurer.configureSyncClient(CognitoIdentityProviderClient.builder(),
+ cognitoProperties, connectionDetails.getIfAvailable(), customizers.orderedStream(),
+ awsSyncClientCustomizers.orderedStream()).build();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ @ConditionalOnProperty(name = { "spring.cloud.aws.cognito.client-id", "spring.cloud.aws.cognito.user-pool-id" })
+ public CognitoTemplate cognitoTemplate(CognitoProperties cognitoProperties,
+ CognitoIdentityProviderClient cognitoIdentityProviderClient) {
+ return new CognitoTemplate(cognitoIdentityProviderClient, cognitoProperties.getClientId(),
+ cognitoProperties.getUserPoolId(), cognitoProperties.getClientSecret());
+ }
+}
diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cognito/CognitoClientCustomizer.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cognito/CognitoClientCustomizer.java
new file mode 100644
index 000000000..c2e72316f
--- /dev/null
+++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cognito/CognitoClientCustomizer.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud.autoconfigure.cognito;
+
+import io.awspring.cloud.autoconfigure.AwsClientCustomizer;
+import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClientBuilder;
+
+@FunctionalInterface
+public interface CognitoClientCustomizer extends AwsClientCustomizer {
+}
diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cognito/CognitoProperties.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cognito/CognitoProperties.java
new file mode 100644
index 000000000..40bd94d3a
--- /dev/null
+++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cognito/CognitoProperties.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud.autoconfigure.cognito;
+
+import io.awspring.cloud.autoconfigure.AwsClientProperties;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Configuration properties for AWS Cognito Integration
+ *
+ * @author Oleh Onufryk
+ * @since 3.3.0
+ */
+
+@ConfigurationProperties(CognitoProperties.CONFIG_PREFIX)
+public class CognitoProperties extends AwsClientProperties {
+
+ /**
+ * Configuration prefix.
+ */
+ public static final String CONFIG_PREFIX = "spring.cloud.aws.cognito";
+
+ /**
+ * The user pool ID.
+ */
+ private String userPoolId;
+
+ /**
+ * The client ID.
+ */
+ private String clientId;
+
+ /**
+ * The client secret.
+ */
+ private String clientSecret;
+
+ public String getUserPoolId() {
+ return userPoolId;
+ }
+
+ public void setUserPoolId(String userPoolId) {
+ this.userPoolId = userPoolId;
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ public String getClientSecret() {
+ return clientSecret;
+ }
+
+ public void setClientSecret(String clientSecret) {
+ this.clientSecret = clientSecret;
+ }
+}
diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cognito/package-info.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cognito/package-info.java
new file mode 100644
index 000000000..81c589992
--- /dev/null
+++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/cognito/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * {@link org.springframework.boot.context.config.ConfigDataLoader} implementation for AWS Cognito.
+ */
+@org.springframework.lang.NonNullApi
+@org.springframework.lang.NonNullFields
+package io.awspring.cloud.autoconfigure.cognito;
diff --git a/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index 657cbf245..665fe659b 100644
--- a/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/spring-cloud-aws-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -15,3 +15,4 @@ io.awspring.cloud.autoconfigure.config.secretsmanager.SecretsManagerAutoConfigur
io.awspring.cloud.autoconfigure.config.parameterstore.ParameterStoreReloadAutoConfiguration
io.awspring.cloud.autoconfigure.config.parameterstore.ParameterStoreAutoConfiguration
io.awspring.cloud.autoconfigure.config.s3.S3ReloadAutoConfiguration
+io.awspring.cloud.autoconfigure.cognito.CognitoAutoConfiguration
diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cognito/CognitoAutoConfigurationTest.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cognito/CognitoAutoConfigurationTest.java
new file mode 100644
index 000000000..de0d4c0a8
--- /dev/null
+++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/cognito/CognitoAutoConfigurationTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud.autoconfigure.cognito;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+import io.awspring.cloud.autoconfigure.core.AwsAutoConfiguration;
+import io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration;
+import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.context.annotation.Bean;
+import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient;
+
+/**
+ * Test for {@link CognitoAutoConfiguration}
+ */
+class CognitoAutoConfigurationTest {
+
+ private final ApplicationContextRunner runner = new ApplicationContextRunner()
+ .withPropertyValues("spring.cloud.aws.region.static:eu-west-1")
+ .withConfiguration(AutoConfigurations.of(RegionProviderAutoConfiguration.class,
+ CredentialsProviderAutoConfiguration.class, CognitoAutoConfiguration.class,
+ AwsAutoConfiguration.class));
+
+ @Test
+ void cognitoAutoConfigurationIsDisabled() {
+ this.runner.withPropertyValues("spring.cloud.aws.cognito.enabled:false")
+ .run(context -> assertThat(context).doesNotHaveBean(CognitoIdentityProviderClient.class));
+ }
+
+ @Test
+ void cognitoAutoConfigurationIsEnabled() {
+ this.runner.withPropertyValues("spring.cloud.aws.cognito.enabled:true")
+ .run(context -> assertThat(context).hasSingleBean(CognitoIdentityProviderClient.class));
+ }
+
+ @Test
+ void createCognitoClientBeanByDefault() {
+ this.runner.run(context -> assertThat(context).hasSingleBean(CognitoAutoConfiguration.class));
+ }
+
+ @Test
+ void usesCustomBeanWhenProvided() {
+ this.runner.withUserConfiguration(CustomCognitoConfiguration.class).run(context -> {
+ assertThat(context).hasSingleBean(CognitoIdentityProviderClient.class);
+ assertThat(context.getBean(CognitoIdentityProviderClient.class)).isNotNull();
+ });
+ }
+
+ @TestConfiguration
+ static class CustomCognitoConfiguration {
+ @Bean
+ CognitoIdentityProviderClient cognitoIdentityProviderClient() {
+ return mock(CognitoIdentityProviderClient.class);
+ }
+ }
+}
diff --git a/spring-cloud-aws-cognito/pom.xml b/spring-cloud-aws-cognito/pom.xml
new file mode 100644
index 000000000..6484a97df
--- /dev/null
+++ b/spring-cloud-aws-cognito/pom.xml
@@ -0,0 +1,27 @@
+
+
+ 4.0.0
+
+
+ io.awspring.cloud
+ spring-cloud-aws
+ 3.3.0-SNAPSHOT
+
+
+ spring-cloud-aws-cognito
+ Spring Cloud AWS Cognito Integration
+
+
+
+ software.amazon.awssdk
+ cognitoidentityprovider
+
+
+ org.springframework
+ spring-core
+
+
+
+
diff --git a/spring-cloud-aws-cognito/src/main/java/io/awspring/cloud/cognito/CognitoAuthOperations.java b/spring-cloud-aws-cognito/src/main/java/io/awspring/cloud/cognito/CognitoAuthOperations.java
new file mode 100644
index 000000000..5cbbc2f2e
--- /dev/null
+++ b/spring-cloud-aws-cognito/src/main/java/io/awspring/cloud/cognito/CognitoAuthOperations.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2013-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud.cognito;
+
+import java.util.List;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.AdminCreateUserResponse;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.AdminInitiateAuthResponse;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.AttributeType;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.ConfirmForgotPasswordResponse;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.ForgotPasswordResponse;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeResponse;
+
+/**
+ * An Interface for the most common Cognito auth operations
+ *
+ * @author Oleh Onufryk
+ * @since 3.3.0
+ */
+
+public interface CognitoAuthOperations {
+
+ /**
+ * Logs in a user using username and password
+ * @param username - the username
+ * @param password - the password
+ * @return {@link AdminInitiateAuthResponse} a result of login operation from the AWS Cognito
+ */
+ AdminInitiateAuthResponse login(String username, String password);
+
+ /**
+ * Creates a new user with provided attributes
+ * @param username - the username
+ * @param password - the password
+ * @param attributeTypes - the list of user attributes defined by user pool
+ * @return {@link AdminCreateUserResponse} a result of user creation operation from the AWS Cognito
+ */
+ AdminCreateUserResponse createUser(String username, String password, List attributeTypes);
+
+ /**
+ * Resets password for a user
+ * @param username - the username
+ * @return {@link ForgotPasswordResponse} a result of password reset operation from the AWS Cognito
+ */
+ ForgotPasswordResponse resetPassword(String username);
+
+ /**
+ * Confirms password reset
+ * @param username - the username
+ * @param confirmationCode - the confirmation code for password reset operation
+ * @param newPassword - the new password
+ * @return {@link ConfirmForgotPasswordResponse} a result of password reset confirmation operation from the AWS
+ * Cognito
+ */
+ ConfirmForgotPasswordResponse confirmResetPassword(String username, String confirmationCode, String newPassword);
+
+ /**
+ * Sets a permanent password for a new user
+ * @param session - the session id returned by the login operation
+ * @param username - the username of the user
+ * @param password - the permanent password for user's account
+ * @return {@link RespondToAuthChallengeResponse} a result of setting permanent password operation from the AWS
+ * Cognito
+ */
+ RespondToAuthChallengeResponse setPermanentPassword(String session, String username, String password);
+
+ /**
+ * Invalidates user's access, id and refresh tokens
+ * @param userName - the username
+ */
+ void logout(String userName);
+
+}
diff --git a/spring-cloud-aws-cognito/src/main/java/io/awspring/cloud/cognito/CognitoParameters.java b/spring-cloud-aws-cognito/src/main/java/io/awspring/cloud/cognito/CognitoParameters.java
new file mode 100644
index 000000000..96a79954a
--- /dev/null
+++ b/spring-cloud-aws-cognito/src/main/java/io/awspring/cloud/cognito/CognitoParameters.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud.cognito;
+
+/**
+ * Parameters used in AWS Cognito operations.
+ *
+ * @author Oleh Onufryk
+ * @since 3.3.0
+ */
+
+public final class CognitoParameters {
+
+ private CognitoParameters() {
+ }
+
+ /**
+ * Parameter represents username for a user.
+ */
+ public static final String USERNAME_PARAM_NAME = "USERNAME";
+
+ /**
+ * Parameter represents password for a user.
+ */
+ public static final String PASSWORD_PARAM_NAME = "PASSWORD";
+
+ /**
+ * Parameter represents a compute secret hash for a user.
+ */
+ public static final String SECRET_HASH_PARAM_NAME = "SECRET_HASH";
+
+ /**
+ * Parameter represents a new password for a user.
+ */
+ public static final String NEW_PASSWORD_PARAM_NAME = "NEW_PASSWORD";
+}
diff --git a/spring-cloud-aws-cognito/src/main/java/io/awspring/cloud/cognito/CognitoTemplate.java b/spring-cloud-aws-cognito/src/main/java/io/awspring/cloud/cognito/CognitoTemplate.java
new file mode 100644
index 000000000..385849e33
--- /dev/null
+++ b/spring-cloud-aws-cognito/src/main/java/io/awspring/cloud/cognito/CognitoTemplate.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2013-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud.cognito;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.springframework.util.Assert;
+import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.AdminCreateUserRequest;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.AdminCreateUserResponse;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.AdminInitiateAuthRequest;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.AdminInitiateAuthResponse;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.AdminUserGlobalSignOutRequest;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.AttributeType;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthFlowType;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.ChallengeNameType;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.ConfirmForgotPasswordRequest;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.ConfirmForgotPasswordResponse;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.ForgotPasswordRequest;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.ForgotPasswordResponse;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeRequest;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeResponse;
+
+/**
+ * Higher level abstraction over {@link CognitoIdentityProviderClient} providing methods for the most common auth
+ * operations
+ *
+ * @author Oleh Onufryk
+ * @since 3.3.0
+ */
+
+public class CognitoTemplate implements CognitoAuthOperations {
+
+ private final CognitoIdentityProviderClient cognitoIdentityProviderClient;
+ private final String clientId;
+ private final String userPoolId;
+ private final String clientSecret;
+
+ public CognitoTemplate(CognitoIdentityProviderClient cognitoIdentityProviderClient, String clientId,
+ String userPoolId, String clientSecret) {
+ Assert.notNull(cognitoIdentityProviderClient, "cognitoIdentityProviderClient is required");
+ Assert.notNull(clientId, "clientId is required");
+ Assert.notNull(userPoolId, "userPoolId is required");
+ this.cognitoIdentityProviderClient = cognitoIdentityProviderClient;
+ this.clientId = clientId;
+ this.userPoolId = userPoolId;
+ this.clientSecret = clientSecret;
+ }
+
+ @Override
+ public AdminInitiateAuthResponse login(String username, String password) {
+ AdminInitiateAuthRequest adminInitiateAuthRequest = AdminInitiateAuthRequest.builder().userPoolId(userPoolId)
+ .clientId(clientId).authFlow(AuthFlowType.ADMIN_USER_PASSWORD_AUTH)
+ .authParameters(resolveAuthParameters(username, password)).build();
+ return cognitoIdentityProviderClient.adminInitiateAuth(adminInitiateAuthRequest);
+ }
+
+ @Override
+ public AdminCreateUserResponse createUser(String username, String password, List attributeTypes) {
+ AdminCreateUserRequest createUserRequest = AdminCreateUserRequest.builder().userPoolId(userPoolId)
+ .username(username).temporaryPassword(password).userAttributes(attributeTypes).build();
+ return cognitoIdentityProviderClient.adminCreateUser(createUserRequest);
+ }
+
+ @Override
+ public ForgotPasswordResponse resetPassword(String username) {
+ ForgotPasswordRequest.Builder forgotPasswordRequestBuilder = ForgotPasswordRequest.builder().clientId(clientId)
+ .username(username);
+ if (this.clientSecret != null) {
+ forgotPasswordRequestBuilder.secretHash(CognitoUtils.calculateSecretHash(clientId, clientSecret, username));
+ }
+ ForgotPasswordRequest forgotPasswordRequest = forgotPasswordRequestBuilder.build();
+
+ return cognitoIdentityProviderClient.forgotPassword(forgotPasswordRequest);
+ }
+
+ @Override
+ public ConfirmForgotPasswordResponse confirmResetPassword(String username, String confirmationCode,
+ String newPassword) {
+ ConfirmForgotPasswordRequest.Builder confirmForgotPasswordRequestBuilder = ConfirmForgotPasswordRequest
+ .builder().clientId(clientId).username(username).password(newPassword)
+ .confirmationCode(confirmationCode);
+
+ if (this.clientSecret != null) {
+ confirmForgotPasswordRequestBuilder
+ .secretHash(CognitoUtils.calculateSecretHash(clientId, clientSecret, username));
+ }
+ ConfirmForgotPasswordRequest confirmForgotPasswordRequest = confirmForgotPasswordRequestBuilder.build();
+ return cognitoIdentityProviderClient.confirmForgotPassword(confirmForgotPasswordRequest);
+ }
+
+ @Override
+ public RespondToAuthChallengeResponse setPermanentPassword(String session, String username, String password) {
+ Map resetPasswordParametersMap = new HashMap<>();
+ resetPasswordParametersMap.put(CognitoParameters.USERNAME_PARAM_NAME, username);
+ resetPasswordParametersMap.put(CognitoParameters.NEW_PASSWORD_PARAM_NAME, password);
+
+ if (this.clientSecret != null) {
+ resetPasswordParametersMap.put(CognitoParameters.SECRET_HASH_PARAM_NAME,
+ CognitoUtils.calculateSecretHash(clientId, clientSecret, username));
+ }
+ RespondToAuthChallengeRequest respondToAuthChallengeRequest = RespondToAuthChallengeRequest.builder()
+ .clientId(clientId).challengeName(ChallengeNameType.NEW_PASSWORD_REQUIRED).session(session)
+ .challengeResponses(resetPasswordParametersMap).build();
+ return cognitoIdentityProviderClient.respondToAuthChallenge(respondToAuthChallengeRequest);
+ }
+
+ @Override
+ public void logout(String userName) {
+ var signOutRequest = AdminUserGlobalSignOutRequest.builder().userPoolId(this.userPoolId).username(userName)
+ .build();
+
+ cognitoIdentityProviderClient.adminUserGlobalSignOut(signOutRequest);
+ }
+
+ private Map resolveAuthParameters(String username, String password) {
+ Map parametersMap = new HashMap<>();
+ parametersMap.put(CognitoParameters.USERNAME_PARAM_NAME, username);
+ parametersMap.put(CognitoParameters.PASSWORD_PARAM_NAME, password);
+ if (this.clientSecret != null) {
+ parametersMap.put(CognitoParameters.SECRET_HASH_PARAM_NAME,
+ CognitoUtils.calculateSecretHash(clientId, clientSecret, username));
+ }
+ return parametersMap;
+ }
+}
diff --git a/spring-cloud-aws-cognito/src/main/java/io/awspring/cloud/cognito/CognitoUtils.java b/spring-cloud-aws-cognito/src/main/java/io/awspring/cloud/cognito/CognitoUtils.java
new file mode 100644
index 000000000..7547c3343
--- /dev/null
+++ b/spring-cloud-aws-cognito/src/main/java/io/awspring/cloud/cognito/CognitoUtils.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud.cognito;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Utility class for Cognito operations.
+ *
+ * @author Oleh Onufryk
+ * @since 3.3.0
+ */
+public class CognitoUtils {
+
+ private CognitoUtils() {
+ }
+
+ // https://docs.aws.amazon.com/cognito/latest/developerguide/signing-up-users-in-your-app.html#cognito-user-pools-computing-secret-hash
+ public static String calculateSecretHash(String userPoolClientId, String userPoolClientSecret, String userName) {
+ final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
+ SecretKeySpec signingKey = new SecretKeySpec(userPoolClientSecret.getBytes(StandardCharsets.UTF_8),
+ HMAC_SHA256_ALGORITHM);
+ try {
+ Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
+ mac.init(signingKey);
+ mac.update(userName.getBytes(StandardCharsets.UTF_8));
+ byte[] rawHmac = mac.doFinal(userPoolClientId.getBytes(StandardCharsets.UTF_8));
+ return Base64.getEncoder().encodeToString(rawHmac);
+ }
+ catch (Exception e) {
+ throw new RuntimeException("Error while calculating secret hash for " + userName);
+ }
+ }
+}
diff --git a/spring-cloud-aws-cognito/src/main/java/io/awspring/cloud/cognito/package-info.java b/spring-cloud-aws-cognito/src/main/java/io/awspring/cloud/cognito/package-info.java
new file mode 100644
index 000000000..01bad4a7c
--- /dev/null
+++ b/spring-cloud-aws-cognito/src/main/java/io/awspring/cloud/cognito/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * AWS Cognito integration.
+ */
+@org.springframework.lang.NonNullApi
+@org.springframework.lang.NonNullFields
+package io.awspring.cloud.cognito;
diff --git a/spring-cloud-aws-cognito/src/test/java/io/awspring/cloud/cognito/CognitoTemplateTest.java b/spring-cloud-aws-cognito/src/test/java/io/awspring/cloud/cognito/CognitoTemplateTest.java
new file mode 100644
index 000000000..2a4f11827
--- /dev/null
+++ b/spring-cloud-aws-cognito/src/test/java/io/awspring/cloud/cognito/CognitoTemplateTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud.cognito;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.AdminCreateUserRequest;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.AdminInitiateAuthRequest;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.AdminUserGlobalSignOutRequest;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.AttributeType;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthFlowType;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.ChallengeNameType;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.ConfirmForgotPasswordRequest;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.ForgotPasswordRequest;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeRequest;
+
+/**
+ * Tests for {@link CognitoTemplate}.
+ *
+ * @author Oleh Onufryk
+ * @since 3.3.0
+ */
+class CognitoTemplateTest {
+
+ private static final String USERNAME = "foo@bar.com";
+ private static final String PASSWORD = "password";
+ private final CognitoIdentityProviderClient cognitoIdentityProviderClient = mock(
+ CognitoIdentityProviderClient.class);
+
+ private final CognitoTemplate cognitoTemplate = new CognitoTemplate(cognitoIdentityProviderClient, "clientId",
+ "userPoolId", "clientSecret");
+
+ @Test
+ void createUser() {
+ AdminCreateUserRequest request = AdminCreateUserRequest.builder().userPoolId("userPoolId").username(USERNAME)
+ .temporaryPassword(PASSWORD).userAttributes(createAttributes()).build();
+ cognitoTemplate.createUser(USERNAME, PASSWORD, createAttributes());
+
+ verify(cognitoIdentityProviderClient).adminCreateUser(request);
+ }
+
+ @Test
+ void login() {
+ AdminInitiateAuthRequest initiateAuthRequest = AdminInitiateAuthRequest.builder().userPoolId("userPoolId")
+ .clientId("clientId").authFlow(AuthFlowType.ADMIN_USER_PASSWORD_AUTH)
+ .authParameters(resolveAuthParameters()).build();
+
+ cognitoTemplate.login(USERNAME, PASSWORD);
+
+ verify(cognitoIdentityProviderClient).adminInitiateAuth(initiateAuthRequest);
+ }
+
+ @Test
+ void resetPassword() {
+ ForgotPasswordRequest forgotPasswordRequest = ForgotPasswordRequest.builder().clientId("clientId")
+ .secretHash(CognitoUtils.calculateSecretHash("clientId", "clientSecret", USERNAME)).username(USERNAME)
+ .build();
+
+ cognitoTemplate.resetPassword(USERNAME);
+
+ verify(cognitoIdentityProviderClient).forgotPassword(forgotPasswordRequest);
+ }
+
+ @Test
+ void confirmResetPassword() {
+ ConfirmForgotPasswordRequest forgotPasswordRequest = ConfirmForgotPasswordRequest.builder().clientId("clientId")
+ .username(USERNAME).password("newPassword").confirmationCode("confirmationCode")
+ .secretHash(CognitoUtils.calculateSecretHash("clientId", "clientSecret", USERNAME)).build();
+
+ cognitoTemplate.confirmResetPassword(USERNAME, "confirmationCode", "newPassword");
+
+ verify(cognitoIdentityProviderClient).confirmForgotPassword(forgotPasswordRequest);
+ }
+
+ @Test
+ void setPermanentPassword() {
+ RespondToAuthChallengeRequest permanentPasswordRequest = RespondToAuthChallengeRequest.builder()
+ .clientId("clientId").challengeName(ChallengeNameType.NEW_PASSWORD_REQUIRED).session("session")
+ .challengeResponses(Map.of(CognitoParameters.USERNAME_PARAM_NAME, USERNAME,
+ CognitoParameters.NEW_PASSWORD_PARAM_NAME, PASSWORD, CognitoParameters.SECRET_HASH_PARAM_NAME,
+ CognitoUtils.calculateSecretHash("clientId", "clientSecret", USERNAME)))
+ .build();
+
+ cognitoTemplate.setPermanentPassword("session", USERNAME, PASSWORD);
+
+ verify(cognitoIdentityProviderClient).respondToAuthChallenge(permanentPasswordRequest);
+ }
+
+ @Test
+ void logout() {
+ AdminUserGlobalSignOutRequest logoutRequest = AdminUserGlobalSignOutRequest.builder().userPoolId("userPoolId")
+ .username(USERNAME).build();
+
+ cognitoTemplate.logout(USERNAME);
+
+ verify(cognitoIdentityProviderClient).adminUserGlobalSignOut(logoutRequest);
+ }
+
+ private List createAttributes() {
+ return List.of(AttributeType.builder().name("email").value("foo@bar.com").build());
+ }
+
+ private Map resolveAuthParameters() {
+ Map parametersMap = new HashMap<>();
+ parametersMap.put(CognitoParameters.USERNAME_PARAM_NAME, CognitoTemplateTest.USERNAME);
+ parametersMap.put(CognitoParameters.PASSWORD_PARAM_NAME, CognitoTemplateTest.PASSWORD);
+ parametersMap.put(CognitoParameters.SECRET_HASH_PARAM_NAME,
+ CognitoUtils.calculateSecretHash("clientId", "clientSecret", CognitoTemplateTest.USERNAME));
+ return parametersMap;
+ }
+
+}
diff --git a/spring-cloud-aws-dependencies/pom.xml b/spring-cloud-aws-dependencies/pom.xml
index bfc4d0cc3..135094582 100644
--- a/spring-cloud-aws-dependencies/pom.xml
+++ b/spring-cloud-aws-dependencies/pom.xml
@@ -84,6 +84,12 @@
true
+
+ software.amazon.awssdk
+ cognitoidentityprovider
+ ${awssdk-v2.version}
+
+
io.awspring.cloud
spring-cloud-aws-core
@@ -138,6 +144,12 @@
${project.version}
+
+ io.awspring.cloud
+ spring-cloud-aws-cognito
+ ${project.version}
+
+
io.awspring.cloud
spring-cloud-aws-testcontainers
@@ -210,6 +222,12 @@
${project.version}
+
+ io.awspring.cloud
+ spring-cloud-aws-starter-cognito
+ ${project.version}
+
+
io.awspring.cloud
spring-cloud-aws-test
diff --git a/spring-cloud-aws-samples/pom.xml b/spring-cloud-aws-samples/pom.xml
index da1eb2363..2a95100ec 100644
--- a/spring-cloud-aws-samples/pom.xml
+++ b/spring-cloud-aws-samples/pom.xml
@@ -23,6 +23,7 @@
spring-cloud-aws-ses-sample
spring-cloud-aws-sns-sample
spring-cloud-aws-sqs-sample
+ spring-cloud-aws-cognito-sample
diff --git a/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/pom.xml b/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/pom.xml
new file mode 100644
index 000000000..ec89dd2e1
--- /dev/null
+++ b/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/pom.xml
@@ -0,0 +1,38 @@
+
+
+
+ spring-cloud-aws-samples
+ io.awspring.cloud
+ 3.3.0-SNAPSHOT
+
+ 4.0.0
+ spring-cloud-aws-cognito-sample
+ Spring Cloud AWS Cognito Sample
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
+
+ io.awspring.cloud
+ spring-cloud-aws-starter-cognito
+ 3.3.0-SNAPSHOT
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/AuthController.java b/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/AuthController.java
new file mode 100644
index 000000000..d9af3f949
--- /dev/null
+++ b/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/AuthController.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud;
+
+import io.awspring.cloud.cognito.CognitoTemplate;
+import java.util.List;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.AdminInitiateAuthResponse;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.AttributeType;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthenticationResultType;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.ChallengeNameType;
+import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeResponse;
+
+/**
+ * Demo controller for authentication operations.
+ *
+ * @author Oleh Onufryk
+ * @since 3.3.0
+ */
+
+@RestController
+@RequestMapping("/auth")
+public class AuthController {
+
+ private final CognitoTemplate cognitoTemplate;
+
+ private static final String USERNAME = "foo@bar.com";
+
+ public AuthController(CognitoTemplate cognitoTemplate) {
+ this.cognitoTemplate = cognitoTemplate;
+ }
+
+ @PostMapping("/signup")
+ void signup(@RequestBody SignupRequest signupRequest) {
+ cognitoTemplate.createUser(signupRequest.username(), signupRequest.password(), getAttributes(signupRequest));
+ }
+
+ @PostMapping("/login")
+ LoginResponse login(@RequestBody LoginRequest loginRequest) {
+ AdminInitiateAuthResponse response = cognitoTemplate.login(loginRequest.username(), loginRequest.password());
+ LoginResponse loginResponse = new LoginResponse();
+ if (ChallengeNameType.NEW_PASSWORD_REQUIRED.equals(response.challengeName())) {
+ loginResponse.setSession(response.session());
+ AuthResult authResult = new AuthResult();
+ authResult.setStatus(Status.SET_PASSWORD);
+ loginResponse.setAuthResult(authResult);
+ return loginResponse;
+ }
+ AuthenticationResultType authenticationResultType = response.authenticationResult();
+ AuthResult authResult = new AuthResult();
+ authResult.setAccessToken(authenticationResultType.accessToken());
+ authResult.setIdToken(authenticationResultType.idToken());
+ authResult.setRefreshToken(authenticationResultType.refreshToken());
+ authResult.setStatus(Status.SUCCESS);
+ loginResponse.setAuthResult(authResult);
+
+ return loginResponse;
+ }
+
+ @PostMapping("/set-password")
+ LoginResponse setPassword(@RequestBody SetPasswordRequest setPasswordRequest) {
+ RespondToAuthChallengeResponse respondToAuthChallengeResponse = cognitoTemplate.setPermanentPassword(
+ setPasswordRequest.session(), setPasswordRequest.username(), setPasswordRequest.newPassword());
+
+ LoginResponse loginResponse = new LoginResponse();
+ AuthResult authResult = new AuthResult();
+ authResult.setAccessToken(respondToAuthChallengeResponse.authenticationResult().accessToken());
+ authResult.setIdToken(respondToAuthChallengeResponse.authenticationResult().idToken());
+ authResult.setRefreshToken(respondToAuthChallengeResponse.authenticationResult().refreshToken());
+ loginResponse.setAuthResult(authResult);
+
+ return loginResponse;
+ }
+
+ @PostMapping("/reset-password")
+ void resetPassword(@RequestBody ResetPasswordRequest resetPasswordRequest) {
+ cognitoTemplate.resetPassword(resetPasswordRequest.username());
+ }
+
+ @PostMapping("/confirm-reset-password")
+ void confirmResetPassword(@RequestBody ConfirmResetPasswordRequest confirmResetPasswordRequest) {
+ cognitoTemplate.confirmResetPassword(confirmResetPasswordRequest.username(),
+ confirmResetPasswordRequest.confirmationCode, confirmResetPasswordRequest.newPassword);
+ }
+
+ @PostMapping("/logout")
+ void logout(@RequestBody LogoutRequest logoutRequest) {
+ cognitoTemplate.logout(logoutRequest.username());
+ }
+
+ private List getAttributes(SignupRequest signupRequest) {
+ return List.of(AttributeType.builder().name("email").value(USERNAME).build(),
+ AttributeType.builder().name("name").value(signupRequest.username()).build(),
+ AttributeType.builder().name("custom:role").value("USER").build()
+ // and all other attributes here
+ );
+ }
+
+ record SignupRequest(String username, String password) {
+ }
+
+ record LoginRequest(String username, String password) {
+ }
+
+ public static class LoginResponse {
+ String session;
+ AuthResult authResult;
+
+ public void setSession(String session) {
+ this.session = session;
+ }
+
+ public void setAuthResult(AuthResult authResult) {
+ this.authResult = authResult;
+ }
+
+ public String getSession() {
+ return session;
+ }
+
+ public AuthResult getAuthResult() {
+ return authResult;
+ }
+ }
+
+ public static class AuthResult {
+ String accessToken;
+ String idToken;
+ String refreshToken;
+ Status status;
+
+ public void setAccessToken(String accessToken) {
+ this.accessToken = accessToken;
+ }
+
+ public void setIdToken(String idToken) {
+ this.idToken = idToken;
+ }
+
+ public void setRefreshToken(String refreshToken) {
+ this.refreshToken = refreshToken;
+ }
+
+ public void setStatus(Status status) {
+ this.status = status;
+ }
+
+ public String getAccessToken() {
+ return accessToken;
+ }
+
+ public String getIdToken() {
+ return idToken;
+ }
+
+ public String getRefreshToken() {
+ return refreshToken;
+ }
+
+ public Status getStatus() {
+ return status;
+ }
+ }
+
+ public enum Status {
+ SUCCESS, SET_PASSWORD
+ }
+
+ record SetPasswordRequest(String session, String username, String newPassword) {
+
+ }
+
+ record ResetPasswordRequest(String username) {
+
+ }
+
+ record ConfirmResetPasswordRequest(String username, String confirmationCode, String newPassword) {
+
+ }
+
+ record LogoutRequest(String username) {
+
+ }
+
+}
diff --git a/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/AuthenticationConverter.java b/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/AuthenticationConverter.java
new file mode 100644
index 000000000..51e08928d
--- /dev/null
+++ b/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/AuthenticationConverter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
+import org.springframework.stereotype.Component;
+
+/**
+ * Demo authentication converter.
+ *
+ * @author Oleh Onufryk
+ * @since 3.3.0
+ */
+
+@Component
+public class AuthenticationConverter implements Converter {
+
+ private static final String ROLE_AUTHORITIES = "custom:role";
+ private final JwtAuthenticationConverter jwtAuthenticationConverter;
+
+ public AuthenticationConverter() {
+ this.jwtAuthenticationConverter = new JwtAuthenticationConverter();
+ jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwt -> getAuthorities(jwt.getClaims()));
+ jwtAuthenticationConverter.setPrincipalClaimName("email");
+ }
+
+ @Override
+ public AbstractAuthenticationToken convert(Jwt source) {
+ return jwtAuthenticationConverter.convert(source);
+ }
+
+ private Collection getAuthorities(Map map) {
+ if (!map.containsKey(ROLE_AUTHORITIES)) {
+ return Collections.emptyList();
+ }
+ String role = (String) map.get(ROLE_AUTHORITIES);
+ return List.of(new SimpleGrantedAuthority(role));
+ }
+}
diff --git a/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/Permission.java b/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/Permission.java
new file mode 100644
index 000000000..65389026d
--- /dev/null
+++ b/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/Permission.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud;
+
+/**
+ * Demo permission enum.
+ *
+ * @author Oleh Onufryk
+ * @since 3.3.0
+ */
+
+public enum Permission {
+ READ, WRITE
+}
diff --git a/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/Role.java b/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/Role.java
new file mode 100644
index 000000000..bd65e41db
--- /dev/null
+++ b/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/Role.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud;
+
+import java.util.List;
+
+/**
+ * Demo role enum.
+ *
+ * @author Oleh Onufryk
+ * @since 3.3.0
+ */
+
+public enum Role {
+ USER(List.of(Permission.READ));
+
+ private final List permissions;
+
+ Role(List permissions) {
+ this.permissions = permissions;
+ }
+
+ public boolean hasPermission(Permission permission) {
+ return permissions.contains(permission);
+ }
+}
diff --git a/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/SecuredResource.java b/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/SecuredResource.java
new file mode 100644
index 000000000..a1b9c559e
--- /dev/null
+++ b/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/SecuredResource.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud;
+
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Demo secured resource.
+ *
+ * @author Oleh Onufryk
+ * @since 3.3.0
+ */
+
+@RestController
+@RequestMapping("/api/secured")
+public class SecuredResource {
+
+ @GetMapping
+ @PreAuthorize("@securityDecisionMaker.hasPermission(authentication, 'READ')")
+ public String secured() {
+ return """
+ {
+ "message": "This is a secured endpoint"
+ }
+ """;
+ }
+}
diff --git a/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/SecurityConfig.java b/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/SecurityConfig.java
new file mode 100644
index 000000000..a9aa3e7e5
--- /dev/null
+++ b/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/SecurityConfig.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.web.SecurityFilterChain;
+
+/**
+ * Demo security configuration.
+ *
+ * @author Oleh Onufryk
+ * @since 3.3.0
+ */
+
+@Configuration
+@EnableMethodSecurity
+public class SecurityConfig {
+
+ private final AuthenticationConverter authenticationConverter;
+
+ public SecurityConfig(AuthenticationConverter authenticationConverter) {
+ this.authenticationConverter = authenticationConverter;
+ }
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ http.csrf(AbstractHttpConfigurer::disable)
+ .authorizeHttpRequests(
+ authorize -> authorize.requestMatchers("/auth/**").permitAll().anyRequest().authenticated())
+ .oauth2ResourceServer(
+ oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(this.authenticationConverter)));
+
+ return http.build();
+ }
+}
diff --git a/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/SecurityDecisionMaker.java b/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/SecurityDecisionMaker.java
new file mode 100644
index 000000000..b1ce93d2a
--- /dev/null
+++ b/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/SecurityDecisionMaker.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.stereotype.Service;
+
+/**
+ * Demo permission evaluator.
+ *
+ * @author Oleh Onufryk
+ * @since 3.3.0
+ */
+
+@Service
+public class SecurityDecisionMaker {
+
+ public boolean hasPermission(Authentication authentication, Permission permission) {
+ return authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).map(Role::valueOf)
+ .anyMatch(role -> role.hasPermission(permission));
+ }
+}
diff --git a/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/SpringCloudAwsCognitoExample.java b/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/SpringCloudAwsCognitoExample.java
new file mode 100644
index 000000000..c213ed517
--- /dev/null
+++ b/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/java/io/awspring/cloud/SpringCloudAwsCognitoExample.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringCloudAwsCognitoExample {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringCloudAwsCognitoExample.class, args);
+ }
+}
diff --git a/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/resources/application.properties b/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/resources/application.properties
new file mode 100644
index 000000000..cdcc3e013
--- /dev/null
+++ b/spring-cloud-aws-samples/spring-cloud-aws-cognito-sample/src/main/resources/application.properties
@@ -0,0 +1,13 @@
+# LocalStack configuration
+spring.cloud.aws.endpoint=http://localhost:4566
+spring.cloud.aws.region.static=us-east-1
+spring.cloud.aws.credentials.access-key=noop
+spring.cloud.aws.credentials.secret-key=noop
+
+spring.cloud.aws.cognito.user-pool-id=eu-central-1_Dummy
+spring.cloud.aws.cognito.client-id=client-id
+spring.cloud.aws.cognito.client-secret=client-secret
+
+# OAuth2 Authorization server configuration
+spring.security.oauth2.authorizationserver.endpoint.jwk-set-uri=https://cognito-idp.eu-central-1.amazonaws.com/eu-central-1_Dummy/.well-known/jwks.json
+spring.security.oauth2.resourceserver.jwt.issuer-uri=https://cognito-idp.eu-central-1.amazonaws.com/eu-central-1_Dummy
diff --git a/spring-cloud-aws-starters/spring-cloud-aws-starter-cognito/pom.xml b/spring-cloud-aws-starters/spring-cloud-aws-starter-cognito/pom.xml
new file mode 100644
index 000000000..a88da0268
--- /dev/null
+++ b/spring-cloud-aws-starters/spring-cloud-aws-starter-cognito/pom.xml
@@ -0,0 +1,26 @@
+
+
+
+ spring-cloud-aws
+ io.awspring.cloud
+ 3.3.0-SNAPSHOT
+ ../../pom.xml
+
+ 4.0.0
+
+ spring-cloud-aws-starter-cognito
+ Spring Cloud AWS Cognito Starter
+
+
+
+ io.awspring.cloud
+ spring-cloud-aws-cognito
+
+
+ io.awspring.cloud
+ spring-cloud-aws-starter
+
+
+