Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ internal class HostedUIClient private constructor(
launchCustomTabs(
uri = createAuthorizeUri(hostedUIOptions),
activity = hostedUIOptions.callingActivity,
customBrowserPackage = hostedUIOptions.browserPackage
customBrowserPackage = hostedUIOptions.browserPackage,
preferPrivateSession = hostedUIOptions.preferPrivateSession
)
}

Expand All @@ -75,14 +76,22 @@ internal class HostedUIClient private constructor(
)
}

private fun launchCustomTabs(uri: Uri, activity: Activity? = null, customBrowserPackage: String?) {
private fun launchCustomTabs(
uri: Uri,
activity: Activity? = null,
customBrowserPackage: String?,
preferPrivateSession: Boolean? = null // allowing nullable, as null means customer didn't specify
) {
if (!BrowserHelper.isBrowserInstalled(context)) {
throw RuntimeException("No browsers installed")
}

val browserPackage = customBrowserPackage ?: defaultCustomTabsPackage

val customTabsIntent = CustomTabsIntent.Builder(session).build().apply {
val customTabsIntent = CustomTabsIntent.Builder(session).apply {
// If customer didn't specify (null), don't add any Intent extra
preferPrivateSession?.let { setEphemeralBrowsingEnabled(it) }
}.build().apply {
browserPackage?.let { intent.`package` = it }
intent.data = uri
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ internal object HostedUIHelper {
authProvider = authProvider,
idpIdentifier = (options as? AWSCognitoAuthWebUISignInOptions)?.idpIdentifier
),
browserPackage = (options as? AWSCognitoAuthWebUISignInOptions)?.browserPackage
browserPackage = (options as? AWSCognitoAuthWebUISignInOptions)?.browserPackage,
preferPrivateSession = options.preferPrivateSession
)

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@ public final class AWSCognitoAuthWebUISignInOptions extends AuthWebUISignInOptio
* @param idpIdentifier The IdentityProvider identifier if using multiple instances of same identity provider.
* @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.
*/
protected AWSCognitoAuthWebUISignInOptions(
List<String> scopes,
String idpIdentifier,
String browserPackage
String browserPackage,
Boolean preferPrivateSession
) {
super(scopes);
super(scopes, preferPrivateSession);
this.idpIdentifier = idpIdentifier;
this.browserPackage = browserPackage;
}
Expand Down Expand Up @@ -80,7 +82,8 @@ public int hashCode() {
return ObjectsCompat.hash(
getScopes(),
getIdpIdentifier(),
getBrowserPackage()
getBrowserPackage(),
getPreferPrivateSession()
);
}

Expand All @@ -94,7 +97,8 @@ public boolean equals(Object obj) {
AWSCognitoAuthWebUISignInOptions webUISignInOptions = (AWSCognitoAuthWebUISignInOptions) obj;
return ObjectsCompat.equals(getScopes(), webUISignInOptions.getScopes()) &&
ObjectsCompat.equals(getIdpIdentifier(), webUISignInOptions.getIdpIdentifier()) &&
ObjectsCompat.equals(getBrowserPackage(), webUISignInOptions.getBrowserPackage());
ObjectsCompat.equals(getBrowserPackage(), webUISignInOptions.getBrowserPackage()) &&
ObjectsCompat.equals(getPreferPrivateSession(), webUISignInOptions.getPreferPrivateSession());
}
}

Expand All @@ -104,6 +108,7 @@ public String toString() {
"scopes=" + getScopes() +
", idpIdentifier=" + getIdpIdentifier() +
", browserPackage=" + getBrowserPackage() +
", preferPrivateSession=" + getPreferPrivateSession() +
'}';
}

Expand Down Expand Up @@ -162,7 +167,8 @@ public AWSCognitoAuthWebUISignInOptions build() {
return new AWSCognitoAuthWebUISignInOptions(
Immutable.of(super.getScopes()),
idpIdentifier,
browserPackage
browserPackage,
super.getPreferPrivateSession()
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ internal data class HostedUIOptions(
val callingActivity: Activity,
val scopes: List<String>?,
val providerInfo: HostedUIProviderInfo,
val browserPackage: String?
val browserPackage: String?,
val preferPrivateSession: Boolean?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 com.amplifyframework.auth.cognito.helpers

import android.app.Activity
import com.amplifyframework.auth.AuthProvider
import com.amplifyframework.auth.cognito.options.AWSCognitoAuthWebUISignInOptions
import io.kotest.matchers.shouldBe
import io.mockk.mockk
import org.junit.Test

class HostedUIHelperTest {

private val mockActivity = mockk<Activity>()

@Test
fun `createHostedUIOptions with AWSCognitoAuthWebUISignInOptions and preferPrivateSession true`() {
val scopes = listOf("openid", "profile")
val options = AWSCognitoAuthWebUISignInOptions.builder()
.scopes(scopes)
.browserPackage("com.android.chrome")
.preferPrivateSession(true)
.build()

val hostedUIOptions = HostedUIHelper.createHostedUIOptions(
callingActivity = mockActivity,
authProvider = AuthProvider.google(),
options = options
)

hostedUIOptions.callingActivity shouldBe mockActivity
hostedUIOptions.scopes shouldBe scopes
hostedUIOptions.browserPackage shouldBe "com.android.chrome"
hostedUIOptions.preferPrivateSession shouldBe true
}

@Test
fun `createHostedUIOptions with AWSCognitoAuthWebUISignInOptions and preferPrivateSession false`() {
val scopes = listOf("openid", "email")
val options = AWSCognitoAuthWebUISignInOptions.builder()
.scopes(scopes)
.browserPackage("org.mozilla.firefox")
.preferPrivateSession(false)
.build()

val hostedUIOptions = HostedUIHelper.createHostedUIOptions(
callingActivity = mockActivity,
authProvider = AuthProvider.facebook(),
options = options
)

hostedUIOptions.callingActivity shouldBe mockActivity
hostedUIOptions.scopes shouldBe scopes
hostedUIOptions.browserPackage shouldBe "org.mozilla.firefox"
hostedUIOptions.preferPrivateSession shouldBe false
}

@Test
fun `createHostedUIOptions with AWSCognitoAuthWebUISignInOptions and preferPrivateSession null`() {
val scopes = listOf("openid")
val options = AWSCognitoAuthWebUISignInOptions.builder()
.scopes(scopes)
.browserPackage("com.android.chrome")
.build()

val hostedUIOptions = HostedUIHelper.createHostedUIOptions(
callingActivity = mockActivity,
authProvider = null,
options = options
)

hostedUIOptions.callingActivity shouldBe mockActivity
hostedUIOptions.scopes shouldBe scopes
hostedUIOptions.browserPackage shouldBe "com.android.chrome"
hostedUIOptions.preferPrivateSession shouldBe null
}

@Test
fun `selectRedirectUri prefers non-HTTP scheme`() {
val redirectUris = listOf(
"https://example.com/callback",
"myapp://auth/callback",
"http://localhost:3000/callback"
)

val selectedUri = HostedUIHelper.selectRedirectUri(redirectUris)
selectedUri shouldBe "myapp://auth/callback"
}

@Test
fun `selectRedirectUri returns first URI when no non-HTTP scheme available`() {
val redirectUris = listOf(
"https://example.com/callback",
"http://localhost:3000/callback"
)

val selectedUri = HostedUIHelper.selectRedirectUri(redirectUris)
selectedUri shouldBe "https://example.com/callback"
}

@Test
fun `selectRedirectUri returns null for empty list`() {
val redirectUris = emptyList<String>()
val selectedUri = HostedUIHelper.selectRedirectUri(redirectUris)
selectedUri shouldBe null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,34 @@ public void testCognitoOptions() {
.scopes(scopes).build();
Assert.assertEquals(webUISignInOptions.getBrowserPackage(), "chrome");
Assert.assertEquals(webUISignInOptions.getScopes(), scopes);
Assert.assertNull(webUISignInOptions.getPreferPrivateSession());

// Test preferPrivateSession option
AWSCognitoAuthWebUISignInOptions webUISignInOptionsWithPrivateSession =
AWSCognitoAuthWebUISignInOptions.builder()
.browserPackage("chrome")
.scopes(scopes)
.preferPrivateSession(true)
.build();
Assert.assertEquals("chrome", webUISignInOptionsWithPrivateSession.getBrowserPackage());
Assert.assertEquals(scopes, webUISignInOptionsWithPrivateSession.getScopes());
Assert.assertEquals(Boolean.TRUE, webUISignInOptionsWithPrivateSession.getPreferPrivateSession());

// Test preferPrivateSession set to false
AWSCognitoAuthWebUISignInOptions webUISignInOptionsWithoutPrivateSession =
AWSCognitoAuthWebUISignInOptions.builder()
.browserPackage("firefox")
.scopes(scopes)
.preferPrivateSession(false)
.build();
Assert.assertEquals("firefox", webUISignInOptionsWithoutPrivateSession.getBrowserPackage());
Assert.assertEquals(scopes, webUISignInOptionsWithoutPrivateSession.getScopes());
Assert.assertEquals(Boolean.FALSE, webUISignInOptionsWithoutPrivateSession.getPreferPrivateSession());

FederateToIdentityPoolOptions federateToIdentityPoolOptions =
FederateToIdentityPoolOptions.builder().developerProvidedIdentityId("test-idp")
.build();
Assert.assertEquals(federateToIdentityPoolOptions
.getDeveloperProvidedIdentityId(), "test-idp");
Assert.assertEquals("test-idp", federateToIdentityPoolOptions
.getDeveloperProvidedIdentityId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class AndroidLibraryConventionPlugin : Plugin<Project> {
}

extension.apply {
compileSdk = 34
compileSdk = 36

buildFeatures {
buildConfig = true
Expand Down
5 changes: 4 additions & 1 deletion core/api/core.api
Original file line number Diff line number Diff line change
Expand Up @@ -1094,9 +1094,10 @@ public final class com/amplifyframework/auth/options/AuthVerifyTOTPSetupOptions$
}

public class com/amplifyframework/auth/options/AuthWebUISignInOptions {
protected fun <init> (Ljava/util/List;)V
protected fun <init> (Ljava/util/List;Ljava/lang/Boolean;)V
public static fun builder ()Lcom/amplifyframework/auth/options/AuthWebUISignInOptions$Builder;
public fun equals (Ljava/lang/Object;)Z
public fun getPreferPrivateSession ()Ljava/lang/Boolean;
public fun getScopes ()Ljava/util/List;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
Expand All @@ -1105,8 +1106,10 @@ public class com/amplifyframework/auth/options/AuthWebUISignInOptions {
public abstract class com/amplifyframework/auth/options/AuthWebUISignInOptions$Builder {
public fun <init> ()V
public fun build ()Lcom/amplifyframework/auth/options/AuthWebUISignInOptions;
public fun getPreferPrivateSession ()Ljava/lang/Boolean;
public fun getScopes ()Ljava/util/List;
public abstract fun getThis ()Lcom/amplifyframework/auth/options/AuthWebUISignInOptions$Builder;
public fun preferPrivateSession (Ljava/lang/Boolean;)Lcom/amplifyframework/auth/options/AuthWebUISignInOptions$Builder;
public fun scopes (Ljava/util/List;)Lcom/amplifyframework/auth/options/AuthWebUISignInOptions$Builder;
}

Expand Down
Loading