Ping SDKs

Suspend and resume authentication with magic links

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

The Ping SDKs support the Suspended Authentication feature provided by PingAM.

Suspended authentication lets you pause a user’s progress through an authentication tree, and later resume from the same point.

Any input provided during authentication is saved when the authentication tree is suspended, and restored when the authentication tree is resumed. This lets the authentication tree continue after closing the browser, using a different browser, or even on a different device.

When suspending an authentication tree, you provide the user with a URL that contains a unique ID that lets them resume their authentication. The unique identifier for retrieving the saved progress can only be used once. These URLs are sometimes referred to as "magic links".

Note that the "magic link" represents a users' authentication journey up to the point it was paused. Ensure appropriate additional authentication is used in the remainder of a suspended authentication journey.

Typical use cases include password-less authentication, and email verification during progressive profile completion.

Prepare for suspended authentication

To use suspended authentication within your application, configure your server as follows:

  • Enable outgoing email.

    Configure PingIDM to be able to send outbound email.

    When targeting applications that use the Ping SDK for JavaScript, alter the email template to include a URI that points to the application, rather than an instance of PingAM.

    Your app can then handle the URI the user clicks, and route it appropriately to resume the authentication.

    For more information, see the IDM Self-Service reference.

    For more information, see Configure outbound email in the IDM documentation.

  • Add an "Email Suspend Node" into an authentication tree.

    Enable suspended authentication by adding the node to a tree.

    For more information, see Suspended authentication in the PingAM documentation.

Handle the suspended authentication callback

The Ping SDKs receive a SuspendedTextOutputCallback when a "Email Suspend Node" is reached:

{
    "type": "SuspendedTextOutputCallback",
    "output": [{
        "name": "message",
        "value": "An email has been sent to the address you entered. Click the link in that email to proceed."
    }, {
        "name": "messageType",
        "value": "0"
    }]
}

Your application should display the message field, which instructs the user on how to proceed.

Authentication is now suspended. The Ping SDKs can resume authentication by using the suspendedId parameter that was emailed to the user.

Capture the resume URI

Your application must be able to capture the URI that the user is emailed. That URI contains the suspendedId parameter that is used to resume the user’s authentication or registration journey.

Exact details on how to capture or intercept the URI from the email are beyond the scope of this documentation. However, the following resources may prove useful:

Android:

iOS:

Ensure your application is able to intercept the resume URI, and obtain the value of the suspendedId parameter it contains, before continuing to the next section.

Resume a suspended authentication

After obtaining the value of the suspendedId parameter, use it in you application to continue the user’s authentication or registration journey, as follows:

Resume authentication in an Android app

The FRSession interface accepts the resume URI, including the suspendedId parameter:

FRSession.authenticate(Context, Uri, NodeListener<FRSession>)
If the specified URI scheme, host, or port does not match with those of the PingAM instance configured for the app, the SDK throws an exception.

For example, you could retrieve the resume URI from the 'intent', and pass it into the SDK as follows:

Uri resumeURI = getIntent().getData();
//Note that SuspendedAuthSessionException (401) is returned if suspendedId is invalid or expired
FRSession.authenticate(Context context, Uri resumeUri, NodeListener<FRSession>)

Resume authentication in an iOS app

The FRSession interface accepts the resume URI, including the suspendedId parameter:

@objc public class FRSession: NSObject {
    public static func authenticate<T>(resumeURI: URL, completion:@escaping NodeCompletion<T>)
}

Examples:

AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate {

  // This method is one of AppDelegate protocol that is invoked when
  // iOS tries to open the app using the app's dedicated URL
  func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {

    // validate the resumeURI contains 'suspendedId' parameter
    let resumeURL = url

    //  With given resumeURI, use FRSession to resume authenticate flow
    FRSession.authenticate(resumeURI: resumeURL) { (token: Token?, node, error) in
        //  Handle Node, or the result of continuing the the authentication flow
    }
  }
}
SceneDelegate.swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {

  func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    if let url = URLContexts.first?.url {
      let resumeURL = url  // validate the resumeURI contains 'suspendedId' parameter

      //  With given resumeURI, use FRSession to resume authenticate flow
      FRSession.authenticate(resumeURI: resumeURL) { (token: Token?, node, error) in
        //  Handle Node, or the result of continuing the the authentication flow
      }
    }
  }
}

Resume authentication in a JavaScript app

The method next() in the FRAuth class has been updated to accept the suspendedID value:

interface StepOptions extends ConfigOptions {
  query: {
    suspendedId: string; // you must have captured the suspendedId from the users' resumeURI
  };
}

abstract class FRAuth {
  public static async next(
    previousStep?: FRStep,
    options?: StepOptions,
  ): Promise<FRStep | FRLoginSuccess | FRLoginFailure> {
    const nextPayload = await Auth.next(previousStep ? previousStep.payload : undefined, options);

    // ... continue as normal
  }
}

For example, your app code may resemble the following:

const step = await FRAuth.next(null, {query: {suspendedId: 'i1PUHHWq6bTi3HxNjFGIqEask4g'}});