diff --git a/aws-auth-cognito/api/aws-auth-cognito.api b/aws-auth-cognito/api/aws-auth-cognito.api index 4f14a110c..247301c23 100644 --- a/aws-auth-cognito/api/aws-auth-cognito.api +++ b/aws-auth-cognito/api/aws-auth-cognito.api @@ -611,6 +611,11 @@ public final class com/amplifyframework/auth/cognito/options/AWSCognitoAuthWebUI public fun equals (Ljava/lang/Object;)Z public fun getBrowserPackage ()Ljava/lang/String; public fun getIdpIdentifier ()Ljava/lang/String; + public fun getLanguage ()Ljava/lang/String; + public fun getLoginHint ()Ljava/lang/String; + public fun getNonce ()Ljava/lang/String; + public fun getPrompt ()Ljava/util/List; + public fun getResource ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -623,6 +628,11 @@ public final class com/amplifyframework/auth/cognito/options/AWSCognitoAuthWebUI public fun getThis ()Lcom/amplifyframework/auth/cognito/options/AWSCognitoAuthWebUISignInOptions$CognitoBuilder; public synthetic fun getThis ()Lcom/amplifyframework/auth/options/AuthWebUISignInOptions$Builder; public fun idpIdentifier (Ljava/lang/String;)Lcom/amplifyframework/auth/cognito/options/AWSCognitoAuthWebUISignInOptions$CognitoBuilder; + public fun language (Ljava/lang/String;)Lcom/amplifyframework/auth/cognito/options/AWSCognitoAuthWebUISignInOptions$CognitoBuilder; + public fun loginHint (Ljava/lang/String;)Lcom/amplifyframework/auth/cognito/options/AWSCognitoAuthWebUISignInOptions$CognitoBuilder; + public fun nonce (Ljava/lang/String;)Lcom/amplifyframework/auth/cognito/options/AWSCognitoAuthWebUISignInOptions$CognitoBuilder; + public fun prompt ([Lcom/amplifyframework/auth/cognito/options/AuthWebUIPrompt;)Lcom/amplifyframework/auth/cognito/options/AWSCognitoAuthWebUISignInOptions$CognitoBuilder; + public fun resource (Ljava/lang/String;)Lcom/amplifyframework/auth/cognito/options/AWSCognitoAuthWebUISignInOptions$CognitoBuilder; } public final class com/amplifyframework/auth/cognito/options/AuthFlowType : java/lang/Enum { @@ -636,6 +646,17 @@ public final class com/amplifyframework/auth/cognito/options/AuthFlowType : java public static fun values ()[Lcom/amplifyframework/auth/cognito/options/AuthFlowType; } +public final class com/amplifyframework/auth/cognito/options/AuthWebUIPrompt : java/lang/Enum { + public static final field CONSENT Lcom/amplifyframework/auth/cognito/options/AuthWebUIPrompt; + public static final field LOGIN Lcom/amplifyframework/auth/cognito/options/AuthWebUIPrompt; + public static final field NONE Lcom/amplifyframework/auth/cognito/options/AuthWebUIPrompt; + public static final field SELECT_ACCOUNT Lcom/amplifyframework/auth/cognito/options/AuthWebUIPrompt; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getValue ()Ljava/lang/String; + public static fun valueOf (Ljava/lang/String;)Lcom/amplifyframework/auth/cognito/options/AuthWebUIPrompt; + public static fun values ()[Lcom/amplifyframework/auth/cognito/options/AuthWebUIPrompt; +} + public final class com/amplifyframework/auth/cognito/options/FederateToIdentityPoolOptions { public static final field Companion Lcom/amplifyframework/auth/cognito/options/FederateToIdentityPoolOptions$Companion; public static final fun builder ()Lcom/amplifyframework/auth/cognito/options/FederateToIdentityPoolOptions$CognitoBuilder; diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/HostedUIClient.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/HostedUIClient.kt index d5f7699ba..8bf11829c 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/HostedUIClient.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/HostedUIClient.kt @@ -164,6 +164,31 @@ internal class HostedUIClient private constructor( builder.appendQueryParameter("scope", it) } + // check if nonce is set as param. + hostedUIOptions.nonce?.takeIf { it.isNotEmpty() }?.let { + builder.appendQueryParameter("nonce", it) + } + + // check if language is set as param. + hostedUIOptions.language?.takeIf { it.isNotEmpty() }?.let { + builder.appendQueryParameter("lang", it) + } + + // check if loginHint is set as param. + hostedUIOptions.loginHint?.takeIf { it.isNotEmpty() }?.let { + builder.appendQueryParameter("login_hint", it) + } + + // check if prompt is set as param. + hostedUIOptions.prompt?.joinToString(" ") { it.value }.let { + builder.appendQueryParameter("prompt", it) + } + + // check if resource is set as param. + hostedUIOptions.resource?.takeIf { it.isNotEmpty() }?.let { + builder.appendQueryParameter("resource", it) + } + return builder.build() } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/HostedUIHelper.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/HostedUIHelper.kt index 3856aa532..2e450545b 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/HostedUIHelper.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/HostedUIHelper.kt @@ -36,7 +36,12 @@ internal object HostedUIHelper { idpIdentifier = (options as? AWSCognitoAuthWebUISignInOptions)?.idpIdentifier ), browserPackage = (options as? AWSCognitoAuthWebUISignInOptions)?.browserPackage, - preferPrivateSession = options.preferPrivateSession + preferPrivateSession = options.preferPrivateSession, + nonce = (options as? AWSCognitoAuthWebUISignInOptions)?.nonce, + language = (options as? AWSCognitoAuthWebUISignInOptions)?.language, + loginHint = (options as? AWSCognitoAuthWebUISignInOptions)?.loginHint, + prompt = (options as? AWSCognitoAuthWebUISignInOptions)?.prompt, + resource = (options as? AWSCognitoAuthWebUISignInOptions)?.resource ) /** diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AWSCognitoAuthWebUISignInOptions.java b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AWSCognitoAuthWebUISignInOptions.java index d44da5887..f8d791c41 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AWSCognitoAuthWebUISignInOptions.java +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AWSCognitoAuthWebUISignInOptions.java @@ -22,6 +22,7 @@ import com.amplifyframework.auth.options.AuthWebUISignInOptions; import com.amplifyframework.util.Immutable; +import java.util.ArrayList; import java.util.List; /** @@ -30,6 +31,11 @@ public final class AWSCognitoAuthWebUISignInOptions extends AuthWebUISignInOptions { private final String idpIdentifier; private final String browserPackage; + private final String nonce; + private final String language; + private final String loginHint; + private final List prompt; + private final String resource; /** * Advanced options for signing in via a hosted web ui. @@ -38,16 +44,33 @@ public final class AWSCognitoAuthWebUISignInOptions extends AuthWebUISignInOptio * @param browserPackage Specify which browser package should be used for web sign in (e.g. "org.mozilla.firefox"). * Defaults to the Chrome package if not specified. * @param preferPrivateSession specifying whether or not to launch web ui in an ephemeral CustomTab. + * @param nonce random value that can be added to the request, which is included in the ID token + * that Amazon Cognito issues. + * @param language language displayed in user-interactive page + * @param loginHint username prompt passed to the authorization server + * @param prompt a list of OIDC parameters that controls authentication behavior for existing sessions. + * @param resource identifier of a resource that you want to bind to the access token in the `aud` claim. */ + @SuppressWarnings("checkstyle:all") protected AWSCognitoAuthWebUISignInOptions( List scopes, String idpIdentifier, String browserPackage, - Boolean preferPrivateSession + Boolean preferPrivateSession, + String nonce, + String language, + String loginHint, + List prompt, + String resource ) { super(scopes, preferPrivateSession); this.idpIdentifier = idpIdentifier; this.browserPackage = browserPackage; + this.nonce = nonce; + this.language = language; + this.loginHint = loginHint; + this.prompt = prompt; + this.resource = resource; } /** @@ -68,6 +91,58 @@ public String getBrowserPackage() { return browserPackage; } + /** + * Optional A random value that can be added to the request, which is included in the ID token + * that Amazon Cognito issues. To guard against replay attacks, your app can inspect the nonce claim in the ID + * token and compare it to the one you generated. + * @return the nonce value + */ + @Nullable + public String getNonce() { + return nonce; + } + + /** Optional The language displayed in user-interactive page. + * For more information, see Managed login localization + * https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-managed-login.html + * @return the language value + */ + @Nullable + public String getLanguage() { + return language; + } + + /** Optional A username prompt passed to the authorization server. You can collect a username, email + * address or phone number from your user and allow the destination provider to pre-populate the user's + * sign-in name. + * @return the login prompt displayed in the username field + */ + @Nullable + public String getLoginHint() { + return loginHint; + } + + /** + * Optional An OIDC parameter that controls authentication behavior for existing sessions. + * @return the prompt value + */ + @Nullable + public List getPrompt() { + return prompt; + } + + /** + * Optional The identifier of a resource that you want to bind to the access token in the `aud` + * claim. When this parameter is included, Amazon Cognito validates that the value is a URL and + * sets the audience of the resulting access token to the requested resource. Values for this + * parameter must begin with "https://", "http://localhost" or a custom URL scheme like "myapp://". + * @return the resource value + */ + @Nullable + public String getResource() { + return resource; + } + /** * Returns a builder for this object. * @return a builder for this object. @@ -83,7 +158,12 @@ public int hashCode() { getScopes(), getIdpIdentifier(), getBrowserPackage(), - getPreferPrivateSession() + getPreferPrivateSession(), + getNonce(), + getLanguage(), + getLoginHint(), + getPrompt(), + getResource() ); } @@ -98,7 +178,12 @@ public boolean equals(Object obj) { return ObjectsCompat.equals(getScopes(), webUISignInOptions.getScopes()) && ObjectsCompat.equals(getIdpIdentifier(), webUISignInOptions.getIdpIdentifier()) && ObjectsCompat.equals(getBrowserPackage(), webUISignInOptions.getBrowserPackage()) && - ObjectsCompat.equals(getPreferPrivateSession(), webUISignInOptions.getPreferPrivateSession()); + ObjectsCompat.equals(getPreferPrivateSession(), webUISignInOptions.getPreferPrivateSession()) && + ObjectsCompat.equals(getNonce(), webUISignInOptions.getNonce()) && + ObjectsCompat.equals(getLanguage(), webUISignInOptions.getLanguage()) && + ObjectsCompat.equals(getLoginHint(), webUISignInOptions.getLoginHint()) && + ObjectsCompat.equals(getPrompt(), webUISignInOptions.getPrompt()) && + ObjectsCompat.equals(getResource(), webUISignInOptions.getResource()); } } @@ -109,6 +194,11 @@ public String toString() { ", idpIdentifier=" + getIdpIdentifier() + ", browserPackage=" + getBrowserPackage() + ", preferPrivateSession=" + getPreferPrivateSession() + + ", nonce=" + getNonce() + + ", language=" + getLanguage() + + ", loginHint=" + getLoginHint() + + ", prompt=" + getPrompt() + + ", resource=" + getResource() + '}'; } @@ -118,6 +208,11 @@ public String toString() { public static final class CognitoBuilder extends Builder { private String idpIdentifier; private String browserPackage; + private String nonce; + private String language; + private String loginHint; + private List prompt; + private String resource; /** * Constructs the builder. @@ -146,6 +241,70 @@ public CognitoBuilder idpIdentifier(@NonNull String idpIdentifier) { return getThis(); } + /** + * A random value that can be added to the request, which is included in the ID token + * that Amazon Cognito issues. + * @param nonce a random value to be added to the request + * @return the instance of the builder. + */ + @NonNull + public CognitoBuilder nonce(@NonNull String nonce) { + this.nonce = nonce; + return getThis(); + } + + /** The language displayed in user-interactive page. + * For more information, see Managed login localization + * https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-managed-login.html + * @param language language value + * @return the instance of the builder. + */ + @NonNull + public CognitoBuilder language(@NonNull String language) { + this.language = language; + return getThis(); + } + + /** A username prompt passed to the authorization server. You can collect a username, email + * address or phone number from your user and allow the destination provider to pre-populate the user's + * sign-in name. + * @param loginHint login prompt to pass to authorization server + * @return the instance of the builder. + */ + @NonNull + public CognitoBuilder loginHint(@NonNull String loginHint) { + this.loginHint = loginHint; + return getThis(); + } + + /** + * Optional An OIDC parameter that controls authentication behavior for existing sessions. + * @param prompt list of AuthWebUIPrompt values + * @return the instance of the builder. + */ + @NonNull + public CognitoBuilder prompt(AuthWebUIPrompt... prompt) { + this.prompt = new ArrayList<>(); + for (AuthWebUIPrompt value : prompt) { + this.prompt.add(value); + } + return getThis(); + } + + /** + * Optional The identifier of a resource that you want to bind to the access token in the `aud` + * claim. When this parameter is included, Amazon Cognito validates that the value is a URL and + * sets the audience of the resulting access token to the requested resource. Values for this + * parameter must begin with "https://", "http://localhost" or a custom URL scheme like "myapp://". + * @param resource resource value + * @return the instance of the builder. + */ + @NonNull + public CognitoBuilder resource(@NonNull String resource) { + this.resource = resource; + return getThis(); + } + /** * This can optionally be set to specify which browser package should perform the sign in action * (e.g. "org.mozilla.firefox"). Defaults to the Chrome package if not set. @@ -168,7 +327,12 @@ public AWSCognitoAuthWebUISignInOptions build() { Immutable.of(super.getScopes()), idpIdentifier, browserPackage, - super.getPreferPrivateSession() + super.getPreferPrivateSession(), + nonce, + language, + loginHint, + prompt, + resource ); } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AuthWebUIPrompt.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AuthWebUIPrompt.kt new file mode 100644 index 000000000..03b2fd2dc --- /dev/null +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AuthWebUIPrompt.kt @@ -0,0 +1,39 @@ +package com.amplifyframework.auth.cognito.options + +/** + * An OIDC parameter that controls authentication behavior for existing sessions. + */ +public enum class AuthWebUIPrompt(val value: String) { + /** + * Amazon Cognito silently continues authentication for users who have a valid authenticated session. + * With this prompt, users can silently authenticate between different app clients in your user pool. + * If the user is not already authenticated, the authorization server returns a login_required error. + */ + NONE(value = "none"), + + /** + * Amazon Cognito requires users to re-authenticate even if they have an existing session. Send this + * value when you want to verify the user's identity again. Authenticated users who have an existing + * session can return to sign-in without invalidating that session. When a user who has an existing + * session signs in again, Amazon Cognito assigns them a new session cookie. This parameter can also + * be forwarded to your IdPs. IdPs that accept this parameter also request a new authentication + * attempt from the user. + */ + LOGIN(value = "login"), + + /** + * This value has no effect on local sign-in and must be submitted in requests that redirect to IdPs. + * When included in your authorization request, this parameter adds prompt=select_account to the URL + * path for the IdP redirect destination. When IdPs support this parameter, they request that users + * select the account that they want to log in with. + */ + SELECT_ACCOUNT(value = "select_account"), + + /** + * This value has no effect on local sign-in and must be submitted in requests that redirect to IdPs. + * When included in your authorization request, this parameter adds prompt=consent to the URL path for + * the IdP redirect destination. When IdPs support this parameter, they request user consent before + * they redirect back to your user pool. + */ + CONSENT(value = "consent") +} diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/HostedUIOptions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/HostedUIOptions.kt index fc6732873..6cbc4526c 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/HostedUIOptions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/HostedUIOptions.kt @@ -16,11 +16,17 @@ package com.amplifyframework.statemachine.codegen.data import android.app.Activity +import com.amplifyframework.auth.cognito.options.AuthWebUIPrompt internal data class HostedUIOptions( val callingActivity: Activity, val scopes: List?, val providerInfo: HostedUIProviderInfo, val browserPackage: String?, - val preferPrivateSession: Boolean? + val preferPrivateSession: Boolean?, + val nonce: String?, + val language: String?, + val loginHint: String?, + val prompt: List?, + val resource: String? ) diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/HostedUIHelperTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/HostedUIHelperTest.kt index fe0e720b2..39c153db7 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/HostedUIHelperTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/HostedUIHelperTest.kt @@ -18,6 +18,7 @@ package com.amplifyframework.auth.cognito.helpers import android.app.Activity import com.amplifyframework.auth.AuthProvider import com.amplifyframework.auth.cognito.options.AWSCognitoAuthWebUISignInOptions +import com.amplifyframework.auth.cognito.options.AuthWebUIPrompt import io.kotest.matchers.shouldBe import io.mockk.mockk import org.junit.Test @@ -88,6 +89,30 @@ class HostedUIHelperTest { hostedUIOptions.preferPrivateSession shouldBe null } + @Test + fun `createHostedUIOptions with Cognito OIDC parameters`() { + val options = AWSCognitoAuthWebUISignInOptions.builder() + .nonce("nonce") + .language("en") + .loginHint("username") + .prompt(AuthWebUIPrompt.LOGIN, AuthWebUIPrompt.CONSENT) + .resource("myapp://") + .build() + + val hostedUIOptions = HostedUIHelper.createHostedUIOptions( + callingActivity = mockActivity, + authProvider = null, + options = options + ) + + hostedUIOptions.callingActivity shouldBe mockActivity + hostedUIOptions.nonce shouldBe "nonce" + hostedUIOptions.language shouldBe "en" + hostedUIOptions.loginHint shouldBe "username" + hostedUIOptions.prompt shouldBe listOf(AuthWebUIPrompt.LOGIN, AuthWebUIPrompt.CONSENT) + hostedUIOptions.resource shouldBe "myapp://" + } + @Test fun `selectRedirectUri prefers non-HTTP scheme`() { val redirectUris = listOf( diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/options/APIOptionsContractTest.java b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/options/APIOptionsContractTest.java index ebedc7657..e3e3211a4 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/options/APIOptionsContractTest.java +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/options/APIOptionsContractTest.java @@ -127,4 +127,25 @@ public void testCognitoOptions() { Assert.assertEquals("test-idp", federateToIdentityPoolOptions .getDeveloperProvidedIdentityId()); } + + /** + * Test Java CognitoAuthWebUIOptions OIDC parameters builder methods. + */ + @Test + public void testCognitoAuthWebUISignInOIDCParameters() { + List prompt = Arrays.asList(AuthWebUIPrompt.LOGIN, AuthWebUIPrompt.CONSENT); + AWSCognitoAuthWebUISignInOptions webUISignInOptionsWithOIDCParameters = + AWSCognitoAuthWebUISignInOptions.builder() + .nonce("nonce") + .language("en") + .loginHint("username") + .prompt(AuthWebUIPrompt.LOGIN, AuthWebUIPrompt.CONSENT) + .resource("myapp://") + .build(); + Assert.assertEquals("nonce", webUISignInOptionsWithOIDCParameters.getNonce()); + Assert.assertEquals("en", webUISignInOptionsWithOIDCParameters.getLanguage()); + Assert.assertEquals("username", webUISignInOptionsWithOIDCParameters.getLoginHint()); + Assert.assertEquals(prompt, webUISignInOptionsWithOIDCParameters.getPrompt()); + Assert.assertEquals("myapp://", webUISignInOptionsWithOIDCParameters.getResource()); + } }