Navigating an authentication journey on iOS
PingOne Advanced Identity Cloud PingAM iOS
The start() method returns a Node instance, which represents where you are in the authentication journey.
There are four main types of Node instance a journey can return.
| Node type | Description |
|---|---|
|
Indicates a step in the middle of the authentication journey. Call |
|
Represents a bad request, such as invalid credentials. For example a bad password or incorrect one-time passcode (OTP). Access the error message using |
|
Signifies an unexpected error during the authentication process, such as network connectivity issues. Retrieve the underlying cause using |
|
Indicates successful authentication. Obtain the user session details using |
Handle the different node types as follows:
let node = await journey.start("sdkUsernamePasswordJourney")
// Determine the type of the current Node
switch node {
case let continueNode as ContinueNode:
// Proceed to the next step in the authentication journey
let nextNode = continueNode.next()
case let errorNode as ErrorNode:
// Handle server-side errors, such as invalid credentials
let errorMessage = errorNode.message
let input = errorNode.input // Access the raw JSON response with the input attribute
// Display error to the user
case let failureNode as FailureNode:
// // Handle unexpected errors, such as network issues, parsing response problems
let errorCause = failureNode.cause
// Log the error and potentially display a generic message
case let successNode as SuccessNode:
// Authentication successful!
// Retrieve the session
let session = successNode.session
let input = successNode.input // Access the raw JSON response with the input attribute
// Proceed with post-authentication actions
default:
break
}
Handling ContinueNode
The ContinueNode type often contains a list of callbacks that require input from the client.
You can access these using node.callbacks(), and provide the necessary information to each relevant callback. Ensure you call next() to submit the collected data and traverse through the journey nodes.
if let current = node as? ContinueNode {
current.callbacks.forEach { callback in
switch callback {
case let nameCallback as NameCallback:
nameCallback.name = "Your Username"
case let passwordCallback as PasswordCallback:
passwordCallback.password = "Your Password"
// Handle other callback types as they are introduced
default:
break
}
}
}
// Proceed to the next Node with the provided input
let nextNode = current.next()
Specific callback types provide their own properties for accessing labels and setting values:
-
NameCallback -
PasswordCallback
let prompt = (callback as? NameCallback)?.prompt // Access the prompt/label
(callback as? NameCallback)?.name = "Your Username" // Set the user’s input
let prompt = (callback as? PasswordCallback)?.prompt // Access the prompt/label
(callback as? PasswordCallback)?.password = "Your Password" // Set the user’s password
|
Learn about supported callbacks in Supported Nodes and Callbacks. |
Handling FailureNode and ErrorNode
The Journey module distinguishes between the FailureNode and ErrorNode types for different categories of errors encountered during the authentication flow.
FailureNode-
Indicates an unexpected issue that prevents the journey from continuing. This could stem from network problems or data parsing errors.
You can access the underlying
Throwablethat caused the error usingnode.cause()1.You should display a user-friendly generic error message and log the details for support investigation.
ErrorNode-
Signifies an error response from the authentication server, typically an HTTP
4xxor5xxstatus code.These errors often relate to invalid user input or issues server-side.
You can retrieve the specific error message provided by the server using
node.message()2, and access the raw JSON response by usingnode.input3.
let node = await journey.start("sdkUsernamePasswordJourney")
switch node {
case let continueNode as ContinueNode:
// ...
break
case let failureNode as FailureNode:
// Retrieve the underlying error
let errorCause = failureNode.cause (1)
// Log the errorCause for debugging
// Display a generic error message to the user
case let errorNode as ErrorNode:
// Retrieve the server-provided error message
let errorMessage = errorNode.message (2)
// Access the raw JSON error response
let rawResponse = errorNode.input (3)
// Display the specific error message to the user
case let successNode as SuccessNode:
// ...
break
default:
break
}
Handling SuccessNode
WHen the Journey module reaches the SuccessNode type it securely stores the session token. Use the journeyUser() function to create an object representing the user, and call token()1 on that object to obtain the session token itself:
let node = await journey.start("sdkUsernamePasswordJourney")
switch node {
case let continueNode as ContinueNode:
let token: Result<Token, OidcError>?
// Checking the user object
let journeyUser = await journey.journeyUser()
// Retrieve the session token, if available
token = journeyUser.token()
break
case let failureNode as FailureNode:
// ...
break
case let errorNode as ErrorNode:
// ...
break
case let successNode as SuccessNode:
// ...
break
default:
break
}
If you integrated the OIDC module, it provides methods to interact with OpenID Connect tokens, such as obtaining data from the user info endpoint, or revoking the access token:
let node = await journey.start("sdkUsernamePasswordJourney")
switch node {
case let continueNode as ContinueNode:
let token: Result<Token, OidcError>?
// Checking the user object
let journeyUser = await journey.journeyUser()
// Retrieve the session token, if available
token = journeyUser.token()
switch token {
case .success(let token):
await MainActor.run {
self.token = String(describing: token)
let accessToken = token.accessToken
}
LogManager.standard.i("AccessToken: \(self.token.accessToken)")
case .failure(let error):
await MainActor.run {
self.token = "Error: \(error.localizedDescription)"
}
LogManager.standard.e("", error: error)
case .none:
break
}
}
// Fetch user information using the access token (if valid)
journeyUser.userinfo()
// Revoke the current access and refresh tokens
journeyUser.revoke()
// Initiate the logout process, potentially clearing local session data
journeyUser.logout()
break
case let failureNode as FailureNode:
// ...
break
case let errorNode as ErrorNode:
// ...
break
case let successNode as SuccessNode:
// ...
break
default:
break
}