okta-mobile-kotlin
okta-mobile-kotlin copied to clipboard
Step Up Redirect in Okta Kotlin SDK - Flow is cancelled
Describe the bug?
I am running into an issue and don’t know how to resolve it.
- Login using this method (ie the mobile sdk launches a Chrome Custom tab to auth)
- Revoke the user’s sessions (ie “Clear Sessions & Revoke Tokens” using the Okta admin portal)
- Keep using the until the access token expires. When a new access token is requested using the (now revoked) refresh token, the user will be required to log in. 4.) The enters valid credentials, but the login flow will now make a call to login/step-up/redirect?stateToken=thetokenhere, which returns a 302. This results in the Chrome Custom tab closing with an error result (specifically a FlowCancellationException). 5.) The user must then relaunch the login to successfully get a token. This time it will be successful and the step up redirection does not happen.
Is this a bug in the Okta Kotlin Mobile SDK? If not, what is the correct way to handle this? How can I differentiate this issue from the user simply dismissing the Chrome Custom (which also results in a FlowCancellationException)? What determines the step up auth and can it be disabled?
What is expected to happen?
When a user correctly enters credentials, the user receives a OidcClientResult.Success
result.
What is the actual behavior?
When a user's token is revoked, the next time the user logs in, he receives a OidcClientResult.Success
result. This appears to be because of a call from the SDK to
login/step-up/redirect?stateToken=theToken
(which results in a OidcClientResult.Error
with the exception being com.okta.webauthenticationui.WebAuthenticationClient$FlowCancelledException: Flow cancelled.
Reproduction Steps?
- Login using this method (ie the mobile sdk launches a Chrome Custom tab to auth)
- Revoke the user’s sessions (ie “Clear Sessions & Revoke Tokens” using the Okta admin portal)
- Keep using the until the access token expires. When a new access token is requested using the (now revoked) refresh token, the user will be required to log in. 4.) The enters valid credentials, but the login flow will now make a call to login/step-up/redirect?stateToken=thetokenhere, which returns a 302. This results in the Chrome Custom tab closing with an error result (specifically a FlowCancellationException). 5.) The user must then relaunch the login to successfully get a token. This time it will be successful and the step up redirection does not happen. Is this a bug in the Okta Kotlin Mobile SDK? If not, what is the correct way to handle this? How can I differentiate this issue from the user simply dismissing the Chrome Custom (which also results in a FlowCancellationException?
Additional Information?
The stack trace:
Flow cancelled. com.okta.webauthenticationui.WebAuthenticationClient$FlowCancelledException: Flow cancelled. at com.okta.webauthenticationui.DefaultRedirectCoordinator.emit(RedirectCoordinator.kt:123) at com.okta.webauthenticationui.SingletonRedirectCoordinator.emit(Unknown Source:2) at com.okta.webauthenticationui.ForegroundViewModel.flowCancelled(ForegroundViewModel.kt:78) at com.okta.webauthenticationui.ForegroundActivity.onDestroy(ForegroundActivity.kt:88) at android.app.Activity.performDestroy(Activity.java:8562) at android.app.Instrumentation.callActivityOnDestroy(Instrumentation.java:1452) at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:5396) at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:5442) at android.app.servertransaction.DestroyActivityItem.execute(DestroyActivityItem.java:47) at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45) at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2307) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7872) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
SDK Version and Artifact(s) used.
implementation(platform('com.okta.kotlin:bom:1.1.1'))
// Add the web-authentication-ui SDK to the project.
implementation('com.okta.kotlin:auth-foundation-bootstrap')
implementation('com.okta.kotlin:web-authentication-ui')
Build Information
No response
I tested the same scenario in the "samples-android browser-sign-in" example and was unable to reproduce the same issue. I also see the "step-up" api being called with a 302
response, but in the sample app, it then calls the /token
endpoint and works correctly.
After digging in further, it seems that this only happens when I initiate a login from onResume
. If the user manually clicks a button to launch the login logic, the issue doesn't occur. I launch a login from onResume
in the case that the user needs a new token (ie - a call returns a 401). That way the user doesn't have to actually click the button. I'll keep digging in to see if I can track down why it fails from onResume. If you have any thoughts on a potential root cause or ways I could debug, please let me know.
Hi I am getting the same issue, the only difference I can see between the sample and my own implementation is that I am calling logout from an activity and fragment.
class MainActivity : AppCompatActivity(), AuthInterface {
override fun logout() {
lifecycleScope.launch {
val response = webAuthClient.logoutOfBrowser(
context = this@MainActivity,
redirectUrl = logoutRedirectUri,
idToken = CredentialBootstrap.defaultCredential().token?.idToken
)
when (response) {
is OidcClientResult.Success -> onLogoutSuccess()
is OidcClientResult.Error -> onLogoutFailure(response.exception)
}
}
}
}
interface AuthInterface {
fun logout()
}
class LoginFragment : Fragment() {
fun onLogoutClicked() {
(activity as AuthInterface).logout()
}
}
This is possibly related to #236. FlowCancellationException shouldn't be thrown unless the user closes the Chrome tab. There is currently a race condition in the sdk which happens if a new Chrome tab is opened quickly after another one finishes. I am working on a fix for that, and hopefully it should fix this issue as well.
A similar issue with FlowCancellationException is resolved in the new release, 1.1.2. Let me know if this issue is still reproducible with this release.
Sorry for the slow response. I somehow missed all the comments on this issue. I'll retest over the next few days and report back.
A similar issue with FlowCancellationException is resolved in the new release, 1.1.2. Let me know if this issue is still reproducible with this release.
Hey there,
I'll join the discussion since I receive the same error, when using 1.1.2. It might be worth noticing: I call login()
from the compose coroutine scope gathered by rememberCoroutineScope
@kkiermasz, could you provide a sample app or code snippet that I can use to reproduce this problem? The problem isn't reproducible on my end and I'd like to understand this issue more.
@kkiermasz, could you provide a sample app or code snippet that I can use to reproduce this problem? The problem isn't reproducible on my end and I'd like to understand this issue more.
Sure thing! Always happy to help. Let me know how I should provide it to you. I'd prefer not to make it public ;)