FlowsUse Cases

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)
Was this helpful?

On this page