Skip to content

Commit e7c7e7a

Browse files
feat: Add prompt parameter support to OAuth2 authorization request (#295)
* Initial plan * feat: add prompt parameter support to OAuth2 authorize URL Agent-Logs-Url: https://github.com/FusionAuth/fusionauth-android-sdk/sessions/2cf5dc51-061f-4e9e-847c-ed3cdc76bab9 Co-authored-by: mrudatsprint <2787768+mrudatsprint@users.noreply.github.com> * test: add e2e test for prompt=login and expose prompt via startAuth() Agent-Logs-Url: https://github.com/FusionAuth/fusionauth-android-sdk/sessions/98961787-dd46-40b0-b58e-09cd12bd21b4 Co-authored-by: mrudatsprint <2787768+mrudatsprint@users.noreply.github.com> * Added end to end tests that test the prompt parameter. * Per datekt analysis, remove unnecessary try catch block. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mrudatsprint <2787768+mrudatsprint@users.noreply.github.com> Co-authored-by: Mike Rudat <mike.rudat@fusionauth.io>
1 parent b9b2bbc commit e7c7e7a

5 files changed

Lines changed: 87 additions & 2 deletions

File tree

app/src/androidTest/java/io/fusionauth/sdk/FullEnd2EndTest.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import androidx.test.espresso.assertion.ViewAssertions.matches
88
import androidx.test.espresso.intent.Intents
99
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
1010
import androidx.test.espresso.matcher.ViewMatchers.withId
11+
import androidx.test.espresso.matcher.ViewMatchers.withText
1112
import androidx.test.ext.junit.rules.ActivityScenarioRule
1213
import androidx.test.ext.junit.runners.AndroidJUnit4
1314
import androidx.test.platform.app.InstrumentationRegistry
@@ -62,6 +63,22 @@ internal class FullEnd2EndTest {
6263
logout()
6364
}
6465

66+
@Test
67+
fun e2eTestWithPromptLogin() {
68+
loginWithPrompt(USERNAME, PASSWORD, "login")
69+
logout()
70+
}
71+
72+
@Test
73+
fun e2eTestWithPromptNone() {
74+
loginActivityRule.scenario.onActivity { activity ->
75+
activity.startAuth("none")
76+
}
77+
78+
// Now assert error UI is present
79+
verifyAuthorizationError()
80+
}
81+
6582
@Test
6683
fun e2eTestSwitchFromPrimaryToAlternative() {
6784
login(USERNAME, PASSWORD)
@@ -202,6 +219,23 @@ internal class FullEnd2EndTest {
202219
}
203220
}
204221

222+
/**
223+
* Starts auth with the given prompt. Handles login form and token activity only.
224+
* Does NOT check for error UI. Tests should check for error UI if expected.
225+
*/
226+
private fun loginWithPrompt(username: String, password: String, prompt: String) {
227+
loginActivityRule.scenario.onActivity { activity ->
228+
activity.startAuth(prompt)
229+
}
230+
231+
handleFALoginForm(username, password)
232+
verifyOnTokenActivity()
233+
}
234+
235+
private fun verifyAuthorizationError() {
236+
onView(withText("Not authorized")).check(matches(isDisplayed()))
237+
}
238+
205239
@After
206240
fun tearDown() {
207241
logger.info("Tearing down test")

app/src/main/java/io/fusionauth/sdk/LoginActivity.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class LoginActivity : AppCompatActivity() {
8181
}
8282

8383
@MainThread
84-
fun startAuth() {
84+
fun startAuth(prompt: String? = null) {
8585
displayLoading("Making authorization request")
8686

8787
lifecycleScope.launch {
@@ -94,7 +94,8 @@ class LoginActivity : AppCompatActivity() {
9494
OAuthAuthorizeOptions(
9595
cancelIntent = Intent(this@LoginActivity, LoginActivity::class.java)
9696
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP),
97-
state = "state-${System.currentTimeMillis()}"
97+
state = "state-${System.currentTimeMillis()}",
98+
prompt = prompt
9899
)
99100
)
100101
} catch (e: AuthorizationException) {

library/src/main/java/io/fusionauth/mobilesdk/oauth/OAuthAuthorizationService.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ class OAuthAuthorizationService internal constructor(
118118
options?.state?.let { authRequestBuilder.setState(it) }
119119
options?.loginHint?.let { authRequestBuilder.setLoginHint(it) }
120120
options?.nonce?.let { authRequestBuilder.setNonce(it) }
121+
options?.prompt?.let { authRequestBuilder.setPrompt(it) }
121122

122123
val completedPendingIntent = PendingIntent.getActivity(
123124
context,
@@ -173,6 +174,7 @@ class OAuthAuthorizationService internal constructor(
173174
options?.idpHint?.let { additionalParameters["idp_hint"] = it }
174175
options?.deviceDescription?.let { additionalParameters["metaData.device.description"] = it }
175176
options?.userCode?.let { additionalParameters["user_code"] = it }
177+
176178
return additionalParameters
177179
}
178180

library/src/main/java/io/fusionauth/mobilesdk/oauth/OAuthAuthorizeOptions.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@ data class OAuthAuthorizeOptions(
3838
* authorization server includes this value when redirecting the user-agent back to the client.
3939
*/
4040
val state: String? = null,
41+
/**
42+
* The prompt parameter to be used for the OAuth authorize request. This parameter indicates
43+
* whether the authorization server should prompt the end-user for reauthentication or consent.
44+
* Standard OIDC values include: none, login, consent, select_account.
45+
*
46+
* See [OIDC prompt parameter](https://fusionauth.io/docs/lifecycle/authenticate-users/oauth/prompt)
47+
* for more information.
48+
*/
49+
val prompt: String? = null,
4150
/**
4251
* The end-user verification code.
4352
*/
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package io.fusionauth.mobilesdk
2+
3+
import io.fusionauth.mobilesdk.oauth.OAuthAuthorizeOptions
4+
import org.junit.Assert.assertEquals
5+
import org.junit.Assert.assertNull
6+
import org.junit.Test
7+
8+
class OAuthAuthorizeOptionsTest {
9+
10+
@Test
11+
fun `prompt defaults to null`() {
12+
val options = OAuthAuthorizeOptions()
13+
assertNull(options.prompt)
14+
}
15+
16+
@Test
17+
fun `prompt can be set to login`() {
18+
val options = OAuthAuthorizeOptions(prompt = "login")
19+
assertEquals("login", options.prompt)
20+
}
21+
22+
@Test
23+
fun `prompt can be set to consent`() {
24+
val options = OAuthAuthorizeOptions(prompt = "consent")
25+
assertEquals("consent", options.prompt)
26+
}
27+
28+
@Test
29+
fun `prompt can be set to none`() {
30+
val options = OAuthAuthorizeOptions(prompt = "none")
31+
assertEquals("none", options.prompt)
32+
}
33+
34+
@Test
35+
fun `prompt can be set to select_account`() {
36+
val options = OAuthAuthorizeOptions(prompt = "select_account")
37+
assertEquals("select_account", options.prompt)
38+
}
39+
}

0 commit comments

Comments
 (0)