Authenticated Flows
Descope supports running initial and post-authentication flows on mobile devices.
Post-authentication or authenticated flows enable functionality like step up authentication and update user. This article explains how to implement them.
Starting an Authenticated Flow
Authenticated flows work like unauthenticated or initial authentication flows. The difference is that the user must already be signed in when the flow starts. The SDK picks up the active session automatically - React Native through AuthProvider, Swift and Kotlin through DescopeSessionManager - so there is nothing extra to pass.
import { FlowView, useDescope, useHostedFlowUrl, useSession } from '@descope/react-native-sdk'
const sdk = useDescope()
const { manageSession, updateUser } = useSession()
const flowUrl = useHostedFlowUrl('<FLOW_ID>')
<FlowView
style={styles.flow}
flowOptions={{ url: flowUrl }}
onSuccess={async (jwtResponse) => {
await manageSession(jwtResponse)
// Pick up any user details the flow changed
const meResponse = await sdk.me(jwtResponse.refreshJwt)
if (meResponse.data) {
await updateUser(meResponse.data)
}
}}
onError={(error) => {
// handle flow errors
}}
/>FlowView resolves the active session through AuthProvider, so it must be rendered inside one. If a session exists, the flow runs as that user.
FlowView renders nothing while isSessionLoading is true. This prevents the flow from starting with an empty refresh JWT on a cold start, but it means the view mounts slightly later than the rest of your screen. Show your own loading indicator until onReady fires:
const [ready, setReady] = useState(false)
<View style={styles.flowContainer}>
<FlowView
style={styles.flow}
flowOptions={{ url: flowUrl }}
onReady={() => setReady(true)}
onSuccess={handleSuccess}
onError={handleError}
/>
{!ready && <ActivityIndicator style={StyleSheet.absoluteFillObject} size="large" />}
</View>Some authenticated flows finish without signing the user in again, such as a profile update. The response still carries the active session's tokens, so onSuccess never receives an empty JWT and manageSession is always safe to call. In that case the native layer returns a placeholder user, which the SDK replaces with the user from the active session before onSuccess runs. Changes the flow made to the user are not reflected in it - call sdk.me and updateUser as shown above to pick them up.
// If DescopeSessionManager holds a valid session, the flow runs as that user
let flow = DescopeFlow(url: "<URL_FOR_FLOW_IN_SETUP_#1>")
let flowViewController = DescopeFlowViewController()
flowViewController.delegate = self
flowViewController.start(flow: flow)
// The delegate receives the response when the flow completes
func flowViewControllerDidFinish(_ controller: DescopeFlowViewController, response: AuthenticationResponse) {
let session = DescopeSession(from: response)
Descope.sessionManager.manageSession(session)
}
func flowViewControllerDidFail(_ controller: DescopeFlowViewController, error: DescopeError) {
// handle flow errors
}
func flowViewControllerDidCancel(_ controller: DescopeFlowViewController) {
// the user dismissed the flow
}descopeFlowView.listener = object : DescopeFlowView.Listener {
override fun onSuccess(response: AuthenticationResponse) {
Descope.sessionManager.manageSession(DescopeSession(response))
}
override fun onError(exception: DescopeException) {
// handle flow errors
}
}
// If DescopeSessionManager holds a valid session, the flow runs as that user
val descopeFlow = DescopeFlow(Uri.parse("<URL_FOR_FLOW_IN_SETUP_#1>"))
descopeFlowView.run(descopeFlow)Management Flows with SDKs
Learn how to easily implement management flows for your app with Descope using the Descope backend SDKs.
Testing Authenticated Flows with JWT Input
Learn how to provide a refresh JWT to the Descope flow runner to test post-auth flows, step-up authentication, impersonation, and MFA enrollment.