Journey is an Android library designed to streamline Authentication and Authorization processes for applications utilizing Ping Advanced Identity Cloud and PingAM.
- Ping Advanced Identity Cloud / PingAM Supported Versions
- Android API level 29 or higher
To integrate this module into your Android project, include the following dependency in
your build.gradle.kts (or build.gradle) file:
dependencies {
implementation("com.pingidentity.sdks:journey:<version>")
}Replace <version> with the latest available version of the SDK from the Maven repository. Ensure your
project's repositories block includes Maven Central or the Ping Identity Maven repository.
Journey offers a straightforward API to manage authentication flows and handle the various states encountered during this process.
sequenceDiagram
Developer ->> Journey: Create Journey instance
Journey ->> Developer: Journey
Developer ->> Journey: start(Journey Name)
Journey ->> AIC: /authenticate
AIC ->> AIC: Launch Journey Flow
AIC ->> Journey: Callback(s)
Journey ->> Developer: Node
Developer ->> Developer: Gather credentials
Developer ->> Journey: next()
Journey ->> AIC: /authenticate
AIC ->> Journey: Session Token
Journey ->> Developer: Session Token
opt OIDC Module
Journey ->> AIC: /authorize
AIC ->> Journey: Authorization Code
Journey ->> AIC: /token
AIC ->> Journey: Access Token
Journey ->> Journey: persist access token
Journey ->> Developer: Access Token
end
For a deeper understanding of PingOne AIC Journeys, refer to the official documentation available here.
To begin using the Journey class, instantiate it within your code, providing a configuration block
to customize its
behavior. This configuration allows you to set parameters such as network timeout and logging
preferences.
Here's a basic example of creating a Journey instance and initiating the authentication flow:
val journey = Journey {
serverUrl = "https://openam-sdks.forgeblocks.com/am"
}
var node = journey.start("login") // Start the login authentication flow
node = node.next()Optionally, you can enhance the Journey instance with the oidc module. This module enables the
discovery of OpenID
Connect (OIDC) endpoints using the discoveryEndpoint attribute.
val journey = Journey {
serverUrl = "your_server_url"
module(Oidc) {
clientId = "your_client_id"
discoveryEndpoint =
"https://your_openam_domain/am/oauth2/alpha/.well-known/openid-configuration"
scopes = mutableSetOf("openid", "email", "address", "profile", "phone")
redirectUri = "org.forgerock.demo:/oauth2redirect"
// Add other OIDC configurations as needed
}
}The Journey configuration block offers further customization options:
val journey = Journey {
timeout = 30000 // Network request timeout in milliseconds (default: 15000ms)
logger = Logger.STANDARD // Use the standard logger for Logcat output
serverUrl = "<server_url>" // Specify the server URL
realm = "<realm_name>" // Specify the realm for authentication
cookie = "<cookie_name>" // Specify the cookie name for session management
module(Oidc) {
clientId = "your_client_id"
discoveryEndpoint =
"https://your_openam_domain/am/oauth2/alpha/.well-known/openid-configuration"
// ... other OIDC configurations
// Storage configuration options
storage {
fileName = "test"
keyAlias = "myKeyAlias"
strongBoxPreferred = true
cacheStrategy = CacheStrategy.CACHE_ON_FAILURE
}
}
module(Session) {
storage {
//keep default filename and keyalias.
strongBoxPreferred = false
cacheStrategy = CacheStrategy.CACHE_ON_FAILURE
}
}
}The storage block allows you to configure how data is persisted for each module. The options
include:
- fileName: The name of the file used for persistent storage.
- keyAlias: If provided, enables encryption using AndroidKeyStore. This results in the use of
EncryptedDataStoreStorage. - strongBoxPreferred: If set to
true, attempts to use hardware-backed StrongBox for key storage (if available). - cacheStrategy: Controls in-memory caching behavior. Options:
NO_CACHE: No caching, always fetch fresh data.CACHE_ON_FAILURE: Cache in memory only if storage operation fails.CACHE: Cache in memory, even if the storage operation fails.
Note: Data that store in the cache is kept in plain text and is not encrypted. A device that can output a memory dump may expose sensitive information
The start() method initiates the authentication journey and returns a Node instance,
representing the current state
of the process. You can then use the next() method (available on ContinueNode) to transition to
the subsequent
state.
The resume(uri) method enables the continuation of an authentication flow after you've obtained a
resume URI, for
instance, through an Email Suspend Node.
Optionally, you can provide options when starting the journey, such as forceAuth and noSession
var node = journey.start("myLogin") {
forceAuth = true
noSession = true
} // Initiate the authentication journey
// Determine the type of the current Node
when (node) {
is ContinueNode -> {
// Proceed to the next step in the authentication journey
val nextNode = node.next()
}
is ErrorNode -> {
// Handle server-side errors (e.g., invalid credentials)
val errorMessage = node.message
node.input["name"] // Access the raw JSON response with the input attribute
// Display error to the user
}
is FailureNode -> {
// Handle unexpected errors (e.g., network issues, unexpected errors like parsing response)
val errorCause = node.cause
// Log the error and potentially display a generic message
}
is SuccessNode -> {
// Authentication successful, retrieve the session
val session = node.session
node.input["name"] // Access the raw JSON response with the input attribute
// Proceed with post-authentication actions
}
}The Node class represents various states within the authentication journey:
| Node Type | Description |
|---|---|
| ContinueNode | Indicates a step in the middle of the authentication journey. Call node.next() to advance to the next node. |
| ErrorNode | Represents a bad request from the server, such as invalid credentials (password, OTP, username). Access the error message using node.message. |
| FailureNode | Signifies an unexpected error during the process, like network connectivity issues. Retrieve the underlying cause using node.cause. |
| SuccessNode | Indicates successful authentication. Obtain the user session details using node.session. |
When the current Node is a ContinueNode, it often contains a list of callbacks that require
user input. You can
access these callbacks using node.callbacks() and provide the necessary information to each
relevant callback.
The following Callback will be supported in the Core Journey Module:
| Callback Name | Callback Description |
|---|---|
| BooleanAttributeInputCallback | Collects true or false. |
| ChoiceCallback | Collects single user input from available choices, retrieves selected choice from user interaction. |
| ConfirmationCallback | Retrieve a selected option from a list of options. |
| ConsentMappingCallback | Prompts the user to consent to share their profile data. |
| HiddenValueCallback | Returns form values that are not visually rendered to the end user. |
| KbaCreateCallback | Collects knowledge-based answers. For example, the name of your first pet. |
| MetadataCallback | Injects key-value metadata into the authentication process. |
| NameCallback | Collects a username. |
| NumberAttributeInputCallback | Collects a number. |
| PasswordCallback | Collects a password or one-time pass code. |
| PollingWaitCallback | Instructs the client to wait for the given period and resubmit the request. |
| StringAttributeInputCallback | Collects the values of attributes for use elsewhere in a tree. |
| SuspendedTextOutputCallback | Pause and resume authentication, sometimes known as "magic links". |
| TextInputCallback | Collects text input from the end user. For example, a nickname for their account. |
| TextOutputCallback | Provides a message to be displayed to a user with a given message type. |
| TermsAndConditionsCallback | Collects a user’s acceptance of the configured Terms & Conditions. |
| ValidatedCreatePasswordCallback | Collects a password value with optional password policy validation. |
| ValidatedCreateUsernameCallback | Collects a username value with optional username policy validation. |
Here's how to access and populate the callbacks:
node.callbacks.forEach { callback ->
when (callback) {
is NameCallback -> callback.name = "Your Username"
is PasswordCallback -> callback.password = "Your Password"
// Handle other callback types as they are introduced
}
}
// Proceed to the next Node with the provided input
val nextNode = node.next()Each specific callback type provides its own properties for accessing labels and setting values.
val prompt = (callback as? NameCallback)?.prompt // Access the prompt/label
(callback as? NameCallback)?.name = "Your Input" // Set the user's inputPasswordCallback offers similar properties to NameCallback:
val prompt = (callback as? PasswordCallback)?.prompt // Access the prompt/label
(callback as? PasswordCallback)?.password = "Your Secret" // Set the user's passwordThe Journey SDK distinguishes between FailureNode and ErrorNode for different types of errors
encountered during the
authentication flow.
A FailureNode indicates an unexpected issue that prevents the journey from continuing. This could
stem from network
problems, data parsing errors, or internal SDK issues. In such cases, you can access the underlying
Throwable that
caused the error using node.cause(). It's generally recommended to display a user-friendly generic
error message and
log the details for support investigation.
An ErrorNode, on the other hand, signifies an error response from the authentication server (
typically an HTTP 4xx or
5xx status code). These errors often relate to invalid user input or issues on the server side. You
can retrieve the
specific error message provided by the server using node.message() and access the raw JSON
response via node.input.
val node = journey.start() // Initiate the authentication flow
when (node) {
is ContinueNode -> { /* ... */
}
is ErrorNode -> {
val errorMessage = node.message // Retrieve the server-provided error message
val rawResponse = node.input // Access the raw JSON error response
// Display the specific error message to the user
}
is FailureNode -> {
val errorCause = node.cause // Retrieve the underlying error
// Log the errorCause for debugging
// Display a generic error message to the user
}
is SuccessNode -> { /* ... */
}
}For applications using Jetpack Compose, you can seamlessly integrate the Journey SDK using a
ViewModel to manage the
UI state.
class AuthViewModel : ViewModel() {
private val _state = MutableStateFlow<Node>(Empty)
val state: StateFlow<Node> = _state
private lateinit var journey: Journey // Initialize your Journey instance
fun initializeJourney() {
journey = Journey {
// Configure your Journey instance here
serverUrl = "YOUR_SERVER_URL"
// ... other configurations
}
}
fun startJourney(flowName: String = "login") {
viewModelScope.launch {
val nextNode = journey.start(flowName)
_state.update { nextNode }
}
}
fun next(node: ContinueNode, input: Map<String, String> = emptyMap()) {
viewModelScope.launch {
val nextNode = node.next(input)
_state.update { nextNode }
}
}
}
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun AuthScreen() {
val viewModel: AuthViewModel = viewModel()
val authState by viewModel.state.collectAsState()
// Initialize Journey when the composable is created
LaunchedEffect(key1 = Unit) {
viewModel.initializeJourney()
viewModel.startJourney()
}
when (val node = authState) {
is ContinueNode -> {
// Render UI elements based on the callbacks in the node
// Example: Display text fields for NameCallback and PasswordCallback
val callbacks = node.callbacks
// ... UI logic to collect user input and call viewModel.handleCallbacks()
}
is ErrorNode -> {
// Display the error message
Text(text = "Authentication Error: ${node.message}")
}
is FailureNode -> {
// Display a generic error message
Text(text = "An unexpected error occurred.")
// Optionally log the errorCause
Log.e("AuthScreen", "Authentication Failure", node.cause)
}
is SuccessNode -> {
// Navigate to the main application screen
Text(text = "Authentication Successful!")
// ... Navigation logic
}
}
}After successful authentication using the Oidc module, the user's session information is typically
stored securely.
The Journey instance provides methods to interact with this stored session.
// Retrieve the existing user session. If an active session cookie (ST) exists, 'user' will not be null.
// Note: Even if a user object is retrieved, the access and refresh tokens within might be expired.
val user: User? = journey.user()
val session: SSOToken? = user?.ssoToken() // Retrieve the SSO token if available
user?.let {
val accessToken = it.accessToken() // Retrieve the current access token
it.revoke() // Revoke the current access and refresh tokens
it.userinfo() // Fetch user information using the access token (if valid)
it.logout() // Initiate the logout process, potentially clearing local session data
}The Journey module has several dependencies that are essential for its functionality.
C4Context
title Journey Component Diagram
Component(journey, "Journey", "Library", "Main Journey Library")
Component(logger, "Logger", "Library", "Logging Utility")
Component(journey_plugin, "Journey Plugin", "Library", "Plugin Support")
Component(orchestrate, "Orchestrate", "Library", "Orchestration Logic")
Component(storage, "Storage", "Library", "Data Storage")
Component(android, "Android", "Library", "Android Specific Functionality")
Component(utils, "Utils", "Library", "Utility Functions")
Rel(journey, logger, "Depends on")
Rel(journey, journey_plugin, "Depends on")
Rel(journey, orchestrate, "Depends on")
Rel(journey, storage, "Depends on")
Rel(journey, android, "Depends on")
Rel(journey, utils, "Depends on")
Please refer to journey-plugin for customizing the Journey's callback.
Callback below will be supported by other modules:
| Callback Name | Callback Description |
|---|---|
| AppIntegrity | Collects a generated token from the client to verify the integrity of the app |
| DeviceBinding | Cryptographically bind a mobile device to a user account. |
| DeviceProfileCallback | Collects meta and/or location data about the authenticating device. |
| DeviceSigningVerifier | Verify ownership of a bound device by signing a challenge. |
| PingOneProtectEvaluationCallback | Collects captured contextual data from the client to perform risk evaluations. |
| PingOneProtectInitializeCallback | Instructs the client to start capturing contextual data for risk evaluations |
| ReCaptchaCallback | Provides data required to use a CAPTCHA in your apps. |
| ReCaptchaEnterpriseCallback | Provides data required to use reCAPTCHA Enterprise in your apps. |
| WebAuthnRegistrationCallback | WebAuthn Registration. |
| WebAuthnAuthenticationCallback | WebAuthn Authentication. |
| SelectIdpCallback | External Identity provider selection. |
| IdpCallback | External Identity provider authentication. |
This software may be modified and distributed under the terms of the MIT license. See the LICENSE file for details.
© Copyright 2025-2026 Ping Identity Corporation. All rights reserved.