Ping SDKs

Ping SDKs

Introducing the Ping SDKs

The Ping SDKs let you rapidly build applications against the REST APIs. Leverage identity best practices for token exchange, security and the optimal OAuth 2.0 flow.

New - Obtain OAuth 2.0 tokens from PingOne and PingFederate!

The Ping SDKs support obtaining OAuth 2.0 tokens from a PingOne or PingFederate server.

To learn more, see the tutorials:

Intelligent authentication and callbacks

The Ping SDKs can leverage authentication journeys. Use the SDKs to easily step through each stage of an authentication tree by using callbacks.

For more information, refer to Supported callbacks.

For example, let’s say you want to use this authentication flow:

  1. Collect username and password.

  2. Request KBA information.

  3. Request the user to accept the terms and conditions.

You can use the SDK to make each callback call the next step in the tree. You don’t have to traverse the REST APIs to call the next step.

am auth flow

Real time response to authentication tree changes

The Ping SDKs empower developers to build applications that can handle the changes to your authentication journeys in real time, without having to redeploy your app.

Token management

The Ping SDKs use the OAuth 2.0 auth code flow, and support PKCE.

This method is the best practice for first-party applications. The SDK automatically handles token exchange for you, and also securely stores the tokens. Token refresh is automatically handled by the SDK, so you don’t have to think about it.

The Ping SDKs also support obtaining OAuth 2.0 tokens from a PingOne or PingFederate server. To learn more, refer to the tutorials.

Single sign-on (SSO)

In some scenarios, your company may have multiple native applications that customers have installed on their devices. You can use the SDK to seamlessly sign users in to multiple applications on a device. When the customer signs in to one application, they are automatically signed in to a second application on that device—​without having to authenticate again.

Push authentication and OTP

The Ping SDKs can help you integrate push authentication or one-time password (OTP) capabilities into your mobile applications so your end users don’t have to download and use a dedicated Authenticator application. The SDK’s Authenticator module can support:

  • Time-based one-time passwords (TOTP)

  • HMAC-based one-time password (HOTP)

  • Push notifications

Pluggability and extensibility

The SDK has a modular architecture and is designed with flexibility in mind. Don’t want to use our method for jailbreak detection? No problem! Just plug in your own method, or use any 3rd-party plug-in instead.

Device security profile

Using the SDK, you have the option to collect device profile information to use in your authentication flows. You might use this data to compare a user sign-in to a prior sign-in event. If the device profile has changed too much from the prior event, you can deny the sign-in.

Jailbreak detection

Detecting whether a device is jailbroken or rooted assures developers that a device is managed by the authorized device owner. Jailbroken devices may be running outdated OS versions, or could be missing security patches. Detecting whether a device is jailbroken can provide valuable insight into the security posture of a device. You can feed that insight into authentication flows.

The iOS and Android SDKs generate a score to determine if a device is jailbroken or rooted. There are a number of factors that go into creating this score. The score ranges from 0 to 1.0, where 1 indicates the device is an emulator.

You can use this information as part of an authentication flow to ask the user for another factor, or to deny access entirely.

Device ID and meta data

Ping SDKs can automatically generate a device ID for you. You can use the ID with PingIDM or PingAM to allow your users to manage their devices.

For example, you can insert the device ID and associated data into a user’s profile. This lets them view their devices and set the devices as trusted. You can also decide to use a recognized device in an authentication flow to avoid asking a user for another factor.

It is up to you what information you collect from users and devices. You should always use data responsibly and provide your users appropriate control over data they share with you. You are responsible for complying with any regulations or data protection laws.

Location information

You can collect latitude and longitude information from your users via the Android and iOS SDKs. Apps that use location services must request location permissions from users.

UI development

The Ping SDKs have a sample user interface that can be used for rapid prototyping, or as a reference implementation for building your own UI. Let’s say you want to get an authentication experience in front of some of your users or business stakeholders. You can easily build an authentication journey and display the results immediately in your application.

Web biometrics

The Ping SDK for JavaScript supports web biometrics functionality provided by PingAM.

Web biometrics lets users authenticate by using an authenticator device; for example, the fingerprint scanner on their laptop or phone, or a USB key such as those provided by Yubico, or Google’s Titan security keys.

Communication with authentication devices is handled by the SDK. PingAM requests that the SDK activates authenticators with certain criteria; for example, it must be built-in to the platform, or is a cross-platform roaming USB device. You can also specify that the device must verify the identity of the user, rather than simply that a user is present.

The Ping SDKs have two methods for handling web biometrics: one for registering devices, and another for authenticating using a registered device.

For more information, refer to Web biometrics.

Mobile biometric authentication

Mobile biometric authentication lets users authenticate by using a mobile device’s biometric authentication. Communication with the platform authenticator, for example, with a fingerprint reader or facial recognition system, is handled by the Ping SDK.

The Ping SDK communicates with PingAM to perform biometric registration and authentication using the WebAuthn nodes. Similar to WebAuthn with the Ping SDK for JavaScript, you can configure the nodes in PingAM to request that the SDK activates authenticators with certain criteria.

The Ping SDKs enable passkey support on supported platforms. Passkeys can be synchronized across a user’s devices and browsers, simplifying device registration and enabling passwordless flows.

This feature is available in the ForgeRock SDK for Android 3.0 and the ForgeRock SDK for iOS v3.0 or later. It requires PingOne Advanced Identity Cloud, or PingAM 7.1 or later.

For more information, refer to What are mobile biometrics?.

Social authentication

You can authenticate by using a trusted Identity Provider (IdP), like Apple, Facebook, Google, and many others. These IdPs are used for authentication and identity verification. This is often referred to as Social Login or Social Authentication. These IdPs return the necessary information to integrate user information into your user’s profile.

Depending on the device platform (Android, Web or iOS), the user is redirected from the current web application or login page to the IdP’s authorization server. Or, if on a native mobile app, the user is directed to the IdP’s authentication SDK, if available. Once on the IdP via a web page or SDK, the user will authenticate and provide the necessary consent required for sharing the information. When complete, the user is redirected back to your app or to your server to complete the authentication journey.

For more information, refer to Set up social login.

Choose how users authenticate

The Ping SDKs simplify the integration between your app and the authorization server. The SDKs provide secure, best practice features for important aspects such as session management and handling tokens.

Before you start using the Ping SDKs, you must decide how your users will authenticate.

You can implement user authentication in two ways:

  • Centralized login authentication

  • Embedded login authentication

Compare centralized and embedded login
Item Centralized login Embedded login

User experience consistency

Allows you to create a consistent user experience for each application or site.

Allows you to create a custom user experience for each application or site.

User experience customization

Hard to customize the authentication experience for each app or site.

Lets you create a custom authentication experience for each app.

User redirection for authentication

Impacts the consistency of the user experience—​redirecting users out of the native experience and to a browser for login.

Does not require redirection of user for login.

Users can authenticate directly within the native experience.

Access to user credentials

Applications do not access user credentials.

Allows an app to access and collect user credentials.

This can create security risks if the app is controlled by a third party.

Support for browser single sign-on

Enables seamless browser-based single sign-on across your apps.

Does not support browser-based single sign-on across your apps.

Frequency of application deployment

Should not require you to rebuild or redeploy apps.

May require you to rebuild or redeploy apps after updating the app UX.

Development effort

Does not require a unique UI login page for each app or site.

This can reduce the amount of development and maintenance work.

Requires you to create a UI login page for each app or site.

This can result in extra work and may increase the risk of inconsistencies between apps and sites.

Centralized login

With this option, you reuse the PingAM UI or your own web application for login requests in multiple apps and sites.

When a user attempts to log in to your application or site, they are redirected to a central login UI. After the user authenticates, they are redirected back to your application or site.

Changes to authentication journeys in your PingAM service are available to all apps that use the central login UI. Your app or site does not need to access user credentials.

android central
Figure 1. Centralized login in Android

Use cases

  • If you require a consistent UI and user experience (UX) in all your apps and sites, using centralized login may be the best option.

  • Simple branding and control over UX is sufficient.

  • Your mobile apps use browser-based single sign-on.

For instructions on enabling centralized login, refer to Use centralized login.

Security considerations

  • Using centralized login in apps built by a third party is safer than using embedded login. Third parties cannot access user credentials.

  • User credentials are authenticated in one domain/origin and not sent elsewhere for authentication.

  • Your apps and sites can use browser-based single sign-on.

Embedded login

With this option, each app has to have its own login User Interface (UI).

When a user attempts to log in to your application or site, they authenticate directly within the currently used application instead of being directed to a centralized web application to authenticate the user.

You can seamlessly integrate authentication journeys into your app, giving you complete control over the end user experience.

android embedded
Figure 2. Embedded login in Android

Use cases

  • If branding and controlling the user experience (UX) is important, using an embedded UI may be a good choice.

  • Control and flexibility of the UI and UX is more important to you than consistency, since each app implements its own authentication UI.

  • Your mobile apps do not use browser-based single-sign on.

For instructions on enabling embedded login, follow the JavaScript tutorials.

Security considerations

  • If a third-party hosts or controls your application or site, a third-party can access and misuse the user credentials.

Security

Token and key security

The Ping SDKs handle and store keys and tokens based on the security best practices of each platform.

Token storage

Depending on the authentication use case, the SDKs will potentially have to store and be able to retrieve the session cookie, ID tokens, access tokens, and refresh tokens.

Each token is serving a different use case, and as such how the SDKs handle them can be different.

The following sections cover how the SDKs handle different types of tokens.

Session tokens and cookies

  • On Android and iOS, the session tokens are stored in either the Android keystore or iOS keychain after authentication completes. The tokens are encrypted using a hardware-backed security key when possible and can be retrieved by the SDK on request.

  • When using the Ping SDK for JavaScript, cookies are stored in the browser’s cookie storage. The cookie name matches the one provided by PingAM (such as iPlanetDirectoryPro) and its value is the actual session token. When making requests to PingAM, the value is passed as an authentication cookie. This cookie is configured with the HTTPOnly and Secure attributes, which provide additional layers of security.

ID, access, and refresh tokens

  • On Android and iOS when authorization is completed any OAuth 2.0-related tokens are stored securely locally, encrypted using a hardware-backed security key when possible and can be retrieved by the SDK on request. Tokens are not configured as cloud sharable by default.

  • When using the Ping SDK for JavaScript, the OAuth 2.0 Tokens are stored by using one of the web storage APIs provided by the browser. By default, this uses the browser’s localStorage, but the SDK also supports sessionStorage.

    If required, app developers can provide a custom storage mechanism that can be passed to the SDK.

    We recommend that JavaScript single-page applications do not use refresh tokens or any other long-running authorization elements due to the potentially unsecure nature of the storage mechanisms provided by browsers.

Token lifecycle

The session and OAuth 2.0-related tokens the SDKs handle all have associated expiry times. When a token reaches its expiry time it becomes unusable.

A feature of the SDKs is that they manage the refresh of OAuth 2.0 tokens. The timing of the refresh is based on a threshold value to improve the end-user experience. The SDKs refresh tokens automatically when the token is requested from storage to be used in your application and its expiry is within the threshold.

In the case of access tokens, if a refresh token is present, then the Android and iOS SDKs will use it to obtain a new access token. If the refresh token cannot be used, is not present, or if it has expired, then the SDKs fall back to using the session token to start a new OAuth 2.0 flow.

The SDKs do not handle the refresh of session tokens. If a session token has expired, the app needs to re-authenticate the user.

When an OAuth 2.0 or session token expires, the SDK removes any respective tokens from the secure storage and performs a cleanup. The Android and iOS SDKs also check if the current session token is the same one used to obtain the OAuth 2.0 tokens. In case of a mismatch, then these orphaned tokens are cleaned.

When using SDK logout methods to perform a Logout event, the SDKs revoke existing OAuth 2.0 tokens, revoke the session, and perform a local cleanup. If the SDKs are unable to revoke the session at the server—​for example the network is unavailable—​then the SDKs remove the tokens from local storage.

When using the Ping SDK for JavaScript, if an access token expires within the threshold limit or returns an HTTP 401 Unauthorized error, the SDK attempts to renew it using the same session cookie that was performing the authorization code flows.

The Ping SDK for JavaScript calls the endSession and session?action=logout endpoints during logout, as well as calling revoke whenever you use FRUser.logout. This ensures that the server invalidates the session cookie.

The Ping SDK for JavaScript has no direct control over the session cookie; it can only make requests to the browser that may or may not be acted upon. Instead, it must rely on the server to manage the cookie removal.

Encryption key storage

On supported platforms and devices, the Ping SDKs generate Hardware-Backed encryption keys, and uses them to encrypt and store tokens. This provides an extra level of security against attacks.

  • The Ping SDK for iOS uses the kSecKeyAlgorithmECIESEncryptionCofactorX963SHA256AESGCM encryption algorithm. The key is stored in the Secure Enclave.

    On unsupported devices, the SDK cannot not enforce hardware-backed encryption and will save the tokens in the iOS keychain.

  • The Ping SDK for Android uses a number of different algorithms, depending on the OS version and device functionality. It supports the following encryptors:

    • AndroidLEncryptor: RSA

    • AndroidMEncryptor: AES

    • AndroidNEncryptor: Similar to M, with the addition of setting setInvalidatedByBiometricEnrollment to true

    • AndroidPEncryptor: Similar to N, with the addition of using Android Strongbox

Hardware-backed key storage and encryption

Both the Android and iOS SDKs use platform-provided methods to create hardware-backed encryption keys.

  • On iOS the SDK creates keys within the SecuredKey.swift class. If SecuredKey generation fails, the KeychainManager generates the KeychainService with no SecuredKey. The values in this case will be added to the iOS keychain as kSecClassGenericPassword types.

    If SecuredKey creation is successful then the value is encrypted before being stored. The SecuredKey.swift class provides an isAvailable() public method that validates whether creation of the SecuredKey using Secure Enclave is available on the device or not.

    The SDKs also support devices that do not have Secure Enclave or other hardware-backed encryption functionality.
  • On Android, the SDK uses DefaultTokenManager and DefaultSingleSignOnManager for storing tokens, in addition to SecuredSharedPreferences.java on supported devices.

    Depending on the Android version, the SDK can use more specific encryptors. For more information, see getEncryptor. For information about the different encryptor classes, see the auth folder in GitHub.

Authentication security

The Ping SDKs provide two methods for implementing authentication in your applications:

Embedded authentication

The app developer is responsible for building the login and registration UI.

Uses the Authorization code grant with PKCE flow, based on RFC7636.

When using embedded authentication, the SDKs do not store user credentials on the device or in the browser.

Centralized authentication

We provide a central login app (or web page) that app developers can use with a redirect for JavaScript apps, or by using an in-app browser in Android and iOS applications.

Android and iOS use the OAuth 2.0 for Native Apps for centralized authentication, based on RFC8252, which is recommended way for third-party applications to authenticate in terms of security, as user credentials are never exposed to the third-party web or native application.

Both options have their merits and drawbacks, and the choice usually depends on your use case. For more information, refer to Choose how users authenticate.

The Ping SDKs also use the following protocols for authentication:

WebAuthn for Mobile and Web Biometrics

Based on the WebAuthn W3C spec.

  • The Ping SDK for iOS uses a custom implementation of the protocol that has been created to offer backward compatibility older iOS versions including iOS 12. For more information, see Supported operating systems.

  • The Ping SDK for Android uses the Google FIDO2 API.

Data security

The Ping SDKs do not save or load any user data, such as username or password, or personal information in memory. The only stored keys and data are the Session and OAuth 2.0 tokens required for authentication, and security-related certificates hashes.

The Ping SDKs for iOS and Android support SSL Pinning. The certificate information used is passed in the form of certificate key hashes in the SDKs configuration file. This means you do not have to bundle certificates with your iOS .ipa or Android .apk files.

OAuth 2.0 security with PKCE

Proof Key for Code Exchange (PKCE) mitigates the risks of an OAuth 2.0 attack. Without PKCE, a malicious application running in the same browser as your public client app could compromise the security of your app.

It is good practice to use PKCE for native apps and SPAs, because the code is stored on browsers and devices. Without PKCE, you’d have to include a client secret in those public-facing apps. For enhanced security, you should use PKCE whenever you have the option to use it.

How PKCE works

Your app, with the help of our code, generates a code_verifier (nonce). When a user make a request, your app creates a hash of that code_verifier as a code_challenge. ForgeRock, as an authorization server, saves the hash value.

After the hash is confirmed as valid, your app exchanges its authorization code grant for an access token. Your client app, as the bearer, can use the token to access to the user’s resources.

This diagram depicts the authorization code grant flow in detail:

pkce

If you’re familiar with OpenID Connect (OIDC) specifications, the web app is the relying party, and PingOne Advanced Identity Cloud or PingAM is the authorization server.

For more information on PKCE standards, see the following IETF document: Proof key for code exchange by OAuth public clients.

For more information on how we implement PKCE for native and SPA apps, refer to Authorization code grant with PKCE.

Configure your server

The Ping SDKs for Android, iOS, and JavaScript can access any of the following:

Select your server below for information on how to configure your environment:

In addition to the servers above, the Ping SDKs are also able to obtain OAuth 2.0 access tokens from the following:

Advanced PingOne Advanced Identity Cloud tenant

Follow the steps in this section to configure your PingOne Advanced Identity Cloud tenant for use with the Ping SDKs.

Configure CORS

Cross-origin resource sharing (CORS) lets user agents make cross-domain server requests.

Configure CORS in PingOne Advanced Identity Cloud to allow browsers from trusted domains to access your PingOne Advanced Identity Cloud protected resources.

Create a demo user

Create an identity in your system so that you can test authentication flows using the SDKs.

Create an authentication journey

Authentication journeys provide fine-grained authentication by allowing multiple paths and decision points throughout the flow.

Create a simple journey for use when testing the Ping SDKs.

Register client applications

Register an OAuth 2.0 client application to allow the SDKs to connect to PingOne Advanced Identity Cloud and obtain OAuth 2.0 tokens.

Configure the OAuth 2.0 provider

Configure the PingOne Advanced Identity Cloud OAuth 2.0 provider service for use with the Ping SDKs.

Configure CORS

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

Cross-origin resource sharing (CORS) lets user agents make cross-domain server requests. In PingOne Advanced Identity Cloud, you can configure CORS to allow browsers from trusted domains to access PingOne Advanced Identity Cloud protected resources. For example, you might want a custom web application running on your own domain to get an end-user’s profile information using the PingOne Advanced Identity Cloud REST API.

By default, CORS is configured to let the Ping SDKs access PingOne Advanced Identity Cloud. The SDK samples and tutorials all use https://localhost:8443 as the host domain, which you should add to your CORS configuration.

If you are using a different domain for hosting SDK applications, ensure you add them to the CORS configuration as accepted origin domains.

To update the CORS configuration in PingOne Advanced Identity Cloud, follow these steps:

  1. Log in to your PingOne Advanced Identity Cloud tenant.

  2. At the top right of the screen, click your name, and then select Tenant settings.

  3. On the Global Settings tab, click Cross-Origin Resource Sharing (CORS).

  4. Perform one of the following actions:

    • If available, click ForgeRockSDK.

    • If you haven’t added any CORS configurations to the tenant, click Add a CORS Configuration, select Ping SDK, and then click Next.

  5. Add https://localhost:8443 and any DNS aliases you use to host your Ping SDK for JavaScript applications to the Accepted Origins property.

  6. Complete the remaining fields to suit your environment.

    This documentation assumes the following configuration, required for the tutorials and sample applications:

    Property Values

    Allowed Origin

    https://localhost:8443

    org.forgerock.demo://oauth2redirect

    Accepted Origins

    https://localhost:8443

    Accepted Methods

    GET

    POST

    Accepted Headers

    accept-api-version

    x-requested-with

    content-type

    authorization

    if-match

    x-requested-platform

    iPlanetDirectoryPro

    Exposed Headers

    authorization

    content-type

    Enable Caching

    True

    Max Age

    600

    Allow Credentials

    True

    Click Show advanced settings to be able to edit all available fields.

  7. Click Save CORS Configuration.

Create a demo user

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

The samples and tutorials in this documentation often require that you have an identity set up so that you can test authentication.

To create a demo user in PingOne Advanced Identity Cloud, follow these steps:

  1. Log in to your PingOne Advanced Identity Cloud tenant.

  2. In the left panel, click Identities > Manage.

  3. Click New Alpha realm - User.

  4. Enter the following details:

    • Username = demo

    • First Name = Demo

    • Last Name = User

    • Email Address = demo.user@example.com

    • Password = Ch4ng3it!

  5. Click Save.

Create an authentication journey

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

Authentication journeys provide fine-grained authentication by allowing multiple paths and decision points throughout the flow. Authentication journeys are made up of nodes that define actions taken during authentication.

Each node performs a single task, such as collecting a username or making a simple decision. Nodes can have multiple outcomes rather than just success or failure. For details, see the Authentication nodes configuration reference in the PingAM documentation.

To create a simple journey for use when testing the Ping SDKs, follow these steps:

  1. In your PingOne Advanced Identity Cloud tenant, navigate to Journeys, and click New Journey.

  2. Enter a name, such as sdkUsernamePasswordJourney and click Save.

    The authentication journey designer appears.

  3. Drag the following nodes into the designer area:

    • Page Node

    • Platform Username

    • Platform Password

    • Data Store Decision

  4. Drag and drop the Platform Username and Platform Password nodes onto the Page Node, so that they both appear on the same page when logging in.

  5. Connect the nodes as follows:

    sdk username password journey idcloud en
    Figure 3. Example username and password authentication journey
  6. Click Save.

Register OAuth 2.0 clients in Advanced Identity Cloud

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

OAuth 2.0 client profiles define how applications connect to PingOne Advanced Identity Cloud to obtain OAuth 2.0 tokens.

To allow the SDKs to connect to PingOne Advanced Identity Cloud and obtain OAuth 2.0 tokens, you must register at least one OAuth 2.0 client application.

There are two types of OAuth 2.0 client:

Public client

Public clients do not use a client secret to obtain tokens because they are unable to keep them hidden. The Ping SDKs commonly use this type of client to obtain tokens, as they cannot guarantee safekeeping of the client credentials in a browser or on a mobile device.

Confidential client

Confidential clients are able to securely store credentials and are commonly used for server-to-server communication. For example, the "Todo" API backend provided with the SDK samples uses a confidential client to obtain tokens.

Only the following tutorials and integrations require a confidential client:

Register a public OAuth 2.0 client

To register a public OAuth 2.0 client application for use with the SDKs in PingOne Advanced Identity Cloud, follow these steps:

  1. Log in to your PingOne Advanced Identity Cloud tenant.

  2. In the left panel, click Applications.

  3. Click Custom Application.

  4. Select OIDC - OpenId Connect as the sign-in method, and then click Next.

  5. Select Native / SPA as the application type, and then click Next.

  6. In Name, enter a name for the application, such as Public SDK Client.

  7. In Owners, select a user that is responsible for maintaining the application, and then click Next.

    When trying out the SDKs, you could select the demo user you created previously.
  8. In Client ID, enter sdkPublicClient, and then click Create Application.

    PingOne Advanced Identity Cloud creates the application and displays the details screen.

  9. On the Sign On tab:

    1. In Sign-In URLs, enter the following values:

      https://localhost:8443/callback.html

      https://sdkapp.example.com:8443/callback

      https://com.example.reactnative.todo/callback

      https://com.example.flutter.todo/callback

      org.forgerock.demo://oauth2redirect

      Also add any other domains where you host SDK applications.
    2. In Grant Types, enter the following values:

      Authorization Code

      Refresh Token

    3. In Scopes, enter the following values:

      openid profile email address

  10. Click Show advanced settings, and on the Authentication tab:

    1. In Token Endpoint Authentication Method, select none.

    2. In Client Type, select Public.

    3. Enable the Implied Consent property.

  11. Click Save.

The application is now configured to accept client connections from and issue OAuth 2.0 tokens to the example applications and tutorials covered by this documentation.

Register a confidential OAuth 2.0 client

The following tutorials and integrations require a confidential client:

To register a confidential OAuth 2.0 client application for use with the SDKs in PingOne Advanced Identity Cloud, follow these steps:

  1. Log in to your PingOne Advanced Identity Cloud tenant.

  2. In the left panel, click Applications.

  3. Click Custom Application.

  4. Select OIDC - OpenId Connect as the sign-in method, and then click Next.

  5. Select Web as the application type, and then click Next.

  6. In Name, enter a name for the application, such as Confidential SDK Client.

  7. In Owners, select a user responsible for maintaining the application, and then click Next.

    When trying out the SDKs, you could select the demo user you created previously.
  8. On the Web Settings page:

    1. In Client ID, enter sdkConfidentialClient

    2. In Client Secret, enter a strong password and make a note of it for later use.

      The client secret is not available to view after this step.

      If you forget it, you must reset the secret and reconfigure any connected clients.

    3. Click Create Application.

      PingOne Advanced Identity Cloud creates the application and displays the details screen.

  9. On the Sign On tab, click Show advanced settings, and on the Access tab:

    1. In Default Scopes, enter am-introspect-all-tokens.

  10. Click Save.

Configure the OAuth 2.0 provider

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

The provider specifies the supported OAuth 2.0 configuration options for a realm.

To ensure the PingOne Advanced Identity Cloud OAuth 2.0 provider service is configured for use with the Ping SDKs, follow these steps:

  1. In your PingOne Advanced Identity Cloud tenant, navigate to Native Consoles > Access Management.

  2. In the left panel, click Services.

  3. In the list of services, click OAuth2 Provider.

  4. On the Core tab, ensure Issue Refresh Tokens is enabled.

  5. On the Consent tab, ensure Allow Clients to Skip Consent is enabled.

  6. Click Save Changes.

Self-managed PingAM server

Follow the steps in this section to configure your self-managed PingAM server for use with the Ping SDKs.

Prepare PingAM for HTTPS

Set up PingAM for HTTPS using secure certificates.

Configure CORS

Cross-origin resource sharing (CORS) lets user agents make cross-domain server requests.

Configure CORS in PingAM to allow browsers from trusted domains to access your PingAM-protected resources.

Create a demo user

Create an identity in your system so that you can test authentication flows using the SDKs.

Create an authentication tree

Authentication trees provide fine-grained authentication by allowing multiple paths and decision points throughout the flow.

Create a simple tree for use when testing the Ping SDKs.

Register client applications

Register an OAuth 2.0 client application to allow the SDKs to connect to PingAM and obtain OAuth 2.0 tokens.

Configure the OAuth 2.0 provider

Configure the PingAM OAuth 2.0 provider service for use with the Ping SDKs.

Prepare PingAM for HTTPS

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

The Ping SDKs require HTTPS or a secure browser context - your PingAM server, and the client app that uses the SDK must communicate over HTTPS.

To set up PingAM for HTTPS using secure certificates, refer to Securing network communications in the PingAM documentation.

Configure CORS

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

Cross-origin resource sharing (CORS) lets user agents make cross-domain server requests. In PingOne Advanced Identity Cloud, you can configure CORS to allow browsers from trusted domains to access PingOne Advanced Identity Cloud protected resources. For example, you might want a custom web application running on your own domain to get an end-user’s profile information using the PingOne Advanced Identity Cloud REST API.

By default, CORS is configured to let the Ping SDKs access PingOne Advanced Identity Cloud. The SDK samples and tutorials all use https://localhost:8443 as the host domain, which you should add to your CORS configuration.

If you are using a different URL for hosting SDK applications, ensure you add them to the CORS configuration as accepted origin domains.

To enable CORS in PingAM, and create a CORS filter to allow requests from your configured domain names, follow these steps:

  1. Log in to the PingAM admin UI as an administrator.

  2. Navigate to Configure > Global Services > CORS Service > Configuration, and set the Enable the CORS filter property to true.

    If this property is not enabled, CORS headers are not added to responses from PingAM, and CORS is disabled entirely.
  3. On the Secondary Configurations tab, click Click Add a Secondary Configuration.

  4. In the Name field, enter ForgeRockSDK.

  5. in the Accepted Origins field, enter any DNS aliases you use for your SDK apps.

    This documentation assumes the following configuration:

    Property Values

    Accepted Origins

    https://localhost:8443

    Accepted Methods

    GET

    POST

    Accepted Headers

    accept-api-version

    x-requested-with

    content-type

    authorization

    if-match

    x-requested-platform

    iPlanetDirectoryPro

    Exposed Headers

    authorization

    content-type

  6. Click Create.

    PingAM displays the configuration of your new CORS filter.

  7. On the CORS filter configuration page:

    1. Ensure Enable the CORS filter is enabled.

    2. Set the Max Age property to 600

    3. Ensure Allow Credentials is enabled.

    sdk cors filter am en
    Figure 4. Example of the completed Ping SDK CORS filter
  8. Click Save Changes.

Create a demo user

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

The samples and tutorials in this documentation often require that you have an identity set up so that you can test authentication.

To create a demo user in PingAM, follow these steps:

  1. Log in to the PingAM admin UI as an administrator.

  2. Navigate to Identities, and then click Add Identity.

  3. Enter the following details:

    • User ID = demo

    • Password = Ch4ng3it!

    • Email Address = demo.user@example.com

  4. Click Create.

Create an authentication tree

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

Authentication trees provide fine-grained authentication by allowing multiple paths and decision points throughout the authentication flow. Authentication trees are made up of nodes that define actions taken during authentication.

Each node performs a single task, such as collecting a username or making a simple decision. Nodes can have multiple outcomes rather than just success or failure. For details, see the Authentication nodes configuration reference in the PingAM documentation.

To create a simple tree for use when testing the Ping SDKs, follow these steps:

  1. Under Realm Overview, click Authentication Trees, then click Create Tree.

  2. Enter a tree name, for example sdkUsernamePasswordJourney, and then click Create.

    The authentication tree designer appears, showing the Start entry point connected to the Failure exit point.

  3. Drag the following nodes from the Components panel on the left side into the designer area:

    • Page Node

    • Username Collector

    • Password Collector

    • Data Store Decision

  4. Drag and drop the Username Collector and Password Collector nodes onto the Page Node, so that they both appear on the same page when logging in.

  5. Connect the nodes as follows:

    trees node login example
    Figure 5. Example username and password authentication tree
  6. Select the Page Node, and in the Properties pane, set the Stage property to UsernamePassword.

    You can configure the node properties by selecting a node and altering properties in the right-hand panel.

    One of the samples uses this specific value to determine the custom UI to display.

  7. Click Save.

Register OAuth 2.0 clients in AM

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

The OAuth 2.0 client profile defines how an application connects to PingAM to obtain OAuth 2.0 tokens.

To allow the SDKs to connect to AMd and obtain OAuth 2.0 tokens, you must register an OAuth 2.0 client application.

There are two types of OAuth 2.0 client:

Public client

Public clients do not use a client secret to obtain tokens because they are unable to keep them hidden. The Ping SDKs commonly use this type of client to obtain tokens, as they cannot guarantee safekeeping of the client credentials in a browser or on a mobile device.

Confidential client

Confidential clients are able to store credentials securely and are commonly used for server-to-server communication.

Only the following tutorials and integrations require a confidential client:

Register a public OAuth 2.0 client

To register a public OAuth 2.0 client application for use with the SDKs in AM, follow these steps:

  1. Log in to the PingAM admin UI as an administrator.

  2. Navigate to Applications > OAuth 2.0 > Clients, and then click Add Client.

  3. In Client ID, enter sdkPublicClient.

  4. Leave Client secret empty.

  5. In Redirection URIs, enter the following values:

    https://sdkapp.example.com:8443/callback

    https://com.example.reactnative.todo/callback

    https://com.example.flutter.todo/callback

    https://localhost:8443/callback.html

    org.forgerock.demo://oauth2redirect

    The Ping SDK for JavaScript attempts to load the redirect page to capture the OAuth 2.0 code and state query parameters that the server appended to the redirect URL.

    If the page you redirect to does not exist, takes a long time to load, or runs any JavaScript you might get a timeout, delayed authentication, or unexpected errors.

    To ensure the best user experience, we highly recommend that you redirect to a static HTML page with minimal HTML and no JavaScript when obtaining OAuth 2.0 tokens.

    Also add any other domains where you will be hosting SDK applications.
  6. In Scopes, enter the following values:

    openid profile email address

  7. Click Create.

    PingAM creates the new OAuth 2.0 client, and displays the properties for further configuration.

  8. On the Core tab:

    1. In Client type, select Public.

    2. Disable Allow wildcard ports in redirect URIs.

    3. Click Save Changes.

  9. On the Advanced tab:

    1. In Grant Types, enter the following values:

      Authorization Code
      Refresh Token
    2. In Token Endpoint Authentication Method, select None.

    3. Enable the Implied consent property.

  10. Click Save Changes.

Register a confidential OAuth 2.0 client

The following tutorials and integrations require a confidential client:

To register a confidential OAuth 2.0 client application for use with the SDKs in AM, follow these steps:

  1. Log in to the PingAM admin UI as an administrator.

  2. Navigate to Applications > OAuth 2.0 > Clients, and then click Add Client.

  3. In Client ID, enter sdkConfidentialClient.

  4. In Client Secret, enter a strong password and make a note of it for later use.

    The client secret is not available to view after this step.

    If you forget it, you must reset the secret and reconfigure any connected clients.

  5. In Default Scopes, enter am-introspect-all-tokens.

    PingAM creates the new OAuth 2.0 client and displays the properties for further configuration.

  6. On the Advanced tab:

    1. Enable the Implied consent property.

  7. Click Save Changes.

Configure the OAuth 2.0 provider

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

The provider specifies the supported OAuth 2.0 configuration options for a realm.

To ensure the PingAM OAuth 2.0 provider service is configured for use with the Ping SDKs, follow these steps:

  1. Log in to the PingAM admin UI as an administrator.

  2. In the left panel, click Services.

  3. In the list of services, click OAuth2 Provider.

  4. On the Core tab, ensure Issue Refresh Tokens is enabled.

  5. On the Consent tab, ensure Allow Clients to Skip Consent is enabled.

  6. Click Save Changes.

PingOne tenant

Follow the steps in this section to configure your PingOne tenant for use with the Ping SDKs.

Create a demo user

Create an identity in your system so that you can test authentication flows using the SDKs.

Create a revoke resource

Create a custom resource in PingOne to allow the Ping SDKs to revoke OAuth 2.0 tokens.

Register OAuth 2.0 applications

Register OAuth 2.0 client applications to allow the Ping SDKs to connect to PingOne and use OAuth 2.0 tokens.

Create a demo user in PingOne

The samples and tutorials in this documentation often require that you have an identity set up so that you can test authentication.

To create a demo user in PingOne, follow these steps:

  1. Log in to your PingOne administration console.

  2. In the left panel, navigate to Directory > Users.

  3. Next to the Users label, click the plus icon ().

    PingOne displays the Add User panel.

  4. Enter the following details:

    • Given Name = Demo

    • Family Name = User

    • Username = demo

    • Email = demo.user@example.com

    • Population = Default

    • Password = Ch4ng3it!

  5. Click Save.

Create a revoke resource in PingOne

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

To allow the Ping SDKs to revoke access tokens issued by PingOne, you must create a custom resource that is assigned the revoke scope in your PingOne tenant.

To create a custom resource and assign the revoke scope, follow these steps:

  1. Log in to your PingOne administration console.

  2. In the left panel, navigate to Applications > Resources.

  3. Next to the Resources label, click the plus icon ().

    PingOne displays the Add Custom Resource panel.

  4. In Resource Name, enter a name for the custom resource, for example SDK Revoke Resource, and then click Next.

  5. On the Attributes page, click Next.

  6. On the Scopes page, click Add Scope.

  7. In Scope Name, enter revoke, and then click Save.

When you have created the custom resource in your PingOne instance, continue with the next step, Register OAuth 2.0 applications.

More information

For more information on resources in PingOne, refer to Adding a custom resource.

Register OAuth 2.0 applications in PingOne

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

OAuth 2.0 client application profiles define how applications connect to PingOne and obtain OAuth 2.0 tokens.

Register a public OAuth 2.0 client for Web apps

To register a public OAuth 2.0 client application in PingOne for use with the Ping SDK for JavaScript, follow these steps:

  1. Log in to your PingOne administration console.

  2. In the left panel, navigate to Applications > Applications.

  3. Next to the Applications label, click the plus icon ().

    PingOne displays the Add Application panel.

  4. In Application Name, enter a name for the profile, for example sdkPublicClient

  5. Select OIDC Web App as the Application Type, and then click Save.

  6. On the Configuration tab, click the pencil icon ().

    1. In Grant Type, select the following values:

      Authorization Code

      Refresh Token

    2. In Redirect URIs, enter the following value:

      https://localhost:8443

      Also add any other URLs where you host SDK applications.

      Failure to add redirect URLs that exactly match your client app’s values can cause PingOne to display an error message such as `Redirect URI mismatch ` when attempting to end a session by redirecting from the SDK.

    3. In Token Endpoint Authentication Method, select None.

    4. In Signoff URLs, enter the following value:

      https://localhost:8443

      Also add any other URLs that redirect users to PingOne to end their session.

      Failure to add sign off URLs that exactly match your client app’s values can cause PingOne to display an error message such as invalid post logout redirect URI when attempting to end a session by redirecting from the SDK.

    5. In CORS Settings, in the drop-down select Allow specific origins, and in the Allowed Origins field, enter the URL where you will be running the sample app.

      For example:

      https://localhost:8443

    6. Click Save.

  7. On the Resources tab, next to Allowed Scopes, click the pencil icon ().

    1. In Scopes, select the following values:

      email

      phone

      profile

      SDK Revoke Resource

      The openid scope is selected by default.

      The result resembles the following:

      Ensure you select the custom `revoke` scope.
      Figure 6. Adding scopes, including the custom "revoke" scope to an application.
  8. Optionally, on the Policies tab, click the pencil icon () to select the authentication policies for the application.

    Applications that have no authentication policy assignments use the environment’s default authentication policy to authenticate users.

    If you have a DaVinci license, you can select PingOne policies or DaVinci Flow policies, but not both. If you do not have a DaVinci license, the page only displays PingOne policies.

    To use a PingOne policy:

    1. Click Add policies and then select the policies that you want to apply to the application.

    2. Click Save.

      PingOne applies the policies in the order in which they appear in the list. PingOne evaluates the first policy in the list first. If the requirements are not met, PingOne moves to the next one.

      For more information, see Authentication policies for applications.

    To use a DaVinci Flow policy:

    1. You must clear all PingOne policies. Click Deselect all PingOne Policies.

    2. In the confirmation message, click Continue.

    3. On the DaVinci Policies tab, select the policies that you want to apply to the application.

    4. Click Save.

      PingOne applies the first policy in the list.

  9. Click Save.

  10. Enable the OAuth 2.0 client application by using the toggle next to its name:

    Enable the application using the toggle.
    Figure 7. Enable the application using the toggle.

The application is now configured to accept client connections from and issue OAuth 2.0 tokens to the JavaScript example PingOne applications and tutorials covered by this documentation.

Register a public OAuth 2.0 client for native mobile apps

To register a public OAuth 2.0 client application in PingOne for use with the Ping SDKs for Android and iOS, follow these steps:

  1. Log in to your PingOne administration console.

  2. In the left panel, navigate to Applications > Applications.

  3. Next to the Applications label, click the plus icon ().

    PingOne displays the Add Application panel.

  4. In Application Name, enter a name for the profile, for example sdkNativeClient

  5. Select Native as the Application Type, and then click Save.

  6. On the Configuration tab, click the pencil icon ().

    1. In Grant Type, select the following values:

      Authorization Code

      Refresh Token

    2. In Redirect URIs, enter the following value:

      org.forgerock.demo://oauth2redirect

    3. In Token Endpoint Authentication Method, select None.

    4. In Signoff URLs, enter the following value:

      org.forgerock.demo://oauth2redirect

    5. Click Save.

  7. On the Resources tab, next to Allowed Scopes, click the pencil icon ().

    1. In Scopes, select the following values:

      email

      phone

      profile

      SDK Revoke Resource

      The openid scope is selected by default.

      The result resembles the following:

      Ensure you select the custom `revoke` scope.
      Figure 8. Adding scopes, including the custom "revoke" scope to an application.
  8. Optionally, on the Policies tab, click the pencil icon () to select the authentication policies for the application.

    Applications that have no authentication policy assignments use the environment’s default authentication policy to authenticate users.

    If you have a DaVinci license, you can select PingOne policies or DaVinci Flow policies, but not both. If you do not have a DaVinci license, the page only displays PingOne policies.

    To use a PingOne policy:

    1. Click Add policies and then select the policies that you want to apply to the application.

    2. Click Save.

      PingOne applies the policies in the order in which they appear in the list. PingOne evaluates the first policy in the list first. If the requirements are not met, PingOne moves to the next one.

      For more information, see Authentication policies for applications.

    To use a DaVinci Flow policy:

    1. You must clear all PingOne policies. Click Deselect all PingOne Policies.

    2. In the confirmation message, click Continue.

    3. On the DaVinci Policies tab, select the policies that you want to apply to the application.

    4. Click Save.

      PingOne applies the first policy in the list.

  9. Click Save.

  10. Enable the OAuth 2.0 client application by using the toggle next to its name:

    Enable the application using the toggle.
    Figure 9. Enable the application using the toggle.

The application is now configured to accept client connections from and issue OAuth 2.0 tokens to the Android and iOS PingOne example applications and tutorials covered by this documentation.

Next steps

You can now configure the Ping SDKs to connect to PingOne and obtain OAuth 2.0 access tokens.

You will need some of the property values from the client profile you created to configure the SDKs:

  1. Log in to your PingOne administration console.

  2. In the left panel, click Applications.

  3. Select the OAuth 2.0 application profile you created earlier. For example, sdkPublicClient or sdkNativeClient.

  4. On the Configuration tab, expand the URLs section, and make a note of the following values:

    OIDC Discovery Endpoint

    Client ID

    Obtaining values from the OIDC application profile in PingOne.
    Figure 10. Obtaining values from the OIDC application profile in PingOne.

When you have obtained these values from your PingOne instance, continue to the tutorials:

More information

For more information on applications in PingOne, refer to Adding an application.

PingFederate server

Follow the steps in this section to configure your PingFederate server for use with the Ping SDKs.

Configure CORS in PingFederate

Cross-origin resource sharing (CORS) lets user agents make cross-domain server requests.

Configure CORS in PingFederate to allow trusted applications and browsers from trusted domains to access your PingFederate resources.

Register OAuth 2.0 applications

Register OAuth 2.0 client applications to allow the Ping SDKs to connect to PingFederate and get OAuth 2.0 tokens.

Configure CORS in PingFederate

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

Cross-origin resource sharing (CORS) lets user agents make cross-domain server requests. In PingFederate, you can configure CORS to allow browsers or apps from trusted domains to access protected resources.

To configure CORS in PingFederate follow these steps:

  1. Log in to the PingFederate administration console as an administrator.

  2. Navigate to System  OAuth Settings  Authorization Server Settings.

  3. In the Cross-Origin Resource Sharing Settings section, in the Allowed Origin field, enter any DNS aliases you use for your SDK apps.

    This documentation assumes the following configuration:

    Property Values

    Allowed Origin

    https://localhost:8443

    org.forgerock.demo://oauth2redirect

  4. Click Save.

    After changing PingFederate configuration using the administration console, you must replicate the changes to each server node in the cluster before they take effect.

    In the PingFederate administration console, navigate to System > Server > Cluster Management, and click Replicate.

    Your PingFederate server is now able to accept connections from origins hosting apps built with the Ping SDKs.

Register OAuth 2.0 applications in PingFederate

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

OAuth 2.0 client application profiles define how applications connect to PingFederate and obtain OAuth 2.0 tokens.

  • To allow the Ping SDKs to connect to PingFederate and obtain OAuth 2.0 tokens, you must register an OAuth 2.0 client application:

    1. Log in to the PingFederate administration console as an administrator.

    2. Navigate to Applications  OAuth  Clients.

    3. Click Add Client.

      PingFederate displays the Clients | Client page.

    4. In Client ID and Name, enter a name for the profile, for example sdkPublicClient

      Make a note of the Client ID value, you will need it when you configure the sample code.

    5. In Client Authentication, select None.

    6. In Redirect URIs, add the following values:

      https://localhost:8443

      org.forgerock.demo://oauth2redirect

      Also add any other URLs where you host SDK applications.

      Failure to add redirect URLs that exactly match your client app’s values can cause PingFederate to display an error message such as `Redirect URI mismatch ` when attempting to end a session by redirecting from the SDK.

    7. In Allowed Grant Types, select the following values:

      Authorization Code

      Refresh Token

    8. In the OpenID Connect section:

      1. In Logout Mode, select Ping Front-Channel

      2. In Front-Channel Logout URIs, add the following values:

        org.forgerock.demo://oauth2redirect

        https://localhost:8443/callback.html

        Also add any other URLs that redirect users to PingFederate to end their session.

        Failure to add sign off URLs that exactly match your client app’s values can cause PingFederate to display an error message such as invalid post logout redirect URI when attempting to end a session by redirecting from the SDK.

      3. In Post-Logout Redirect URIs, add the following values:

        org.forgerock.demo://oauth2redirect

        https://localhost:8443/callback.html

    9. Click Save.

      After changing PingFederate configuration using the administration console, you must replicate the changes to each server node in the cluster before they take effect.

      In the PingFederate administration console, navigate to System > Server > Cluster Management, and click Replicate.

      The application is now configured to accept client connections from and issue OAuth 2.0 tokens to the Ping SDK PingFederate example applications and tutorials covered by this documentation.

Next steps

You can now configure the Ping SDKs to connect to PingFederate and obtain OAuth 2.0 access tokens:

More information

For more information on applications in PingFederate, refer to Managing OAuth clients.

Configure the Ping SDKs

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

You need to configure certain settings in the SDKs so they can connect to your authorization server to authenticate your users and obtain tokens.

The method you use to configure these settings depends on which SDK you are using.

Ping SDK for Android configuration

Configure SDK properties in your Android app by editing the strings.xml file, located in app/src/main/res/values.

Server properties

Property Description

forgerock_url

The base URL of the server to connect to, including port and deployment path.

Identity Cloud example:

https://openam-forgerock-sdks.forgeblocks.com/am

Self-hosted example:

https://openam.example.com:8443/openam

forgerock_realm

The realm in which the OAuth 2.0 client profile and authentication journeys are configured.

For example, alpha.

Defaults to the self-hosted top-level realm root.

forgerock_timeout

A timeout, in seconds, for each request that communicates with your server.

forgerock_cookie_name

The name of the cookie that contains the session token.

For example, with a self-hosted PingAM server this value might be iPlanetDirectoryPro.

PingOne Advanced Identity Cloud tenants use a random alpha-numeric string.

To locate the cookie name in an PingOne Advanced Identity Cloud tenant, navigate to Tenant settings > Global Settings, and copy the value of the Cookie property.

Journey properties

Property Description

forgerock_auth_service

The name of a user authentication tree configured in your server.

For example, sdkUsernamePasswordJourney.

forgerock_registration_service

The name of a user registration tree configured in your server.

For example, sdkRegistrationJourney.

OAuth 2.0 properties

Property Description

forgerock_oauth_client_id

The client_id of the OAuth 2.0 client profile to use.

For example, sdkPublicClient.

forgerock_oauth_redirect_uri

The redirect_uri as configured in the OAuth 2.0 client profile.

This value must match a value configured in your OAuth 2.0 client, but is not actually used by the Android application.

For example, https://sdkapp.example.com:8443/callback.

forgerock_oauth_sign_out_redirect_uri

The URI to redirect users to after they sign out and revoke their OAuth 2.0 tokens.

For example, com.forgerock.app://oauth2redirect.

forgerock_oauth_scope

A list of scopes to request when performing an OAuth 2.0 authorization flow, separated by spaces.

For example, openid profile email address.

forgerock_oauth_threshold

A threshold, in seconds, to refresh an OAuth 2.0 token before the access_token expires.

Defaults to 30 seconds.

SSL pinning properties

Property Description

forgerock_ssl_pinning_public_key_hashes

An array of public key certificate hashes (strings) for trusted sites and services.

buildSteps

An array of BuildStep objects to provide additional SSL pinning parameters to OkHttpClient instances.

Custom endpoint properties

Property Description

forgerock_authenticate_endpoint

Override the default path to your server’s /json/authenticate endpoint.
Default: /json/realms/{forgerock_realm}/authenticate

forgerock_authorize_endpoint

Override the default path to the PingAM’s /oauth2/authorize endpoint.
Default: /oauth2/realms/{forgerock_realm}/authorize

forgerock_token_endpoint

Override the default path to your server’s /oauth2/access_token endpoint.
Default: /oauth2/realms/{forgerock_realm}/access_token

forgerock_revoke_endpoint

Override the default path to your server’s /oauth2/token/revoke endpoint.
Default: /oauth2/realms/{forgerock_realm}/token/revoke

forgerock_userinfo_endpoint

Override the default path to your server’s /oauth2/userinfo endpoint.
Default: /oauth2/realms/{forgerock_realm}/userinfo

forgerock_session_endpoint

Override the default path to your server’s /json/sessions endpoint.

SDK permissions

The Ping SDK for Android requires certain permissions depending on your use case.

Internet permissions (required)

Your Android app requires the following permission to access the Internet:

Permission name Description

android.permission.INTERNET

Lets applications open network sockets.

Location permissions (optional)

Your Android app requires the following location permissions if your app needs to capture location information during device profiling:

Permission name Description

android.permission.ACCESS_FINE_LOCATION

Lets the app access precise location.

android.permission.ACCESS_COARSE_LOCATION

Lets the app access approximate location.

Ping SDK for iOS Properties

Configure SDK properties in your iOS app by editing the FRAuthConfig.plist file.

Server properties

Property Description

forgerock_url

The base URL of the server to connect to, including port and deployment path.

Identity Cloud example:

https://openam-forgerock-sdks.forgeblocks.com/am

Self-hosted example:

https://openam.example.com:8443/openam

forgerock_realm

The realm in which the OAuth 2.0 client profile and authentication journeys are configured.

For example, alpha.

Defaults to the self-hosted top-level realm root.

forgerock_timeout

A timeout, in seconds, for each request that communicates with your server.

forgerock_enable_cookie

When true, enables cookie use.

Defaults to true.

forgerock_cookie_name

The name of the cookie that contains the session token.

For example, with a self-hosted PingAM server this value might be iPlanetDirectoryPro.

PingOne Advanced Identity Cloud tenants use a random alpha-numeric string.

To locate the cookie name in an PingOne Advanced Identity Cloud tenant, navigate to Tenant settings > Global Settings, and copy the value of the Cookie property.

Journey properties

Property Description

forgerock_auth_service

The name of a user authentication tree configured in your server.

For example, sdkUsernamePasswordJourney.

forgerock_registration_service_name

The name of a user registration tree configured in your server.

For example, sdkRegistrationJourney.

OAuth 2.0 properties

Property Description

forgerock_oauth_client_id

The client_id of the OAuth 2.0 client profile to use.

For example, sdkPublicClient.

forgerock_oauth_redirect_uri

The redirect_uri as configured in the OAuth 2.0 client profile.

[NOTE] This value must match a value configured in your OAuth 2.0 client, but is not actually used by the iOS application.

For example, https://sdkapp.example.com:8443/callback.

forgerock_oauth_scope

A list of scopes to request when performing an OAuth 2.0 authorization flow, separated by spaces.

For example, openid profile email address.

forgerock_oauth_threshold

A threshold, in seconds, to refresh an OAuth 2.0 token before the access_token expires.

Defaults to 30 seconds.

SSL pinning properties

Property Description

forgerock_ssl_pinning_public_key_hashes

An array of public key certificate hashes (strings) for trusted sites and services.

forgerock_keychain_access_group

Keychain access group for the shared keychain.

Custom endpoint properties

Property Description

forgerock_authenticate_endpoint

Override the default path to your server’s /json/authenticate endpoint.
Default: /json/realms/{forgerock_realm}/authenticate

forgerock_authorize_endpoint

Override the default path to the PingAM’s /oauth2/authorize endpoint.
Default: /oauth2/realms/{forgerock_realm}/authorize

forgerock_token_endpoint

Override the default path to your server’s /oauth2/access_token endpoint.
Default: /oauth2/realms/{forgerock_realm}/access_token

forgerock_revoke_endpoint

Override the default path to your server’s /oauth2/token/revoke endpoint.
Default: /oauth2/realms/{forgerock_realm}/token/revoke

forgerock_userinfo_endpoint

Override the default path to your server’s /oauth2/userinfo endpoint.
Default: /oauth2/realms/{forgerock_realm}/userinfo

forgerock_session_endpoint

Override the default path to your server’s /json/sessions endpoint.

Ping SDK for JavaScript Properties

Configure SDK properties in your JavaScript app by editing a serverConfig object, a parameter of the forgerock.Config.set() function.

Ping SDK for JavaScript properties
Property Description

serverConfig

An interface for configuring how the SDK contacts the PingAM instance.

Contains baseUrl and timeout.

serverConfig: {baseUrl}

The base URL of the server to connect to, including port and deployment path.

Identity Cloud example:

https://openam-forgerock-sdks.forgeblocks.com/am

Self-hosted example:

https://openam.example.com:8443/openam

serverConfig: {wellknown}

A URL to the server’s .well-known/openid-configuration endpoint.

Use the Config.setAsync() method to set SDK configuration using values derived from those provided at the URL.

Example:

https://openam-forgerock-sdks.forgeblocks.com/am/oauth2/realms/root/realms/alpha/.well-known/openid-configuration

Self-hosted example:

https://openam.example.com:8443/openam/oauth2/realms/root/.well-known/openid-configuration

serverConfig: {timeout}

A timeout, in milliseconds, for each request that communicates with your server.

For example, for 30 seconds specify 30000.

Defaults to 5000 (5 seconds).

realmPath

The realm in which the OAuth 2.0 client profile and authentication journeys are configured.

For example, alpha.

Defaults to the self-hosted top-level realm root.

tree

The name of the user authentication tree configured in your server.

For example, sdkUsernamePasswordJourney.

clientId

The client_id of the OAuth 2.0 client profile to use.

redirectUri

The redirect_uri as configured in the OAuth 2.0 client profile.

The Ping SDK for JavaScript attempts to load the redirect page to capture the OAuth 2.0 code and state query parameters that the server appended to the redirect URL.

If the page you redirect to does not exist, takes a long time to load, or runs any JavaScript you might get a timeout, delayed authentication, or unexpected errors.

To ensure the best user experience, we highly recommend that you redirect to a static HTML page with minimal HTML and no JavaScript when obtaining OAuth 2.0 tokens.

For example, https://localhost:8443/callback.html.

scope

A list of scopes to request when performing an OAuth 2.0 authorization flow, separated by spaces.

For example, openid profile email address.

oauthThreshold

A threshold, in seconds, to refresh an OAuth 2.0 token before the access_token expires.

Defaults to 30 seconds.

logLevel

Specify whether the SDK should output its log messages in the console and the level of messages to display.

One of:

  • none (default)

  • info

  • warn

  • error

  • debug

logger

Specify a function to override the default logging behavior.

tokenStore

The API to use for storing tokens on the client:

sessionStorage

Store tokens using the sessionStorage API. The browser clears session storage when a page session ends.

localStorage

Store tokens using the localStorage API. The browser saves local storage data across browser sessions. This is the default setting, as it provides the highest browser compatibility.

prefix

Override the default fr prefix string applied to the keys used for storing data on the client, such as tokens, device IDs, and information about the steps in a journey.

For example, the key used for storing tokens consists of the prefix, followed by the ID of the OAuth 2.0 client:

fr-sdkPublicClient.

platformHeader

Specify whether to include an X-Requested-Platform header in outgoing requests.

The server can use the value of this header to alter the logic of an authentication flow. For example, if the value indicates a JavaScript web app, the journey could avoid device binding nodes, as they are only supported by Android and iOS apps.

Defaults to false.

Custom endpoint properties

You can override the default paths for a number of endpoints by adding a serverConfig {paths} structure.

You can use a server’s .well-known/openid-configuration endpoint to configure paths automatically.

Enter the well-known URL in the serverConfig.wellknown property, and use the asynchronous Config.setAsync() method to configure the SDK:

await Config.setAsync({
  clientId: 'sdkPublicClient',
  redirectUri: 'https://localhost:8443/callback.html',
  scope: 'openid profile email address',
  serverConfig: {
    wellknown: 'https://openam-forgerock-sdks.forgeblocks.com/am/oauth2/realms/root/realms/alpha/.well-known/openid-configuration'
  },
});
Available properties to override default paths
Property Description

authenticate

Override the default /json/authenticate endpoint.
Default: json/{realmPath}/authenticate

authorize

Override the default /oauth2/authorize endpoint.
Default: oauth2/{realmPath}/authorize

accessToken

Override the default /oauth2/access_token endpoint.
Default: oauth2/{realmPath}/access_token

revoke

Override the default /oauth2/token/revoke endpoint.
Default: oauth2/{realmPath}/token/revoke

userInfo

Override the default /oauth2/userinfo endpoint.
Default: oauth2/{realmPath}/userinfo

sessions

Override the default /json/sessions endpoint.
Default: json/{realmPath}/sessions

endSession

Override the default /oauth2/connect/endSesison endpoint.
Default: oauth2/{realmPath}/connect/endSession

Example:

forgerock.Config.set({
    serverConfig: {
        baseUrl: 'https://openam-forgerock-sdks.forgeblocks.com/am',
        paths: {
            authenticate: 'iam/endpoints/authN',
            authorize: 'iam/endpoints/authZ'
        },
        timeout: 30000,
    },
    realmPath: 'alpha',
    tree: 'sdkUsernamePasswordJourney'
});
Any endpoint paths that you do not override use the pre-configured defaults.

Configure the SDKs dynamically

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

It can sometimes be convenient to change configurations without reinstalling your app. For example, to switch test environments on the fly or to switch to a different service, change the app settings programmatically with dynamic configuration.

  • Android

  • iOS

Use the FROptionsBuilder methods to build an FROptions object, and pass the object to the FRAuth.start() method.

The builder provides access to the settings defined by the following properties:

Ping SDK for Android dynamic properties

Domain (frOptionsBuilder attribute)

Property name

Description

Required

Android (Java)

Android (Kotlin)

Server (server)

setUrl

url

The base URL of the PingAM instance to connect to, including port and deployment path; for example, https://openam.example.com:8443/openam.

setRealm

realm

The realm where the OAuth 2.0 client profile is configured.

Default: root

setTimeout

timeout

A timeout, in seconds, for each request that communicates with PingAM.

Default: 30

setCookieName

cookieName

The name of the cookie that contains the session token, for example, iPlanetDirectoryPro.

To locate the cookie name in an PingOne Advanced Identity Cloud tenant, go to Tenant settings > Global Settings > Cookie.

Default: iPlanetDirectoryPro

setCookieCache

cookieCache

Time, in seconds, to cache the session token cookie in memory.

Default: 0

Journeys (service)

setAuthService

authService

The name of the user authentication tree configured in PingAM.

setRegistrationService

registrationService

The name of the user registration tree configured in PingAM.

OAuth 2.0 (oauth)

setOauthClientId

oauthClientId

The client_id of the OAuth 2.0 client profile to use.

setOauthRedirectUri

oauthRedirectUri

The redirect_uri as configured in the OAuth 2.0 client profile.

SetOauthScope

oauthScope

A list of scopes to request when performing an OAuth 2.0 authorization flow.

setOauthThreshold

oauthThreshold

A threshold, in seconds, to refresh an OAuth 2.0 token before the access_token expires (defaults to 30 seconds).

setOauthCache

oauthCache

Time, in seconds, to cache an OAuth 2.0 token in memory (defaults to 0 seconds).

Storage (store)

setOidcStorage

oidcStorage

A custom class for the storage of OpenID Connect-related items, such as access tokens.

SetSsoTokenStorage

ssoTokenStorage

A custom class for the storage of single sign-on-related items, such as SSO tokens.

SetCookiesStorage

cookiesStorage

A custom class for the storage of cookies.

SSL pinning (sslPinning)

setPins

pins

An array of public key certificate hashes (strings) for trusted sites and services.

setBuildSteps

buildSteps

An array of BuildStep objects to provide additional SSL pinning parameters to OkHttpClient instances.

Custom endpoints (urlPath)

setAuthenticateEndpoint

authenticateEndpoint

Override the default path to PingAM’s /json/authenticate endpoint.
Default: /json/realms/{forgerock_realm}/authenticate

setAuthorizeEndpoint

authorizeEndpoint

Override the default path to the PingAM’s /oauth2/authorize endpoint.
Default: /oauth2/realms/{forgerock_realm}/authorize

setTokenEndpoint

tokenEndpoint

Override the default path to PingAM’s /oauth2/access_token endpoint.
Default: /oauth2/realms/{forgerock_realm}/access_token

setRevokeEndpoint

revokeEndpoint

Override the default path to PingAM’s /oauth2/token/revoke endpoint.
Default: /oauth2/realms/{forgerock_realm}/token/revoke

setUserinfoEndpoint

userinfoEndpoint

Override the default path to PingAM’s /oauth2/userinfo endpoint.
Default: /oauth2/realms/{forgerock_realm}/userinfo

setSessionEndpoint

sessionEndpoint

Override the default path to PingAM’s /json/sessions endpoint.

In addition, the Ping SDK for Android lets you configure the log level and a custom logger.

Use the FROptions interface to build an options object and pass the object to the FRAuth.start() method.

The options object provides access to the settings defined by the following properties. These settings are the same as the properties in FRAuthConfig.plist:

Ping SDK for iOS dynamic properties
Domain Property Description Required

Server

forgerock_url

The base URL of the PingAM instance to connect to, including port and deployment path; for example, https://openam.example.com:8443/openam.

forgerock_realm

The realm where the OAuth 2.0 client profile is configured.

Default: root

forgerock_timeout

A timeout, in seconds, for each request that communicates with PingAM (defaults to 30 seconds).

forgerock_enable_cookie

When true, enables cookie use (defaults to true).

forgerock_cookie_name

The name of the cookie that contains the SSO token, for example, iPlanetDirectoryPro.

To locate the cookie name in an PingOne Advanced Identity Cloud tenant, go to Tenant Settings > Global Settings > Server.

Default: iPlanetDirectoryPro

Journeys

forgerock_auth_service_name

The name of the user authentication tree configured in PingAM.

forgerock_registration_service_name

The name of the user registration tree configured in PingAM.

OAuth 2.0

forgerock_oauth_client_id

The client_id of the OAuth 2.0 client profile to use.

forgerock_oauth_redirect_uri

The redirect_uri as configured in the OAuth 2.0 client profile.

forgerock_oauth_scope

A list of scopes to request when performing an OAuth 2.0 authorization flow.

forgerock_oauth_threshold

A threshold, in seconds, to refresh an OAuth 2.0 token before the access_token expires (defaults to 30 seconds).

SSL pinning

forgerock_ssl_pinning_public_key_hashes

An array of public key certificate hashes (strings) for trusted sites and services.

forgerock_keychain_access_group

Keychain access group for the shared keychain.

Custom endpoints

forgerock_authenticate_endpoint

Override the default path to PingAM’s /json/authenticate endpoint.
Default: /json/realms/{forgerock_realm}/authenticate

forgerock_authorize_endpoint

Override the default path to the PingAM’s /oauth2/authorize endpoint.
Default: /oauth2/realms/{forgerock_realm}/authorize

forgerock_token_endpoint

Override the default path to PingAM’s /oauth2/access_token endpoint.
Default: /oauth2/realms/{forgerock_realm}/access_token

forgerock_revoke_endpoint

Override the default path to PingAM’s /oauth2/token/revoke endpoint.
Default: /oauth2/realms/{forgerock_realm}/token/revoke

forgerock_userinfo_endpoint

Override the default path to PingAM’s /oauth2/userinfo endpoint.
Default: /oauth2/realms/{forgerock_realm}/userinfo

forgerock_session_endpoint

Override the default path to PingAM’s /json/sessions endpoint.

You must provide a value for properties that are marked as required.

The SDK throws an exception if you provide an empty string for a required property.

Session and token lifecycle

The SDK revokes and removes persisted tokens if you change any of the following properties dynamically:

  • forgerock_cookie_name

  • forgerock_oauth_client_id

  • forgerock_oauth_redirect_uri

  • forgerock_oauth_scope

  • forgerock_realm

  • forgerock_url

Android example

The following examples use dynamic configuration.

  • Android - Java

  • Android - Kotlin

FROptions options = FROptionsBuilder.build(frOptionsBuilder -> {
    frOptionsBuilder.server(serverBuilder -> {
        serverBuilder.setUrl("https://tenant.forgeblocks.com/am");
        serverBuilder.setRealm("alpha");
        serverBuilder.setCookieName("46b42b4229cd7a3");
        return null;
    });
    frOptionsBuilder.oauth(oAuthBuilder -> {
        oAuthBuilder.setOauthClientId("androidClient");
        oAuthBuilder.setOauthRedirectUri("https://localhost:8443/callback");
        oAuthBuilder.setOauthScope("openid profile email address");
        return null;
    });
    frOptionsBuilder.service(serviceBuilder -> {
        serviceBuilder.setAuthServiceName("Login");
        serviceBuilder.setRegistrationServiceName("Registration");
        return null;
    });
    return null;
});
FRAuth.start(this, options);
val options = FROptionsBuilder.build {
    server {
       url = "https://openam-forgerock-sdks.forgeblocks.com/am"
       realm = "alpha"
       cookieName = "iPlanetDirectoryPro"
    }
    oauth {
       oauthClientId = "sdkPublicClient"
       oauthRedirectUri = "https://localhost:8443/callback"
       oauthScope = "openid profile email address"
    }
    service {
       authServiceName = "Login"
       registrationServiceName = "Registration"
    }
}

FRAuth.start(this, options);

When the application calls FRAuth.start(), the FRAuth class checks for the presence of an FROptions object. If the object is not present, static initialization from strings.xml happens. If the object is present, the FRAuth class uses the options object and calls the same internal initialization method.

The app can call FRAuth.start() multiple times in its lifecycle:

  • When the app calls FRAuth.start() for the first time in its lifecycle, the SDK checks for the presence of session and access tokens in the local storage. If an existing session is present, initialization does not log the user out.

  • If the app calls FRAuth.start() again, the SDK checks whether session managers and token managers are initialized, and cleans the existing session and token storage. This ensures that changes to the app configuration remove and revoke existing sessions and tokens.

iOS example

The following Swift example uses dynamic configuration.

let options = FROptions(url: "https://tenant.forgeblocks.com/am",
                        realm: "alpha",
                        cookieName: "46b42b4229cd7a3",
                        oauthClientId: "iosClient",
                        oauthRedirectUri: "frauth://com.forgerock.ios.frexample",
                        oauthScope: "openid profile email address",
                        authServiceName: "Login",
                        registrationServiceName: "Register")
try FRAuth.start(options: options)

When the application calls FRAuth.start(), the FRAuth class checks for the presence of an FROptions object. If the object is not present, the static initialization from FRAuthConfig.plist happens. If the object is present, the FRAuth class converts it to a [String, Any] dictionary and calls the same internal initialization method.

The app can call FRAuth.start() multiple times in its lifecycle:

  • When the app calls FRAuth.start() for the first time in its lifecycle, the SDK checks for the presence of session and access tokens in the local storage. If an existing session is present, initialization does not log the user out.

  • If the app calls FRAuth.start() again, the SDK checks whether session managers and token managers are initialized, and cleans the existing session and token storage. This ensures that changes to the app configuration remove and revoke existing sessions and tokens.

Using the .well-known endpoint for dynamic configuration

You can configure the Android and iOS SDKs to obtain many required settings from your PingOne server’s .well-known OpenID Connect endpoint. Settings gathered from the endpoint include the paths to use for OAuth 2.0 authorization requests, and login endpoints.

  • Android

  • iOS

Use the FROptions.discover method to use the .well-known endpoint to configure OAuth 2.0 paths:

val option =
    options.discover("https://auth.pingone.com/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/as/.well-known/openid-configuration")

FRAuth.start(context, option)

Use the FROptions.discover method to use the .well-known endpoint to configure OAuth 2.0 paths:

let options = try await FROptions(config: config).discover(
  discoveryURL: "https://auth.pingone.com/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/as/.well-known/openid-configuration")

try FRAuth.start(options: options)

Limitations

  • Apps do not manage tokens from multiple servers, only those of the currently active server.

  • Dynamic configuration is not persistent.

  • Dynamic configuration applies to core configuration, not extensions such as callback overrides, device profile configuration, and request interception.

  • FRUI pre-defined UI elements do not use dynamic configuration.

Enable SSL pinning

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

The Ping SDKs support SSL pinning, sometimes referred to as certificate pinning. SSL pinning is the security practice of validating the certificates presented by the server against known values.

When the SDK attempts to make an HTTPS connection to your authorization server, it first verifies that a hash of the server’s public key (obtained from the server’s SSL certificate) matches a set of hashes defined within your app. This SSL pinning reduces the chance of a man-in-the-middle (MITM) attack, improving the security of your app.

If the hash does not match, your app does not connect to the authorization server, and an error is returned instead. Note that if your public key changes, you will need to rebuild and re-release your app with the new hash included.

Get a hash of the public key from your server

To enable SSL pinning you need a hash of your server’s public key. You can use the openssl tool to extract this from your server’s SSL certificate and create the hash value.

In the following command, replace <tenant-env-fqdn> with the fully-qualified domain name of your server, for example, my-company.forgeblocks.com:

echo | openssl s_client -servername <tenant-env-fqdn> -connect <tenant-env-fqdn>:443 | openssl x509 -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

The command outputs a hash of the public key extracted from the certificate:

S4kZuhQQ1DPcXBSWFQXD0gG+UW7usdbVx6roNWpRl65I=

Use this value in the next steps to configure SSL pinning.

Configure SSL pinning in Android

To enable SSL pinning in the Ping SDK for Android, add the hash of the public keys for any PingAM authorization servers your application will contact to your app’s configuration.

Add the hashes to an array named forgerock_ssl_pinning_public_key_hashes in your strings.xml file:

<string-array name="forgerock_ssl_pinning_public_key_hashes">
   <item>S4kZuhQQ1DPcXBSWFQXD0gG+UW7usdbVx6roNWpRl65I=</item>
</string-array>

If the public key you use to obtain SSL certificates for the PingAM servers change, update the strings.xml file with the new hash and re-release your app.

You can also update this property programmatically by using dynamic configuration.

Override default implementation of SSL pinning for Android

You can override how the Ping SDK for Android performs SSL pinning by registering your own implementation.

To override the default SSL pinning, you create your own implementation of checkServerTrusted():

try {
    final TrustManager myCustomTrustManager = new X509TrustManager() {
        @Override
        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {}

        @Override
        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
            // Provide custom SSL Pinning handling
        }

        @Override
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return new java.security.cert.X509Certificate[] {};
        }
    };
    SSLContext sslContext = SSLContext.getInstance("SSL");
    sslContext.init(null, new TrustManager[] { myCustomTrustManager }, new java.security.SecureRandom());
    Config.getInstance().reset();
    Config.getInstance().init(this, null);
    Config.getInstance().setBuildSteps(Collections.singletonList(builder1 -> {
        builder1.sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) myCustomTrustManager);
        builder1.hostnameVerifier((s, sslSession) -> true);
    }));

} catch (NoSuchAlgorithmException | KeyManagementException e) {
    runOnUiThread(() -> content.setText(e.getMessage()));
}

Alternatively, you can use dynamic configuration to override the SDK’s SSL pinning functionality:

val myCustomTrustManager: TrustManager = object : X509TrustManager {
    override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
    override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
        // Provide custom SSL Pinning handling
    }
    override fun getAcceptedIssuers(): Array<X509Certificate> {
        return arrayOf()
    }
}
val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, arrayOf(myCustomTrustManager), SecureRandom())

val option = FROptionsBuilder.build {
    server {
        forgerock_url = "https://custom.example.com"
        forgerock_realm = "prod"
    }
    sslPinning {
        buildSteps = listOf(object: BuildStep<OkHttpClient.Builder> {
            override fun build(builder1: OkHttpClient.Builder) {
                builder1.sslSocketFactory(
                    sslContext.socketFactory,
                    myCustomTrustManager as X509TrustManager
                )
                builder1.hostnameVerifier { s, sslSession -> true }
            }
        })
        forgerock_ssl_pinning_public_key_hashes = emptyList()
    }
}

Configure SSL pinning in iOS

To enable SSL pinning in the Ping SDK for iOS, add the hash of the public keys for any PingAM authorization servers your application will contact to your app’s configuration.

Add the hashes to an array named forgerock_ssl_pinning_public_key_hashes in your FRAuthConfig.plist file:

<key>forgerock_ssl_pinning_public_key_hashes</key>
<array>
    <string>S4kZuhQQ1DPcXBSWFQXD0gG+UW7usdbVx6roNWpRl65I=</string>
</array>

If the public key you use to obtain SSL certificates for the PingAM servers change, update the FRAuthConfig.plist file with the new hash and re-release your app.

You can also update this property programmatically by using dynamic configuration.

Override default implementation of SSL pinning for iOS

You can override how the Ping SDK for iOS performs SSL pinning by registering your own implementation.

To override the default SSL pinning, create a new CustomPinningHandler subclass of the default FRURLSessionSSLPinningHandler class. Override the implementation of the urlSession functions:

class CustomPinningHandler: FRURLSessionSSLPinningHandler {
    override func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        // Provide Custom SSL Pinning handling
    }

    override func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        // Provide Custom SSL Pinning handling
    }
}

Add your new custom handler as part of the configuration:

let customPinningHandler = CustomPinningHandler(frSecurityConfiguration: nil)
RestClient.shared.setURLSessionConfiguration(config: nil, handler: customPinningHandler)

Customize the Ping SDKs

You can customize the Ping SDKs to suit your exact requirements.

For information on how to customize the SDK, refer to the following:


Intercept and modify REST calls

The Ping SDKs support modification of REST API calls before they are sent.

For example, you can customize:

  • Request parameters

  • Headers

  • Request URLs

  • Request methods

  • The request body and post data


Use and customize loggers

You can customize how the Ping SDKs output debug logs.

For example, you could direct them to a third-party logging service, or prefix each message before outputting them to the console.


Customize storage

There are use cases where you might need to customize how the SDKs store data

For example, you might be running on hardware that provides specialized security features.

For these cases, the SDKs allow you to provide your own storage classes.

Intercept and modify REST calls

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

The Ping SDKs support modification of REST calls before they are sent.

For example, you can customize:

  • Request parameters

  • Headers

  • Cookies

  • Request URLs

  • Request methods

  • The request body and post data

Request interceptors

Each SDK provides an interface that you can use to customize requests:

  • Android

  • iOS

  • JavaScript

public interface FRRequestInterceptor<Action> {
    @NonNull Request intercept(Request request, Action action);
}

public class Action {
    private String type;
    private JSONObject payload;
}
public protocol RequestInterceptor {
    func intercept(request: Request, action: Action) -> Request
}

public struct Action {
    public let type: String
    public let payload: [String: Any]?
}
type RequestMiddleware = (req: RequestObj, action: Action, next: () => RequestObj) => void;

interface RequestObj {
  url: URL;
  init: RequestInit;
}

interface Action {
  type: string;
  payload?: any; // optional data
}

Request interceptors have two inputs:

The Request object

Represents the original request, and has information about the body, method type, parameters, and more.

  • Android

  • iOS

  • JavaScript

public class Request {
    public URL url();
    public Iterator<Pair<String, String>> headers();
    public String header(String name);
    public List<String> headers(String name);
    public String method();
    public Object tag();
    public Body body();
    public Builder newBuilder();
}

// Use Build to build upon on existing Request
public class Builder {
   public Builder url(URL url);
   public Builder url(String url);
   public Builder header(String name, String value);
   public Builder addHeader(String name, String value);
   public Builder removeHeader(String name);
   public Builder get();
   public Builder put(Body body);
   public Builder post(Body body);
   public Builder delete(Body body);
   public Builder delete();
   public Builder patch(Body body);
   public Request build();

}
public struct Request {
    // Properties
    public let url: String
    public let method: HTTPMethod
    private(set) public var headers: [String: String]
    public let bodyParams: [String: Any]
    public let urlParams: [String: String]
    public let responseType: ContentType
    public let requestType: ContentType
    public let timeoutInterval: Double

    public enum ContentType: String {
        case plainText = "text/plain"
        case json = "application/json"
        case urlEncoded = "application/x-www-form-urlencoded"
    }

    public enum HTTPMethod: String {
        case GET = "GET"
        case PUT = "PUT"
        case POST = "POST"
        case DELETE = "DELETE"
    }

    public func build() -> URLRequest?
}

Refer to the native JavaScript Request object in the MDN Web Docs.

The Action object

Represents the type of operation the request performs:

Action Description

START_AUTHENTICATE

Initial call to an authentication tree

AUTHENTICATE

Proceed through an authentication tree flow

AUTHORIZE

Obtain authorization token from PingAM

EXCHANGE_TOKEN

Exchange authorization code for an access token

REFRESH_TOKEN

Refresh an access token

REVOKE_TOKEN

Revoke a refresh or access token

LOGOUT

Log out a session

USER_INFO

Obtain information from the userinfo endpoint

PUSH_REGISTER

Register a push device with PingAM; for example, a call to /json/push/sns/message?_action=register

PUSH_AUTHENTICATE

Authenticate using push; for example, a call to /json/push/sns/message?_action=authenticate

The AUTHENTICATE and START_AUTHENTICATE actions have a payload that contains:

tree

The name of the authentication tree being called.

type

Whether the call is to a service, or is in response to composite_advice.

The outcome of applying a request interceptor is the entire modified request object, ready to either be sent to PingAM, or to have additional request interceptors applied.

Examples

This section covers how to develop request interceptors, referred to as "middleware" in the Ping SDK for JavaScript, and apply them to outbound requests from your applications.

Ping SDK for Android

Query parameters and headers

The example sets the ForceAuth query parameter to true, and adds an Accept-Language header with a value of en-GB on all outgoing requests of the START_AUTHENTICATE type:

public class QueryParamsAndHeaderRequestInterceptor implements FRRequestInterceptor<Action> {
    @NonNull
    @Override
    public Request intercept(@NonNull Request request, Action tag) {
        if (tag.getType().equals(START_AUTHENTICATE)) {
            return request.newBuilder()
                    // Add query parameter:
                    .url(Uri.parse(request.url().toString())
                            .buildUpon()
                            .appendQueryParameter("ForceAuth", "true").toString())

                    // Add additional header:
                    .addHeader("Accept-Language", "en-GB")

                    // Construct the updated request:
                    .build();
        }
        return request;
    }
}

To register the request interceptor, use the RequestInterceptorRegistry.getInstance().register() method:

RequestInterceptorRegistry.getInstance().register(new QueryParamsAndHeaderRequestInterceptor())

Any calls the app makes to initiate authentication now have the query parameter ForceAuth=true appended, and include an accept-language: en-GB header added.

Cookies

The example adds a custom cookie to outgoing requests:

public class CustomCookieInterceptor implements FRRequestInterceptor<Action>, CookieInterceptor {
    @NonNull
    @Override
    public Request intercept(@NonNull Request request) {
       return request;
    }

    @NonNull
    @Override
    public Request intercept(@NonNull Request request, Action tag) {
        return request;
    }

    @NonNull
    @Override
    public List<Cookie> intercept(@NonNull List<Cookie> cookies) {
        List<Cookie> newCookies = new ArrayList<>();
        newCookies.addAll(cookies);
        newCookies.add(
          new Cookie.Builder()
            .domain("example.com")
            .name("member").value("gold")
            .httpOnly().secure().build()
        );

        return newCookies;
    }
}

You can register multiple request interceptors as follows:

RequestInterceptorRegistry.getInstance().register(
    new QueryParamsAndHeaderRequestInterceptor(),
    new CustomCookieInterceptor()
);

Ping SDK for iOS

Query parameters and headers

The example sets the ForceAuth query parameter to true, and adds an Accept-Language header with a value of en-GB on all outgoing requests of the AUTHENTICATE or START_AUTHENTICATE type:

class QueryParamsAndHeaderRequestInterceptor: RequestInterceptor {
    func intercept(request: Request, action: Action) -> Request {
        if action.type == "START_AUTHENTICATE" || action.type == "AUTHENTICATE" {
            // Add query parameter:
            var urlParams = request.urlParams
            urlParams["ForceAuth"] = "true"

            // Add additional header:
            var headers = request.headers
            headers["Accept-Language"] = "en-GB"

            // Construct the updated request:
            let newRequest = Request(
                url: request.url,
                method: request.method,
                headers: headers,
                bodyParams: request.bodyParams,
                urlParams: urlParams,
                requestType: request.requestType,
                responseType: request.responseType,
                timeoutInterval: request.timeoutInterval
            )
            return newRequest
        }
        else {
            return request
        }
    }
}

To register the request interceptor, use the registerInterceptors() method:

FRRequestInterceptorRegistry.shared.registerInterceptors(
    interceptors: [
        QueryParamsAndHeaderRequestInterceptor()
    ]
)

Any calls the app makes to initiate authentication now have the query parameter ForceAuth=true appended, and include an accept-language: en-GB header added.

Cookies

The example adds a custom cookie to outgoing requests:

class CookieInterceptor: RequestInterceptor {
    func intercept(request: Request, action: Action) -> Request {
        if action.type == "START_AUTHENTICATE" || action.type == "AUTHENTICATE" {

            var headers = request.headers
            headers["Cookie"] = "member=gold; level=2"

            let newRequest = Request(
                url: request.url,
                method: request.method,
                headers: headers,
                bodyParams: request.bodyParams,
                urlParams: request.urlParams,
                requestType: request.requestType,
                responseType: request.responseType,
                timeoutInterval: request.timeoutInterval)
            return newRequest
        }
        else {
            return request
        }
    }
}

You can register multiple request interceptors as follows:

FRRequestInterceptorRegistry.shared.registerInterceptor(
    interceptors: [
        QueryParamsAndHeaderRequestInterceptor(),
        CookieInterceptor()
    ]
)

Ping SDK for JavaScript

The example has two middleware configurations. One sets the ForceAuth query parameter to true, the other adds an Accept-Language header with a value of en-GB on all outgoing requests of the START_AUTHENTICATE type:

const forceAuthMiddleware = (
    req: RequestObj,
    action: Action,
    next: () => RequestObj
): void => {
    switch (action.type) {
        case 'START_AUTHENTICATE':
            req.url.searchParams.set('ForceAuth', 'true');
            break;
    }
    next();
};

const addHeadersMiddleware = (
    req: RequestObj,
    action: Action,
    next: () => RequestObj
): void => {
    switch (action.type) {
        case 'START_AUTHENTICATE':
            const headers = req.init.headers as Headers;
            headers.append('Accept-Language', 'en-GB');
            break;
    }
    next();
};

Apply the middleware in the config:

Config.set({
    clientId: 'sdkPublicClient',
    middleware: [
        forceAuthMiddleware,
        addHeadersMiddleware
    ],
    redirectUri: 'https://localhost:8443/callback.html',
    realmPath: 'alpha',
    scope: 'openid profile email address',
    serverConfig: {
        baseUrl: 'https://openam-forgerock-sdks.forgeblocks.com/am',
        timeout: 30000
    },
    tree: 'UsernamePassword'
});

Any calls the app makes to start authentication now have the query parameter and header added.

You can only modify headers in certain types of request.

For example START_AUTHENTICATE and AUTHENTICATE types, but not AUTHORIZE types as they occur in an iframe.

Use and customize loggers

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

About the default Ping SDK for Android logger

The Ping SDK for Android does all of its logging through a custom interface called FRLogger. The default implementation of this interface logs the messages through the native Android Log class. This displays messages from the SDK in real-time in the Logcat window in Android Studio.

The log severity levels defined in the Ping SDK for Android are as follows:

Log level Description

DEBUG

Show debug log messages intended only for development, as well as the message levels lower in this list; INFO, WARN, and ERROR.

In addition, all network activities of the SDK are included in the logs.

INFO

Show expected log messages for regular usage, as well as the message levels lower in this list, WARN, and ERROR.

WARN

Show possible issues that are not yet errors, as well as the messages of ERROR log level.

ERROR

Show issues that caused errors.

NONE

No log messages are shown.

The log levels are cumulative.

If you select a lower severity level, all messages logged at higher severity levels are also included. For example, if you select the DEBUG level, the log includes all events logged at the DEBUG, INFO, WARN, and ERROR levels.

By default, the log level of the Ping SDK for Android is set to Logger.Level.WARN.

Customize the Ping SDK for Android logger

The Ping SDK for Android allows developers to customize the default logger behavior:

  1. Create a class that implements the FRLogger interface:

    import androidx.annotation.Nullable;
    import org.forgerock.android.auth.FRLogger;
    
    public class MyCustomLogger implements FRLogger {
        @Override
        public void error(@Nullable String tag, @Nullable Throwable t, @Nullable String message, @Nullable Object... values) {
            /// Custom error message handling...
        }
    
        @Override
        public void error(@Nullable String tag, @Nullable String message, @Nullable Object... values) {
            /// Custom error message handling...
        }
    
        @Override
        public void warn(@Nullable String tag, @Nullable String message, @Nullable Object... values) {
            /// Custom warning message handling...
        }
    
        @Override
        public void warn(@Nullable String tag, @Nullable Throwable t, @Nullable String message, @Nullable Object... values) {
            /// Custom warning message handling...
        }
    
        @Override
        public void debug(@Nullable String tag, @Nullable String message, @Nullable Object... values) {
            /// Custom debug message handling...
        }
    
        @Override
        public void info(@Nullable String tag, @Nullable String message, @Nullable Object... values) {
            /// Custom info message handling...
        }
    
        @Override
        public void network(@Nullable String tag, @Nullable String message, @Nullable Object... values) {
            /// Custom network details handling...
        }
    
        @Override
        public boolean isNetworkEnabled() {
            return true; // include network call details in the logs
        }
    }
  2. In your application, set the custom logger and desired log level:

    Logger.setCustomLogger(new MyCustomLogger()); // The default logger will no longer be active
    Logger.set(Logger.Level.DEBUG);
  3. You can now use the Logger interface in your app.

    For example:

    String TAG = MainActivity.class.getSimpleName();
    Logger.debug (TAG, "Happy logging!");

About the default Ping SDK for iOS logger

The Ping SDK for iOS does all of its logging through a custom protocol called FRLogger. The default implementation of the FRLogger protocol logs the messages through the native iOS FRConsoleLogger class. This displays messages from the SDK in real-time in the console window in Xcode.

Each log message has an associated log level that describes the type and the severity of the message. Log levels are helpful tool for tracking and analyzing events that take place in your app.

The log severity levels defined in the Ping SDK for iOS are as follows:

Log level Description

none

Prevent logging

verbose

Logs that are not important or can be ignored

info

Logs that maybe helpful or meaningful for debugging, or understanding the flow

network

Logs for network traffic, including request and response

warning

Logs that are a minor issue or an error that can be ignored

error

Logs that are a severe issue or a major error that impacts the SDK’s functionality or flow

all

Logs at all levels

The log levels are not cumulative. That is, you should explicitly specify all the log levels you want to record.

For example, if you select the debug level, the output only includes events logged at debug level.

To include other levels, you must specify an array of the required log levels.

By default, the log level of the Ping SDK for iOS is set to LogLevel.none.

Customize the Ping SDK for iOS logger

The Ping SDK for iOS lets developers customize the default logger behavior:

  1. Create a class that conforms to the FRLogger protocol:

    class MyCustomLogger: FRLogger {
        func logVerbose(timePrefix: String, logPrefix: String, message: String) {
            /// Custom verbose message handling...
        }
    
        func logInfo(timePrefix: String, logPrefix: String, message: String) {
            /// Custom info message handling...
        }
    
        func logNetwork(timePrefix: String, logPrefix: String, message: String) {
            /// Custom network message handling...
        }
    
        func logWarning(timePrefix: String, logPrefix: String, message: String) {
            /// Custom warning message handling...
        }
    
        func logError(timePrefix: String, logPrefix: String, message: String) {
            /// Custom error message handling...
        }
    }
  2. In your application, set the custom logger and desired log level:

    FRLog.setCustomLogger(MyCustomLogger()) // The default logger will no longer be active
    FRLog.setLogLevel([.all])
  3. You can now use the FRLog class in your app.

    For example:

    FRLog.v("Happy logging!")

About the default Ping SDK for JavaScript logger

The Ping SDK for JavaScript performs logging through the native console class. This displays messages from the SDK in real-time in the console window provided in many browsers.

The default logLevel is none, which prevents the Ping SDK for JavaScript from logging any messages to the console.

To enable the output of log messages from the Ping SDK for JavaScript, specify a logLevel value other than none.

For example, use the following code to specify the debug level:

Setting the log level in the Ping SDK for JavaScript configuration
Config.set({
  serverConfig: {
    baseUrl: 'https://openam-forgerock-sdks.forgeblocks.com/am/',
    timeout: 5000,
  },
  logLevel: 'debug',
});

The log severity levels defined in the Ping SDK for JavaScript are as follows:

Log level Description

debug

Show debug log messages intended only for development, as well as the message levels lower in this list; info, warn, and error.

In addition, all network activities of the SDK are included in the logs.

info

Show expected log messages for regular usage, as well as the message levels lower in this list, warn, and error.

warn

Show possible issues that are not yet errors, as well as the messages of error log level.

error

Show issues that caused errors.

none

No log messages are shown. This is the default setting.

The log levels are cumulative. If you select a lower severity level, all messages logged at higher severity levels are also included.

For example, if you select the debug level, the output includes all events logged by the SDK at debug, info, warn, and error levels.

For more information on configuring the Ping SDK for JavaScript, refer to Ping SDK for JavaScript Properties

Customize the Ping SDK for JavaScript logger

The Ping SDK for JavaScript allows developers to customize the default logger behavior. For example, you might want to redirect the logs to an external service.

  1. Create a function that implements the LoggerFunctions interface.

    For example, the following code adds a prefix to each log message from the SDK and logs it to the console:

    const customLogger = {
      warn: (msg) => console.warn(`[FR SDK] ${msg}`),
      error: (msg) => console.error(`[FR SDK] ${msg}`),
      log: (msg) => console.log(`[FR SDK] ${msg}`),
      info: (msg) => console.info(`[FR SDK] ${msg}`),
    };

    The signature of the interface defaults to the following:

    (…​msgs: unknown[]) ⇒ void

    You can pass your own type definition into the Generic if required. For example:

    // typescript generic example
    type YourAsyncLoggerType = LoggerFunctions<
      (...msgs: unknown[]) => Promise<void>,
      (...msgs: unknown[]) => Promise<void>,
      (...msgs: unknown[]) => Promise<void>,
      (...msgs: unknown[]) => Promise<void>
      >
    
    const customLoggerWithApiCall: YourAsyncLoggerType = {
      warn: (msg) => yourAsyncLogFunction.warn(`[FR SDK] ${msg}`),
      error: (msg) => yourAsyncLogFunction.error(`[FR SDK] ${msg}`),
      log: (msg) => yourAsyncLogFunction.log(`[FR SDK] ${msg}`),
      info: (msg) => yourAsyncLogFunction.info(`[FR SDK] ${msg}`),
    };
  2. In the SDK configuration of your app, specify the custom logger and required log level:

    Config.set({
        serverConfig: {
            baseUrl: 'https://openam-forgerock-sdks.forgeblocks.com/am/',
            timeout: '5000'
        },
        logLevel: 'error',
        logger: customLogger,
    });

    The SDK redirects its logging output to your custom handler.

Customize storage

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

Depending on the authentication use case, the SDKs may need to store and retrieve session cookies, ID tokens, access tokens, and refresh tokens.

Each token is serving a different use case, and as such how the SDKs handle them can be different.

The SDKs employ identity best practices for storing data by default. To learn more about how the SDKs store different data, refer to Token and key security and Data security.

There are use cases where you might need to customize how to store data. For example, you might be running on hardware that provides specialized security features, or perhaps target older hardware that cannot handle the latest algorithms.

For these cases, you can provide your own storage classes.

Customize storage on Android

You can configure your Android apps to use customized storage for these types of data:

  1. OAuth 2.0 / OpenID Connect 1.0 tokens

  2. SSO data

  3. Cookies

Configure storage overrides

Add a store key to the FROptionsBuilder.build parameters to specify which storage types to override, and the class that provides the implementation:

val options = FROptionsBuilder.build {
    server {
       url = "https://openam-forgerock-sdks.forgeblocks.com/am"
       realm = "alpha"
       cookieName = "iPlanetDirectoryPro"
    }
    oauth {
       oauthClientId = "sdkPublicClient"
       oauthRedirectUri = "https://localhost:8443/callback"
       oauthScope = "openid profile email address"
    }
    service {
       authServiceName = "Login"
       registrationServiceName = "Registration"
    }
    store {
      // Default storage settings
      // Uses SecureSharedPreferences
      //   oidcStorage = TokenStorage(ContextProvider.context)
      //   ssoTokenStorage = SSOTokenStorage(ContextProvider.context)
      //   cookiesStorage = CookiesStorage(ContextProvider.context)

      oidcStorage = MyCustomTokenStorage(ContextProvider.context)
      ssoTokenStorage = MyCustomSSOTokenStorage(ContextProvider.context)
      cookiesStorage = MyCustomCookiesStorage(ContextProvider.context)
    }
}

FRAuth.start(this, options);

You can only specify the store options when dynamically configuring the Ping SDK for Android.

Implement storage override classes

Use the Storage interface to override the different types of storage as follows

OpenID Connect storage

Storage<AccessToken>

SSO token storage

Storage<SSOToken>

Cookie storage

Storage<Collection<String>>

You must implement the following functions in each storage class:

save()

Stores an item in the customized storage.

get()

Retrieves an item from the customized storage.

delete()

Removes an item from the customized storage.

Examples:

  • OpenID Connect storage

  • SSO token storage

class MyCustomTokenStorage(context: Context) : Storage<AccessToken> {

    override fun save(item: AccessToken) {
        TODO("Implement save to storage functionality")
    }

    override fun get(): AccessToken? {
        TODO("Implement retrieve to storage functionality")
    }

    override fun delete() {
        TODO("Implement remove from storage functionality")
    }
}
class MyCustomSSOTokenStorage(context: Context) : Storage<SSOToken> {

    override fun save(item: SSOToken) {
        TODO("Implement save to storage functionality")
    }

    override fun get(): SSOToken? {
        TODO("Implement retrieve to storage functionality")
    }

    override fun delete() {
        TODO("Implement remove from storage functionality")
    }

}

The SDK includes a basic example of a customized storage class that places data temporarily in memory. Refer to MemoryStorage.kt in the forgerock-android-sdk GitHub repo.

Apps you release that use customized storage will not be able to access existing data that was stored using a different method.

This may mean your users will have to log in again after upgrading to an app that is using a different storage mechanism.

To prevent having to log in again your custom storage could manually migrate any existing data to the new storage during initialization.

For an example of migrating existing stored data, see SSOTokenStorage.kt

Implement storage fallbacks

One use case for providing custom storage is when the device you are targeting might not support the default SecureSharedPreferences storage methods provided by the SDK.

In this case you can create a fallback mechanism such that if the default storage method produces an error, a second storage method attempts to save the data.

The following CustomStorageWithFallback.kt example file is available in the forgerock-android-sdk GitHub repo.

package com.example.app.storage

import android.content.Context
import kotlinx.serialization.Serializable
import org.forgerock.android.auth.AccessToken
import org.forgerock.android.auth.SSOToken
import org.forgerock.android.auth.storage.CookiesStorage
import org.forgerock.android.auth.storage.SSOTokenStorage
import org.forgerock.android.auth.storage.Storage
import org.forgerock.android.auth.storage.TokenStorage

/
 * A custom storage implementation that switches to a fallback storage when an error occurs.
 */
class CustomStorageWithFallback<T : @Serializable Any>(
    private val context: Context,
    private val flag: String, (1)
    primary: Storage<T>, (2)
    private val fallback: Storage<T> (3)
) : Storage<T> {

    @Volatile
    private var current: Storage<T> = primary (4)

    /
     * Save an item to the current storage. If an error occurs, switch to the fallback storage.
     *
     * @param item The item to be saved.
     */
    override fun save(item: T) {
        try {
            // Save the item to the current storage.
            current.save(item) (5)
        } catch (e: Throwable) {
            // If an error occurs, switch to the fallback storage.
            context.getSharedPreferences("storage-control", Context.MODE_PRIVATE).edit()
                .putInt(flag, 1).apply() (6)
            fallback.save(item) (7)
            current = fallback
        }
    }

    /
     * Retrieve an item from the current storage.
     *
     * @return The retrieved item, or null if no item is found.
     */
    override fun get(): T? {
        return current.get()
    }

    /
     * Delete an item from the current storage.
     */
    override fun delete() {
        current.delete()
    }
}

/
 * Load the SSO token storage with a fallback mechanism.
 *
 * @param context The application context.
 * @return The storage instance for SSO tokens.
 */
fun loadSSOTokenStorage(context: Context): Storage<SSOToken> {  (8)
    return loadStorage(
        context,
        "ssoStorage",
        { SSOTokenStorage(context) },
        { MemoryStorage() }
    )
}

/
 * Load the token storage with a fallback mechanism.
 *
 * @param context The application context.
 * @return The storage instance for tokens.
 */
fun loadTokenStorage(context: Context): Storage<AccessToken> { (9)
    return loadStorage(
        context,
        "tokenStorage",
        { TokenStorage(context) },
        { MemoryStorage() }
    )
}

/
 * Load the cookies storage with a fallback mechanism.
 *
 * @param context The application context.
 * @return The storage instance for cookies.
 */
fun loadCookiesStorage(context: Context): Storage<Collection<String>> { (10)
    return loadStorage(
        context,
        "cookiesStorage",
        { CookiesStorage(context) },
        { MemoryStorage() }
    )
}

/
 * Load a storage instance with a fallback mechanism.
 *
 * @param T The type of object to be stored.
 * @param context The application context.
 * @param flag A flag used to control the storage type.
 * @param primary A function to initialize the primary storage.
 * @param fallback A function to initialize the fallback storage.
 * @return The storage instance.
 */
inline fun <reified T : Any> loadStorage( (11)
    context: Context,
    flag: String,
    primary: () → Storage<T>,
    fallback: () → Storage<T>
): Storage<T> {
    val control = context.getSharedPreferences("storage-control", Context.MODE_PRIVATE)
    // Get the storage type from the control flag. 0: primary, 1: fallback.
    val storageType = control.getInt(flag, 0)
    return when (storageType) {
        // Use the primary storage.
        0 → CustomStorageWithFallback(context,
            flag,
            primary(),
            fallback())

        // Use the fallback storage.
        else → fallback()
    }
}
1 Flag whether the code should use the primary storage mechanism, or the fallback
2 The class to use as the primary storage mechanism
3 The class to use as the fallback storage mechanism
4 Initially, set the primary mechanism as current
5 Attempt to save with the current mechanism
6 If it fails, set flag to 1
7 Attempt to save with the fallback mechanism
8 Create an SSO token wrapper function to load the primary and fallback mechanisms
9 Create an OIDC token wrapper function to load the primary and fallback mechanisms
10 Create a Cookie wrapper function to load the primary and fallback mechanisms
11 Create a function to load the customized storage wrappers

Configure your SDK application as follows to use the customized storage with fallback functionality:

store {
    oidcStorage = loadTokenStorage(ContextProvider.context)
    ssoTokenStorage = loadSSOTokenStorage(ContextProvider.context)
    cookiesStorage = loadCookiesStorage(ContextProvider.context)
}

Ping SDK tutorials

Android tutorials

To complete these tutorials successfully, you should perform the following prerequisite tasks.

Prerequisites

Before starting the tutorials, complete these prerequisite tasks.

Configure your server

Select your server environment below for instructions on creating the necessary configuration to successfully complete these tutorials:

Tutorial steps

There are three tutorials available for Android, depending on your server environment:

  • Advanced Identity Cloud/PingAM

  • PingOne

  • PingFederate

This tutorial guides you through creating a Ping SDK-enabled Android app from beginning to end.

It uses embedded login to implement user authentication journeys, meaning you get to design and implement the user interface to your requirements.

Step 1. Configure the development environment

In this step, you set up your environment to create Android applications using the freely-available Android Studio IDE.

You then create a new application project and configure it to use the Ping SDK for Android.

Step 2. Configure connection strings

In this step, you provide your application with the settings it needs to connect to your PingOne Advanced Identity Cloud or PingAM instance.

For example, which authentication tree to use, and the realm it is a part of.

Step 3. Initialize the SDK

In this step, you enable debug logging during development.

You then and add a call to the FRAuth.start() method, which initializes the SDK and loads the configuration you have defined in the previous step.

Step 4. Create a status view

In this step, you create a layout and add buttons to log in and log out your user, as well as a text view field to show their current authentication status.

You also add the code to update the value displayed in the text view.

Step 5. Add login and logout calls

In this step, you update the app with the NodeListener interface, which manages the client side of the authentication journey.

Step 6. Create UI to handle the callbacks

In this step, you add a UI fragment to obtain credentials from the user, and code to open that fragment when the callback is received.

You also add code to populate the callback with the credentials and return it to the server, completing the authentication journey.

Step 7. Test the app

In this step, you will test your application.

You run it in the emulator or on your Android device, perform authentication with a demo user, check the log for success messages, and then log out the user.

Configure a sample Android app to launch a browser to authenticate your users in PingOne, using centralized login.

After authenticating, PingOne redirects users back to your sample client application, that can then get OAuth 2.0 tokens and user info for the user.

Step 1. Download the samples

In this step, you download the Ping SDK sample apps repo, which contains the projects you will use.

Step 2. Configure the sample app

In this step, you configure the "app" sample to connect to the OAuth 2.0 application you created in PingOne, using the centralized login method.

Step 3. Run the sample app and perform centralized login

In this final step, you run the sample app that you configured in the previous step.

The app performs a centralized login on your PingOne instance.

Configure a sample Android app to launch a browser to authenticate your users in PingFederate, using centralized login.

After authenticating, PingFederate redirects users back to your sample client application, that can then get OAuth 2.0 tokens and user info for the user.

Step 1. Download the samples

In this step, you download the Ping SDK sample apps repo, which contains the projects you will use.

Step 2. Configure the sample app

In this step, you configure the "app" sample to connect to the OAuth 2.0 application you created in PingFederate, using the centralized login method.

Step 3. Run the sample app and perform centralized login

In this final step, you run the sample app that you configured in the previous step.

The app performs a centralized login to your PingFederate server.

Advanced Identity Cloud/PingAM

Step 1. Configure the development environment

In this step, you set up your environment to create Android applications using the freely-available Android Studio IDE.

You then create a new application project and configure it to use the Ping SDK for Android.

Prerequisites

Android Studio

Download and install Android Studio, which is available for many popular operating systems.

An Android emulator or physical device

To try the quick start application as you develop it, you need an Android device. To add a virtual, emulated Android device to Android Studio, refer to Create and manage virtual devices, on the Android Developers website.

Create a new project

  1. In Android Studio, select File > New > New Project.

  2. On the New Project screen, select Empty Views Activity, and then click Next.

  3. On the next screen:

    • In the Name field, enter Ping SDK for Android Quick Start.

    • In the Package name field, enter com.example.quickstart.

    • In the Save location field, enter the location in which to create the project.

    • In the Language drop-down, select Java.

    • In the Minimum SDK drop-down, select API 23: Android 6.0 (Marshmallow).

    • Click Finish.

      Android Studio creates a simple application that you can now configure to use the Ping SDK for Android.

Configure compile options

The Ping SDK for Android requires at least Java 8 (v1.8).

Configure compile options in your project to use this version of Java, or later:

  1. In the Android view of your project, right-click app, and then click Open module settings.

  2. In the Project Structure dialog, navigate to Modules > app > Properties.

  3. In the Source Compatibility and Target compatibility drop-downs, select the version of Java to use for the project:

    Selecting the Java version for a project in Android Studio
    Figure 11. Selecting the Java version for a project in Android Studio
  4. Click OK.

Add build dependencies

To use the Ping SDK for Android, add the relevant dependencies to your project:

  1. In the Project tree view of your Android Studio project, open the Gradle Scripts/build.gradle file for the module.

  2. In the dependencies section, add the following:

    implementation 'org.forgerock:forgerock-auth:4.6.0'
    Example of the dependencies section after editing:
    dependencies {
        implementation 'org.forgerock:forgerock-auth:4.6.0'
        ...
        implementation 'androidx.appcompat:appcompat:1.6.1'
        implementation 'com.google.android.material:material:1.8.0'
        implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    }

(Optional) Enable optional clear traffic and location support

If you are not using the PingOne Advanced Identity Cloud but rather a local PingAM server that does not use the HTTPS protocol, you can edit your project manifest file to allow cleartext connections.

You should only configure this property during development against a local PingAM server.

Do not configure this property in your production applications.

  1. Open the project manifest file.

    For example, app > manifests > AndroidManifest.xml.

  2. Add an android:usesCleartextTraffic="true" attribute to the <application> element.

(Optional) Enable location permissions

If you intend for your application to use any of the Android location services; for example, the SDK’s location matching or geofencing features, add one of the relevant properties to the project manifest file

  1. Open the project’s manifest file.

    For example, app > manifests > AndroidManifest.xml.

  2. Add the relevant properties as a child of the <manifest> element:

    1. Coarse location access

        <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    2. Fine location access (requires both permissions)

        <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

      For information about which permission to use, see Location permissions in the Google Developer Documentation.

Example completed manifest file

The following shows an example AndroidManifest.xml file with support for cleartext traffic and fine location access enabled:

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.ForgeRockSDKForAndroidQuickStart"
        tools:targetApi="31"
        android:usesCleartextTraffic="true">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <meta-data
                android:name="android.app.lib_name"
                android:value="" />
        </activity>
    </application>

    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

</manifest>

Check point

In Android Studio, select Run > Run 'app'.

Android Studio builds the application and runs it in the default emulator.

As you have not yet added any UI, the app displays only "Hello World!".

Hello World!

You have now configured your Android app development environment, created a new project, and configured it with the required dependencies and build options.

In the next step, you configure your application with the settings it needs to connect to your PingOne Advanced Identity Cloud or PingAM instance.

Step 2. Configure connection strings

In this step, you provide your application with the settings it needs to connect to your PingOne Advanced Identity Cloud or PingAM instance.

For example, which authentication tree to use and the realm it is a part of.

For this quick start guide, you must provide at least the following properties:

Property Description

forgerock_oauth_client_id

The client_id of the OAuth 2.0 client profile to use.

forgerock_oauth_redirect_uri

The redirect_uri as configured in the OAuth 2.0 client profile.

This value must match a value configured in your OAuth 2.0 client, but is not actually used by the Android application.

forgerock_oauth_scope

A list of scopes to request when performing an OAuth 2.0 authorization flow.

forgerock_url

The URL of the PingOne Advanced Identity Cloud or PingAM instance.

For example, https://openam-forgerock-sdks.forgeblocks.com/am

If you are not using PingOne Advanced Identity Cloud, specify the port and deployment path.

For example, https://openam.example.com:8443/openam.

forgerock_realm

The realm in which the OAuth 2.0 client profile is configured.

For example, alpha

If you are not using PingOne Advanced Identity Cloud, specify the default PingAM the top-level realm; root.

forgerock_auth_service

The name of the journey to use for authentication.

For example, sdkUsernamePasswordJourney

forgerock_cookie_name

The name of the cookie that contains the session token. To obtain the name of the cookie in the PingOne Advanced Identity Cloud:

  1. Click your user in the top-right corner and select Tenant settings.

  2. On the Global Settings tab, copy the value of the Cookie property.

The value is a random string of characters, such as 29cd7a346b42b42.

If you are not using PingOne Advanced Identity Cloud, the cookie name is usually iPlanetDirectoryPro.

Show additional configuration properties
Property Description

forgerock_oauth_threshold

A threshold, in seconds, to refresh an OAuth 2.0 token before the access_token expires (defaults to 30 seconds).

forgerock_timeout

A timeout, in seconds, for each request that communicates with PingAM.

Add required connection settings to your app

  1. In the Project tree view of your Android Studio project, navigate to app  res  values, and then open the strings.xml file.

  2. Inside the <resources> element, add the following elements, adjusting the values for your deployment:

    <!-- OAuth 2.0 client details -->
    <string name="forgerock_oauth_client_id" translatable="false">sdkPublicClient</string>
    <string name="forgerock_oauth_redirect_uri" translatable="false">https://sdkapp.example.com:8443/callback</string>
    <string name="forgerock_oauth_scope" translatable="false">openid profile email address</string>
    
    <!-- PingOne Advanced Identity Cloud details -->
    <string name="forgerock_url" translatable="false">https://openam-forgerock-sdks.forgeblocks.com/am</string>
    <string name="forgerock_cookie_name" translatable="false">iPlanetDirectoryPro</string>
    <string name="forgerock_realm" translatable="false">alpha</string>
    
    <!-- Journey details -->
    <string name="forgerock_auth_service" translatable="false">sdkUsernamePasswordJourney</string>

Check point

You have now configured your application with the settings it needs to connect to your PingOne Advanced Identity Cloud or PingAM instance.

In the next step, you add debug logging and initialize the SDK.

Step 3. Initialize the SDK

In this step, you enable debug logging during development.

You then add a call to the FRAuth.start() method, which initializes the SDK and loads the configuration you have defined in the previous step.

Enable debug logging and initialize the SDK

  1. Open the project’s MainActivity class file.

    For example, app > java > com.example.quickstart > MainActivity.

  2. Enable debug logging and initialize the SDK in the onCreate() method after the generated code:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
     // Add these lines:
      Logger.set(Logger.Level.DEBUG);
        FRAuth.start(this);
    }
  3. Add the required import statements for org.forgerock.android.auth.FRAuth and org.forgerock.android.auth.Logger.

    import org.forgerock.android.auth.FRAuth;
    import org.forgerock.android.auth.Logger;

Check point

You have now added debug logging to your app and initialized the SDK.

  1. To test the setup so far, in Android Studio, select Run > Run 'app'.

    If everything is configured correctly, the app builds, and the default emulator will run the application.

  2. Open the Logcat pane. The SDK will generate output similar to the following if everything is configured correctly:

    -- PROCESS STARTED (14305) for package com.example
    [4.6.0] [DefaultTokenManager]: Using SharedPreference: StorageDelegate

    In the Logcat filter bar, enter tag:ForgeRock to only view output from the SDK.

If you get errors when running the app, check the app > res > values > strings.xml has the correct values. Refer to Step 2. Configure connection strings.

In the next step, you create the initial user interface to display the current authentication status, and add buttons to log in and log out.

Step 4. Create a status view

In this step, you create a layout and add buttons to log in and log out your user, as well as a text view field to show their current authentication status.

You also add the code to update the value displayed in the text view.

Create a layout for the status view

  1. Navigate to app > res > layout and open activity_main.xml.

  2. Select and delete the existing TextView element that contains the text Hello World!.

  3. From the Palette pane, drag a new TextView element to the canvas:

    • id: textViewUserStatus

    • text: User status

  4. From the Palette pane, drag a new Button element to the canvas:

    • id: buttonLogin

    • text: Log in

  5. From the Palette pane, drag a second new Button element to the canvas:

    • id: buttonLogout

    • text: Log out

  6. Layout the elements on the canvas to your liking.

    The following screenshot shows one possibility:

    Possible layout of status view elements
Show activity_main.xml source
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textViewUserStatus"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:text="User status"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/buttonLogin"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="8dp"
        android:text="Log in"
        app:layout_constraintEnd_toStartOf="@+id/buttonLogout"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textViewUserStatus" />

    <Button
        android:id="@+id/buttonLogout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:text="Log out"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/buttonLogin"
        app:layout_constraintTop_toBottomOf="@+id/textViewUserStatus" />
</androidx.constraintlayout.widget.ConstraintLayout>

Add a function to update the status view

  1. Open the project’s MainActivity class file.

    For example, app > java > com.example.quickstart > MainActivity.

  2. Add the following statements before the definition of the onCreate() function:

    private TextView status;
    private Button loginButton;
    private Button logoutButton;
  3. Add import statements for the FRUser module, and for android.widget.Button and android.widget.TextView.

    import org.forgerock.android.auth.FRUser;
    import android.widget.Button;
    import android.widget.TextView;
  4. In the onCreate() function, after the call to FRAuth.start(), add references to the elements on the status view layout:

    // Add references to status view elements
    status = findViewById(R.id.textViewUserStatus);
    loginButton = findViewById(R.id.buttonLogin);
    logoutButton = findViewById(R.id.buttonLogout);
    updateStatus();
  5. Add the following function after the existing onCreate() function:

    private void updateStatus() {
        runOnUiThread(() -> {
            if (FRUser.getCurrentUser() == null) {
                status.setText("User is not authenticated.");
                loginButton.setEnabled(true);
                logoutButton.setEnabled(false);
            } else {
                status.setText("User is authenticated.");
                loginButton.setEnabled(false);
                logoutButton.setEnabled(true);
            }
        });
    }

Check point

In Android Studio, select Run > Run 'app'.

If everything is configured correctly, the app builds, and the default emulator runs the application.

The app shows the Log in and Log out buttons, as well as a text view element that displays User is not authenticated:

Status view

In the next step, you create and attach functions to the buttons to start the authentication journey, or begin the logout process.

Step 5. Add login and logout calls

In this step, you update the app with the NodeListener interface, which manages the client side of the authentication journey.

The interface provides methods to handle the results of the authentication journey:

onSuccess()

The authentication journey is complete and an FRUser object is now available for further use.

For example, you could display the user’s name in your app.

onCallbackReceived()

Recursively handle each step within the authentication journey, by completing and returning any callbacks received.

For example, in this quick start guide we receive NameCallback and PasswordCallback callbacks. In the next step, we create the UI to request these credentials from the user.

onException()

Handle any errors.

Implement NodeListener and methods

  1. Edit the MainActivity class so that it implements NodeListener<FRUser>:

    public class MainActivity extends AppCompatActivity implements NodeListener<FRUser> {
  2. Add import statements for org.forgerock.android.auth.NodeListener and org.forgerock.android.auth.Node:

    import org.forgerock.android.auth.NodeListener;
    import org.forgerock.android.auth.Node;
  3. At the bottom of the MainActivity class, add the handler methods from the NodeListener interface:

    public class MainActivity extends AppCompatActivity implements NodeListener<FRUser> {
    
      // …​
      // …​
      // …​
    
      @Override
       public void onSuccess(FRUser result) {
         updateStatus();
       }
    
       @Override
       public void onCallbackReceived(Node node) {
         // Display appropriate UI to handle callbacks
       }
    
       @Override
       public void onException(Exception e) {
         Logger.error(TAG, e.getMessage(), e);
       }
    }
  4. Attach FRUser.login() and FRUser.logout() calls to the appropriate buttons, after the updateStatus() call:

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Logger.set(Logger.Level.DEBUG);
        FRAuth.start(this);
        // Add references to status view elements
        status = findViewById(R.id.textViewUserStatus);
        loginButton = findViewById(R.id.buttonLogin);
        logoutButton = findViewById(R.id.buttonLogout);
        updateStatus();
    
        // Attach 'FRUser.login()' to 'loginButton'
        loginButton.setOnClickListener(view → FRUser.login(getApplicationContext(), this));
    
        // Attach 'FRUser.getCurrentUser().logout()' to 'logoutButton'
        logoutButton.setOnClickListener(view → {
            FRUser.getCurrentUser().logout();
            updateStatus();
        });
    }

Check point

  1. In Android Studio, select Run > Run 'app'.

    If everything is configured correctly, the app builds, and the emulator runs the application.

  2. In the Emulator, click the Log in button.

    In the Run pane, you should see the following to indicate that the journey was found and the callbacks were returned. In our case, a NameCallback and PasswordCallback callback, as configured in the page node:

    [4.6.0] [AuthServiceResponseHandler]: Journey callback(s) received.

In the next step, you add a UI fragment to obtain credentials from the user, and code to open that fragment when the callback is received.

You also add code to populate the callback with the credentials and return it to the server, completing the authentication journey.

Step 6. Create UI to handle the callbacks

In this step, you add a UI fragment to obtain credentials from the user, and code to open that fragment when a callback is received.

The authentication journey in this quick start guide sends the NameCallback and PasswordCallback callbacks.

For demonstration purposes, this application uses a DialogFragment to collect the username and password.

You also add code to populate the callback with the credentials and return it to the server, completing the authentication journey.

Create a UI fragment

  1. Navigate to app > res.

  2. Right-click layout and select New > Fragment > Fragment (Blank).

  3. In the New Android Component dialog, enter the following values, and then click Finish:

    • Fragment Name: usernamePasswordFragment

    • Fragment Layout Name: fragment_username_password

    • Source Language: Java

  4. Navigate to app > res > layout and open fragment_username_password.xml.

  5. Select and delete the existing TextView element that contains the text Hello blank fragment.

  6. In the Component Tree pane, right-click the FrameLayout component, select Convert FrameLayout to ConstraintLayout, and then click OK.

  7. In the Palette pane, from the Text category drag a Plain Text input element to the canvas:

    • id: inputUsername

    • text: Username

  8. Drag a Password element to the canvas:

    • id: inputPassword

    • hint: Password

  9. In the Palette pane, from the Button category, drag a Button element to the canvas:

    • id: buttonCancel

    • text: Cancel

  10. Drag a second Button element to the canvas:

    • id: buttonContinue

    • text: Continue

  11. Layout the elements on the canvas to your liking.

    The following screenshot shows one possibility:

    Possible layout of fragment elements
Show fragment_username_password.xml source
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/frameLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".usernamePasswordFragment">

    <EditText
        android:id="@+id/inputUsername"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:ems="10"
        android:inputType="text"
        android:text="Username"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/inputPassword"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:ems="10"
        android:hint="Password"
        android:inputType="textPassword"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/inputUsername" />

    <Button
        android:id="@+id/buttonCancel"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="8dp"
        android:text="Cancel"
        app:layout_constraintEnd_toStartOf="@+id/buttonContinue"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/inputPassword"
        tools:text="Cancel" />

    <Button
        android:id="@+id/buttonContinue"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:text="Continue"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/buttonCancel"
        app:layout_constraintTop_toBottomOf="@+id/inputPassword" />
</androidx.constraintlayout.widget.ConstraintLayout>

Configure the fragment code

  1. Open usernamePasswordFragment.java

    For example, app  java  com.example.quickstart  usernamePasswordFragment.

  2. Update the class to extend DialogFragment rather than Fragment, which makes opening and closing the fragment easier:

    public class usernamePasswordFragment extends DialogFragment {
  3. Add import statements for androidx.fragment.app.DialogFragment:

    import androidx.fragment.app.DialogFragment;
  4. Within the usernamePasswordFragment class, initialize required variables:

    private MainActivity listener;
    private Node node;
  5. Update the newInstance method to accept a node object as its only parameter:

    public static usernamePasswordFragment newInstance(Node node) {
        usernamePasswordFragment fragment = new usernamePasswordFragment();
        Bundle args = new Bundle();
        args.putSerializable("NODE", node);
        fragment.setArguments(args);
        return fragment;
    }
  6. Insert an onResume() method below the newInstance() method. This correctly sizes the fragment dialog when displayed:

    @Override
    public void onResume() {
        super.onResume();
        ViewGroup.LayoutParams params = getDialog().getWindow().getAttributes();
        params.width = ViewGroup.LayoutParams.MATCH_PARENT;
        params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
        getDialog().getWindow().setAttributes((android.view.WindowManager.LayoutParams) params);
    }
  7. Delete the onCreate() function.

  8. Update the onCreateView method to capture the values from the fields in the fragment and populate the callbacks the node returned:

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_username_password, container, false);
        node = (Node) getArguments().getSerializable("NODE");
        AppCompatEditText username = view.findViewById(R.id.inputUsername);
        AppCompatEditText password = view.findViewById(R.id.inputPassword);
        Button next = view.findViewById(R.id.buttonContinue);
        next.setOnClickListener(v -> {
            dismiss();
            node.getCallback(NameCallback.class)
                    .setName(username.getText().toString());
            node.getCallback(PasswordCallback.class)
                    .setPassword(password.getText().toString().toCharArray());
            node.next(getContext(), listener);
        });
        Button cancel = view.findViewById(R.id.buttonCancel);
        cancel.setOnClickListener(v -> {
            dismiss();
        });
        return view;
    }
  9. Add an onAttach() method after the onCreateView() method. This ensures the fragment is correctly connected to the main activity:

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        if (context instanceof MainActivity) {
            listener = (MainActivity) context;
        }
    }
  10. Add any missing required import statements:

    import android.content.Context;
    import android.os.Bundle;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.Button;
    
    import androidx.annotation.NonNull;
    import androidx.appcompat.widget.AppCompatEditText;
    import androidx.fragment.app.DialogFragment;
    
    import org.forgerock.android.auth.Node;
    import org.forgerock.android.auth.callback.NameCallback;
    import org.forgerock.android.auth.callback.PasswordCallback;
Show completed usernamePasswordFragment.java source
package com.example.quickstart;

import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.DialogFragment;
import android.content.Context;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatEditText;
import org.forgerock.android.auth.Node;
import org.forgerock.android.auth.callback.NameCallback;
import org.forgerock.android.auth.callback.PasswordCallback;

/**
 * A simple {@link Fragment} subclass.
 * Use the {@link usernamePasswordFragment#newInstance} factory method to
 * create an instance of this fragment.
 */
public class usernamePasswordFragment extends DialogFragment {

    private MainActivity listener;
    private Node node;

    public usernamePasswordFragment() {
        // Required empty public constructor
    }

    public static usernamePasswordFragment newInstance(Node node) {
        usernamePasswordFragment fragment = new usernamePasswordFragment();
        Bundle args = new Bundle();
        args.putSerializable("NODE", node);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onResume() {
        super.onResume();
        ViewGroup.LayoutParams params = getDialog().getWindow().getAttributes();
        params.width = ViewGroup.LayoutParams.MATCH_PARENT;
        params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
        getDialog().getWindow().setAttributes((android.view.WindowManager.LayoutParams) params);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_username_password, container, false);
        node = (Node) getArguments().getSerializable("NODE");
        AppCompatEditText username = view.findViewById(R.id.inputUsername);
        AppCompatEditText password = view.findViewById(R.id.inputPassword);
        Button next = view.findViewById(R.id.buttonContinue);
        next.setOnClickListener(v -> {
            dismiss();
            node.getCallback(NameCallback.class)
                    .setName(username.getText().toString());
            node.getCallback(PasswordCallback.class)
                    .setPassword(password.getText().toString().toCharArray());
            node.next(getContext(), listener);
        });
        Button cancel = view.findViewById(R.id.buttonCancel);
        cancel.setOnClickListener(v -> {
            dismiss();
        });
        return view;
    }

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        if (context instanceof MainActivity) {
            listener = (MainActivity) context;
        }
    }
}

Open the fragment when receiving callbacks

  1. Open the project’s MainActivity class file.

    For example, app > java > com.example.quickstart > MainActivity.

  2. Update the onCallbackReceived() method to open the fragment to gather the credentials:

    @Override
    public void onCallbackReceived(Node node) {
        usernamePasswordFragment fragment = usernamePasswordFragment.newInstance(node);
        fragment.show(getSupportFragmentManager(), usernamePasswordFragment.class.getName());
    }

Check point

You have now completed the quick start application.

You added a UI fragment to obtain credentials from the user, and code to open that fragment when the callback is received.

You also added code to populate the callback with the credentials and return it to the server, completing the authentication journey.

In the next step, you test the application by authenticating a user, checking the logs, and then logging out.

Step 7. Test the app

In this step, you run and test your application.

You run it in the emulator or on your Android device, perform authentication with a demo user, check the log for success messages, and then log out the user.

Log in as a demo user

  1. In Android Studio, select Run > Run 'app'.

  2. Click Log in.

    The fragment dialog appears, with fields for both name and password, as well as continue and cancel buttons:

    The dialog fragment with text fields and buttons.
  3. Enter the credentials of the demo user:

    • Name: demo

    • Password: Ch4ng3it!

  4. Click Continue.

    If authentication is successful, the application returns to the main screen, and displays User is authenticated.

  5. Open the Logcat pane.

    If authentication was successful, the log contains entries similar to the following:

    [4.6.0] [AuthServiceResponseHandler]: Journey finished with Success outcome.
    [4.6.0] [AuthServiceResponseHandler]: SSO Token received.

    If authentication fails:

    • Check the credentials you are using are correct.

      For example, attempt to log directly into your ID Cloud or PingAM instance using them.

    • Check your strings.xml has the correct values for your environment

  6. Click the Log out button.

    If logout is successful, the application displays User is not authenticated.

    The Logcat pane contains entries similar to the following:

    [4.6.0] [OAuth2ResponseHandler]: Revoke success
    [4.6.0] [DefaultTokenManager]: Revoking AccessToken & Refresh Token Success

You have successfully completed the tutorial.

Next Steps

PingOne server

Step 1. Download the samples

Check that you have completed the prerequisites before starting the tutorial.

To start this tutorial, you need to download the ForgeRock SDK sample apps repo, which contains the projects you will use.

  1. In a web browser, navigate to the SDK Sample Apps repository.

  2. Download the source code using one of the following methods:

    Download a ZIP file
    1. Click Code, and then click Download ZIP.

    2. Extract the contents of the downloaded ZIP file to a suitable location.

    Use a Git-compatible tool to clone the repo locally
    1. Click Code, and then copy the HTTPS URL.

    2. Use the URL to clone the repository to a suitable location.

      For example, from the command-line you could run:

The result of these steps is a local folder named sdk-sample-apps.

Step 2. Configure the sample app

In this step, you configure the "kotlin-ui-prototype" sample to connect to the OAuth 2.0 application you created in PingOne, using the centralized login method.

  1. In Android Studio, open the sdk-sample-apps/android/kotlin-ui-prototype project you cloned in the previous step.

  2. In the Project pane, switch to the Android view.

  3. In the Android view, navigate to app > kotlin+java > com.example.app > env, and open EnvViewModel.kt.

    This file contains the server environments the sample app can use. Each specifies the properties using the FROptionsBuilder.build method.

  4. Add the following after any existing environments, with a suitable name. For example, you could use the name of the OAuth 2.0 client, sdkNativeClient:

    val sdkNativeClient = FROptionsBuilder.build {
        server {
            url = "<PingOne Issuer URL>"
        }
        oauth {
            oauthClientId = "<PingOne Client ID>"
            oauthRedirectUri = "org.forgerock.demo://oauth2redirect"
            oauthSignOutRedirectUri = "org.forgerock.demo://oauth2redirect"
            oauthScope = "openid profile email address revoke"
        }
    }

    Replace the following strings with the values you obtained when you registered an OAuth 2.0 application for native mobile apps in PingOne.

    <PingOne Client ID>

    The client ID from your OAuth 2.0 native mobile application in PingOne.

    For example, 6c7eb89a-66e9-ab12-cd34-eeaf795650b2

    <PingOne Issuer URL>

    The Issuer endpoint from your OAuth 2.0 application in PingOne.

    For example, https://auth.pingone.com/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/as

    The issuer URL is the same as the OIDC Discovery Endpoint, after removing /.well-known/openid-configuration.

    The result resembles the following:

    val sdkNativeClient = FROptionsBuilder.build {
        server {
            url = "https://auth.pingone.com/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/as"
        }
        oauth {
            oauthClientId = "6c7eb89a-66e9-ab12-cd34-eeaf795650b2"
            oauthRedirectUri = "org.forgerock.demo://oauth2redirect"
            oauthSignOutRedirectUri = "org.forgerock.demo://oauth2redirect"
            oauthScope = "openid profile email address revoke"
        }
    }
  5. In the init object, add your configuration to the list of servers available to the app:

    The result resembles the following:

    init {
        servers.add(localhost)
        // ...
        servers.add(sdkNativeClient)
    }
  6. Optionally, specify which of the configured policies PingOne uses to authenticate users.

    In /app/kotlin+java/com.example.app/centralize/CentralizeLoginViewModel, in the login(fragmentActivity: FragmentActivity) function, add an acr_values parameter to the authorization request by using the setAdditionalParameters() method:

    fun login(fragmentActivity: FragmentActivity) {
      FRUser.browser().appAuthConfigurer()
        // Add acr values to the authorization request
        .authorizationRequest{
          it.setAdditionalParameters(
            mapOf(
              "acr_values" to "<Policy IDs>"
            )
          )
        }
        .customTabsIntent {
          it.setColorScheme(CustomTabsIntent.COLOR_SCHEME_DARK)
        }.appAuthConfiguration { appAuthConfiguration → }
        .done()
        .login(fragmentActivity,
            object : FRListener<FRUser> {
                override fun onSuccess(result: FRUser) {
                  state.update {
                      it.copy(user = result, exception = null)
                  }
                }
    
                override fun onException(e: Exception) {
                    state.update {
                        it.copy(user = null, exception = e)
                    }
                }
            }
        )
    }

    Replace <Policy IDs> with either a single DaVinci policy, by using its flow policy ID, or one or more PingOne policies by specifying the policy names, separated by spaces or the encoded space character %20.

    Examples:

    DaVinci flow policy ID

    "acr_values" to "d1210a6b0b2665dbaa5b652221badba2"

    PingOne policy names

    "acr_values" to "Single_Factor%20Multi_Factor"

    For more information, refer to Editing an application - OIDC.

With the sample configured, you can proceed to Step 3. Run the sample app and perform centralized login.

Step 3. Run the sample app and perform centralized login

In the following procedure, you run the sample app that you configured in the previous step. The app performs a centralized login on your PingOne instance.

Log in as a demo user

  1. In Android Studio, select Run  Run 'app'.

  2. On the Environment screen, ensure the PingOne environment you added in the previous step is selected.

    android sample app env en
    Figure 12. Select the PingOne environment
  3. Tap the menu icon (), and then tap Centralize Login:

    android sample app centralized login en
    Figure 13. From the menu, select Centralize Login.

    The app launches a web browser and redirects to your PingOne environment:

    android sample app pingone en
    Figure 14. Browser launched and redirected to PingOne
  4. Sign on using the credentials of the demo user:

    • Name: demo

    • Password: Ch4ng3it!

    If authentication is successful, the application returns to the user info screen.

  5. Tap Show Userinfo to display the details of the token issues to the demo user:

    android sample app userinfo en
    Figure 15. User info of the demo user
  6. Tap the menu icon (), and then tap Logout.

    The app briefly opens a browser to log the user out of PingOne, and revoke the tokens.

    To verify the user is logged out:

    1. In the PingOne administration console, navigate to Directory > Users.

    2. Select the user you signed in as.

    3. From the Sevices dropdown, select Authentication:

      pingone sessions en
      Figure 16. Checking a user’s sessions in PingOne.

      The Sessions section displays any existing sessions the user has, and whether they originate from a mobile device.

PingFederate server

Step 1. Download the samples

Check that you have completed the prerequisites before starting the tutorial.

To start this tutorial, you need to download the projects you will use.

  1. In a web browser, navigate to the SDK Sample Apps repository.

  2. Download the source code using one of the following methods:

    Download a ZIP file
    1. Click Code, and then click Download ZIP.

    2. Extract the contents of the downloaded ZIP file to a suitable location.

    Use a Git-compatible tool to clone the repo locally
    1. Click Code, and then copy the HTTPS URL.

    2. Use the URL to clone the repository to a suitable location.

      For example, from the command-line you could run:

      git clone https://github.com/ForgeRock/sdk-sample-apps.git

The result of these steps is a local folder named sdk-sample-apps.

Step 2. Configure the sample app

In this step, you configure the "kotlin-ui-prototype" sample to connect to the OAuth 2.0 application you created in PingFederate, using the centralized login method.

  1. In Android Studio, open the sdk-sample-apps/android/kotlin-ui-prototype folder you cloned in the previous step.

  2. In the Project pane, switch to the Android view.

  3. In the Android view, navigate to app > kotlin+java > com.example.app > env, and open EnvViewModel.kt.

    This file has the server environments the sample app uses. Each specifies the properties using the FROptionsBuilder.build method.

  4. Update the PingFederate example configuration with your environment’s details:

    // Example values for a PingFederate instance
    val PingFederate = FROptionsBuilder.build {
        server {
            url = "<PingFederate Base URL>"
        }
        oauth {
            oauthClientId = "<PingFederate Client ID>"
            oauthRedirectUri = "org.forgerock.demo://oauth2redirect"
            oauthSignOutRedirectUri = "org.forgerock.demo://oauth2redirect"
            oauthScope = "openid profile email address"
        }
    }

    Replace the following string with the value you obtained when you registered an OAuth 2.0 application in PingFederate.

    <PingFederate Client ID>

    The client ID from your OAuth 2.0 application in PingFederate.

    For example, sdkPublicClient

    <PingFederate Base URL>

    The Base URL of your PingFederate server.

    How do I find my PingFederate Base URL?

    To verify the base URL of your PingFederate server:

    1. Log in to your PingFederate administration console.

    2. Navigate to System  Server  Protocol Settings.

    3. Make a note of the Base URL value.

      Do not use the admin console URL.

    The sample code adds /.well-known/openid-configuration after the base URL value to form the .well-known endpoint of your server. The SDK reads the OAuth 2.0 paths it requires from this endpoint.

    The result resembles the following:

    val PingFederate = FROptionsBuilder.build {
        server {
            url = "https://pingfed.example.com"
        }
        oauth {
            oauthClientId = "sdkPublicClient"
            oauthRedirectUri = "org.forgerock.demo://oauth2redirect"
            oauthSignOutRedirectUri = "org.forgerock.demo://oauth2redirect"
            oauthScope = "openid profile email address"
        }
    }
  5. In the init object, check that PingFederate is in the list of server configurations available to the sample app:

    For example:

    init {
        servers.add(PingAM)
        servers.add(PingAdvancedIdentityCloud)
        servers.add(PingOne)
        servers.add(PingFederate)
    }

With the sample configured, you can proceed to Step 3. Run the sample app and perform centralized login.

Step 3. Run the sample app and perform centralized login

In the following procedure, you run the sample app that you configured in an earlier step. The app performs a centralized login on your PingFederate instance.

Log in as a demo user

  1. In Android Studio, select Run  Run 'app'.

  2. On the Environment screen, select the PingFederate .well-known endpoint you added in the earlier step.

    pingfed sample app env en
    Figure 17. Select the PingFederate environment
  3. Tap the menu icon (), and then tap Centralize Login:

    android sample app centralized login en
    Figure 18. From the menu, select Centralize Login.

    The app launches a web browser and redirects to your PingFederate environment:

    pingfed sample app login en
    Figure 19. Browser launched and redirected to PingFederate
  4. Sign on using your PingFederate credentials

    If authentication is successful, the application returns to the user info screen.

  5. Tap Show Userinfo to display the details of the token issues to the demo user:

    android sample app pingfed userinfo en
    Figure 20. User info of the demo user
  6. Tap the menu icon (), and then tap Logout.

    The app opens a browser momentarily to log the user out of PingFederate, and revoke the tokens.

Apple iOS tutorials

Prerequisites

Before starting the tutorials, complete these prerequisite tasks.

Configure your server

Select your server environment below for instructions on creating the necessary configuration to successfully complete these tutorials:

Tutorial steps

There are three tutorials available for iOS, depending on your server environment:

  • Advanced Identity Cloud/PingAM

  • PingOne

  • PingFederate

This tutorial guides you through creating a Ping SDK-enabled iOS app from beginning to end.

It uses embedded login to implement user authentication journeys, meaning you get to design and implement the user interface to your requirements.

Step 1. Download the samples

In this step, you download the Ping SDK sample apps repo, which contains the projects you will use.

Step 2. Configure the sample app

In this step, you create a file to contain the settings you need to connect to your PingOne Advanced Identity Cloud or PingAM instance.

Step 3. Run the sample app and perform embedded login

In this final step, you run the sample app. The app steps through the login journey, rendering a UI to collect the required data for each node.

Configure a sample iOS app to launch a browser to authenticate your users in PingOne, using centralized login.

After authenticating, PingOne redirects users back to your sample client application, that can then get OAuth 2.0 tokens and user info for the user.

Step 1. Download the samples

In this step, you download the Ping SDK sample apps repo, which contains the projects you will use.

Step 2. Configure the sample app

In this step, you configure the "FRExample" sample to connect to the OAuth 2.0 application you created in PingOne, using the centralized login method.

Step 3. Run the sample app and perform centralized login

In this final step, you run the sample app that you configured in the previous step.

The app performs a centralized login to your PingOne instance.

Configure a sample iOS app to launch a browser to authenticate your users in PingFederate, using centralized login.

After authenticating, PingFederate redirects users back to your sample client application, that can then get OAuth 2.0 tokens and user info for the user.

Step 1. Download the samples

In this step, you download the Ping SDK sample apps repo, which contains the projects you will use.

Step 2. Configure the sample app

In this step, you configure the "FRExample" sample to connect to the OAuth 2.0 application you created in PingFederate, using the centralized login method.

Step 3. Run the sample app and perform centralized login

In this final step, you run the sample app that you configured in the previous step.

The app performs a centralized login to your PingFederate instance.

Advanced Identity Cloud/PingAM

Step 1. Download the samples

Check that you have completed the prerequisites before starting the tutorial.

To start this tutorial, you need to download the SDK sample apps repo, which contains the projects you will use.

  1. In a web browser, navigate to the SDK Sample Apps repository.

  2. Download the source code using one of the following methods:

    Download a ZIP file
    1. Click Code, and then click Download ZIP.

    2. Extract the contents of the downloaded ZIP file to a suitable location.

    Use a Git-compatible tool to clone the repo locally
    1. Click Code, and then copy the HTTPS URL.

    2. Use the URL to clone the repository to a suitable location.

      For example, from the command-line you could run:

      git clone https://github.com/ForgeRock/sdk-sample-apps.git

The result of these steps is a local folder named sdk-sample-apps.

Step 2. Configure the sample app

In this step, you configure the "uikit-quickstart" sample app to connect to the OAuth 2.0 application you created in PingOne Advanced Identity Cloud or PingAM, using the embedded login method.

  1. In Xcode, on the File menu, click Open.

  2. Navigate to the sdk-sample-apps folder you cloned in the previous step, navigate to iOS > uikit-quickstart > Quickstart.xcodeproj, and then click Open.

  3. In the navigator pane in Xcode, right-click FRAuthConfig and select Open As > Source Code.

  4. Replace the existing file content with the following:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
      <dict>
         <key>forgerock_url</key>
         <string>{as_url}</string>
         <key>forgerock_cookie_name</key>
         <string>{cookie_name}</string>
         <key>forgerock_realm</key>
         <string>{realm_path}</string>
         <key>forgerock_oauth_client_id</key>
         <string>{oauth2_client_id}</string>
         <key>forgerock_oauth_redirect_uri</key>
         <string>{oauth2_redirect}</string>
         <key>forgerock_oauth_scope</key>
         <string>openid profile email address</string>
         <key>forgerock_oauth_threshold</key>
         <string>60</string>
         <key>forgerock_timeout</key>
         <string>60</string>
         <key>forgerock_auth_service_name</key>
         <string>sdkUsernamePasswordJourney</string>
         <key>forgerock_registration_service_name</key>
         <string>Registration</string>
      </dict>
    </plist>
  5. Replace the following strings with the values you obtained when you registered the OAuth 2.0 application in either PingOne Advanced Identity Cloud or PingAM:

    {as_url}

    The base URL of the server to connect to.

    Identity Cloud example:

    https://openam-forgerock-sdks.forgeblocks.com/am

    Self-hosted example:

    https://openam.example.com:8443/openam

    {cookie_name}

    The name of the cookie that contains the session token.

    For example, with a self-hosted PingAM server this value might be iPlanetDirectoryPro.

    PingOne Advanced Identity Cloud tenants use a random alpha-numeric string.

    To locate the cookie name in an PingOne Advanced Identity Cloud tenant, navigate to Tenant settings > Global Settings, and copy the value of the Cookie property.

    {realm_path}

    The realm in which the OAuth 2.0 client profile and authentication journeys are configured.

    Usually, root for AM and alpha or beta for Advanced Identity Cloud.

    {oauth2_client_id}

    The client ID of your OAuth 2.0 application in PingOne Advanced Identity Cloud or PingAM.

    For example, sdkPublicClient

    {oauth2_redirect}

    The redirect_uri as configured in the OAuth 2.0 client profile.

    This value must exactly match a value configured in your OAuth 2.0 client.

    For example, org.forgerock.demo://oauth2redirect.

    The result resembles the following:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
      <dict>
         <key>forgerock_url</key>
         <string>https://openam.example.com:8443/openam</string>
         <key>forgerock_cookie_name</key>
         <string>iPlanetDirectoryPro</string>
         <key>forgerock_realm</key>
         <string>alpha</string>
         <key>forgerock_oauth_client_id</key>
         <string>sdkPublicClient</string>
         <key>forgerock_oauth_redirect_uri</key>
         <string>org.forgerock.demo://oauth2redirect</string>
         <key>forgerock_oauth_scope</key>
         <string>openid profile email address</string>
         <key>forgerock_oauth_threshold</key>
         <string>60</string>
         <key>forgerock_timeout</key>
         <string>60</string>
         <key>forgerock_auth_service_name</key>
         <string>sdkUsernamePasswordJourney</string>
         <key>forgerock_registration_service_name</key>
         <string>Registration</string>
      </dict>
    </plist>
  6. Save your changes.

With the sample configured, you can proceed to Step 3. Run the sample app and perform embedded login.

Step 3. Run the sample app and perform embedded login

In the following procedure, you run the sample app that you configured in the previous step. The app steps through the login journey, rendering a UI to collect the required data for each node, for example a username and password node, together inside a page node.

  1. In Xcode, select Product > Run.

    Xcode launches the sample app in the iPhone simulator.

    ios quickstart login button en
    Figure 21. Starting the quickstart app in an iOS simulator
  2. In the sample app on the iPhone simulator, tap the Login button.

    The app displays fields to input the user’s credentials:

    ios quickstart username password en
    Figure 22. Login as your demo user
  3. Sign on using the credentials of your demo user, and then click Next. For example:

    • User Name: demo

    • Password: Ch4ng3it!

    If authentication is successful the app displays a message that the user is authenticated and enables the Logout button:

    ios quickstart lgout button en
    Figure 23. Login as your demo user

    The console in Xcode outputs the access token, as well as a message that the user is authenticated:

    [FRCore][4.6.0] [🌐 - Network] Response | [✅ 200] :
    https://openam-forgerock-sdks.forgeblocks.com/am/oauth2/realms/alpha/access_token in 77 ms
    	Response Header: [
        AnyHashable("x-forgerock-transactionid"): 670c-c3e9,
        AnyHashable("Content-Type"): application/json;charset=UTF-8,
        AnyHashable("Date"): Tue, 12 Nov 2024 15:36:27 GMT,
    	  Response Data: {
            "access_token":"eyJ0…​Pc8k",
            "refresh_token":"eyJ0…​dnA4",
            "scope":"address openid profile email",
            "id_token":"eyJ0…​czKQ",
            "token_type":"Bearer",
            "expires_in":3598
        }
      ]
    User is authenticated
  4. Tap Logout to revoke the tokens, end the session, and return to the initial screen.

    The console in Xcode outputs the calls to the /sessions and /revoke endpoints:

    Logout button is pressed
    
    [FRCore][4.6.0] [🌐 - Network] Request | [POST]
    https://openam-forgerock-sdks.forgeblocks.com/am/json/realms/alpha/sessions
        Additional Headers: [
            "accept-api-version": "resource=3.1",
            "8a92ca506c38f08": "qc7z…​MQ.."]
    	URL Parameters: ["_action": "logout"]
    	Body Parameters: ["tokenId": "qc7z…​MQ.."]
    
    [FRCore][4.6.0] [🌐 - Network] Request | [POST]
    https://openam-forgerock-sdks.forgeblocks.com/am/oauth2/realms/alpha/token/revoke
        Body Parameters: [
            "token": "eyJ0…​dnA4",
            "client_id": "sdkPublicClient"]
    
    [FRCore][4.6.0] [🌐 - Network] Response | [✅ 200] :
    https://openam-forgerock-sdks.forgeblocks.com/am/json/realms/alpha/sessions?_action=logout in 117 ms
        Response Data: {
            "result":"Successfully logged out"}
    
    [FRCore][4.6.0] [🌐 - Network] Response | [✅ 200] :
    https://openam-forgerock-sdks.forgeblocks.com/am/oauth2/realms/alpha/token/revoke in 113 ms
    	Response Data: {}

PingOne server

Step 1. Download the samples

Check that you have completed the prerequisites before starting the tutorial.

To start this tutorial, you need to download the ForgeRock SDK sample apps repo, which contains the projects you will use.

  1. In a web browser, navigate to the SDK Sample Apps repository.

  2. Download the source code using one of the following methods:

    Download a ZIP file
    1. Click Code, and then click Download ZIP.

    2. Extract the contents of the downloaded ZIP file to a suitable location.

    Use a Git-compatible tool to clone the repo locally
    1. Click Code, and then copy the HTTPS URL.

    2. Use the URL to clone the repository to a suitable location.

      For example, from the command-line you could run:

      git clone https://github.com/ForgeRock/sdk-sample-apps.git

The result of these steps is a local folder named sdk-sample-apps.

Step 2. Configure the sample app

In this step, you configure the "FRExample" app to connect to the OAuth 2.0 application you created in PingOne, using the centralized login method.

  1. In Xcode, on the File menu, click Open.

  2. Navigate to the sdk-sample-apps folder you cloned in the previous step, navigate to iOS > uikit-frexamples > FrExample > FrExample > FRExample.xcodeproj, and then click Open.

  3. In the Project Navigator pane, navigate to FRExample > FRExample, and open the ViewController file.

  4. In the ViewController file:

    1. Change the useDiscoveryURL variable to true:

      let useDiscoveryURL = true

      Changing the variable causes the sample to use the discover method to get many of the required configuration values from your PingOne OIDC .well-known endpoint.

    2. Replace CLIENT_ID_PLACEHOLDER with the ID of the OAuth 2.0 client application you created previously in PingOne:

      let config =
      ["forgerock_oauth_client_id": "6c7eb89a-66e9-ab12-cd34-eeaf795650b2",
      "forgerock_oauth_redirect_uri": "org.forgerock.demo://oauth2redirect",
      "forgerock_oauth_scope" : "openid profile email address revoke",
      "forgerock_ssl_pinning_public_key_hashes": ["SSL_PINNING_HASH_PLACEHOLDER"]]
    3. Remove or comment out the forgerock_ssl_pinning_public_key_hashes line.

      For information on SSL pinning, refer to Enable SSL pinning.

    4. Replace DISCOVERY_URL_PLACEHOLDER with the .well-known endpoint from your OAuth 2.0 native mobile application in PingOne.

      For example:

      let discoveryURL = "https://auth.pingone.com/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/as/.well-known/openid-configuration"
    5. Optionally, specify which of the configured policies PingOne uses to authenticate users.

      In the performCentralizedLogin function, add an acr_values parameter to the authorization request by using the setCustomParam() method:

      func performCentralizedLogin() {
          FRUser.browser()?
              .set(presentingViewController: self)
              .set(browserType: .authSession)
              // Add acr values to the authorization request
              .setCustomParam(key: "acr_values", value: "<Policy IDs>")
              .build().login { (user, error) in
                  self.displayLog("User: \(String(describing: user)) || Error: \(String(describing: error))")
          }
          return
      }

      Replace <Policy IDs> with either a single DaVinci policy, by using its flow policy ID, or one or more PingOne policies by specifying the policy names, separated by spaces or the encoded space character %20.

      Examples:

      DaVinci flow policy ID

      .setCustomParam(key: "acr_values", value: "d1210a6b0b2665dbaa5b652221badba2")

      PingOne policy names

      .setCustomParam(key: "acr_values", value: "Single_Factor%20Multi_Factor")

    For more information, refer to Editing an application - OIDC.

With the sample configured, you can proceed to Step 3. Run the sample app and perform centralized login.

Step 3. Run the sample app and perform centralized login

In the following procedure, you run the sample app that you configured in the previous step. The app performs a centralized login on your PingOne instance.

Log in as a demo user

  1. In Xcode, select Product  Run.

    Xcode launches the sample app in the iPhone simulator.

  2. In the sample app on the iPhone simulator, in the Select an action menu, select Login with Browser, and then click Perform Action.

    ios samples app login with browser en
    Figure 24. Select the PingOne environment
    You might see a dialog asking if you want to open a browser. If you do, tap Continue.

    The app launches a web browser and redirects to your PingOne environment:

    ios sample app pingone en
    Figure 25. Browser launched and redirected to PingOne
  3. Sign on using the credentials of the demo user:

    • Name: demo

    • Password: Ch4ng3it!

    If authentication is successful, the application displays the tokens issued by PingOne.

  4. Tap Login with Browser to open the drop-down menu, select User Logout, and then tap Perform Action.

    The app briefly opens a browser to sign the user out of PingOne, and revoke the tokens.

    To verify the user is signed out:

    1. In the PingOne administration console, navigate to Directory > Users.

    2. Select the user you signed in as.

    3. From the Sevices dropdown, select Authentication:

      pingone sessions en
      Figure 26. Checking a user’s sessions in PingOne.

      The Sessions section displays any existing sessions the user has, and whether they originate from a mobile device.

PingFederate server

Step 1. Download the samples

Check that you have completed the prerequisites before starting the tutorial.

To start this tutorial, you need to download the ForgeRock SDK sample apps repo, which contains the projects you will use.

  1. In a web browser, navigate to the SDK Sample Apps repository.

  2. Download the source code using one of the following methods:

    Download a ZIP file
    1. Click Code, and then click Download ZIP.

    2. Extract the contents of the downloaded ZIP file to a suitable location.

    Use a Git-compatible tool to clone the repo locally
    1. Click Code, and then copy the HTTPS URL.

    2. Use the URL to clone the repository to a suitable location.

      For example, from the command-line you could run:

      git clone https://github.com/ForgeRock/sdk-sample-apps.git

The result of these steps is a local folder named sdk-sample-apps.

Step 2. Configure the sample app

In this step, you configure the "FRExample" app to connect to the OAuth 2.0 application you created in PingFederate, using the centralized login method.

  1. In Xcode, on the File menu, click Open.

  2. Navigate to the sdk-sample-apps folder you cloned in the previous step, navigate to iOS > uikit-frexamples > FrExample > FrExample > FRExample.xcodeproj, and then click Open.

  3. In the Project Navigator pane, navigate to FRExample > FRExample, and open the ViewController file.

  4. In the ViewController file:

    1. Change the useDiscoveryURL variable to true:

      let useDiscoveryURL = true

      Changing the variable causes the sample to use the discover method to get many of the required configuration values from your PingFederate OIDC .well-known endpoint.

    2. Replace CLIENT_ID_PLACEHOLDER with the ID of the OAuth 2.0 client application you created previously in PingFederate:

      let config =
      ["forgerock_oauth_client_id": "sdkPublicClient",
      "forgerock_oauth_redirect_uri": "org.forgerock.demo://oauth2redirect",
      "forgerock_oauth_scope" : "openid profile email address",
      "forgerock_ssl_pinning_public_key_hashes": ["SSL_PINNING_HASH_PLACEHOLDER"]]
    3. Remove or comment out the forgerock_ssl_pinning_public_key_hashes line.

      For information on SSL pinning, refer to Enable SSL pinning.

    4. Replace DISCOVERY_URL_PLACEHOLDER with the .well-known endpoint from your PingFederate server.

      The .well-known endpoint is the base URL of your PingFederate server, with /.well-known/openid-configuration appended.

      How do I find my PingFederate Base URL?

      To verify the base URL of your PingFederate server:

      1. Log in to your PingFederate administration console.

      2. Navigate to System  Server  Protocol Settings.

      3. Make a note of the Base URL value.

        Do not use the admin console URL.

      For example:

      let discoveryURL = "https://pingfed.example.com/as/.well-known/openid-configuration"

For more information, refer to Editing an application - OIDC.

With the sample configured, you can proceed to Step 3. Run the sample app and perform centralized login.

Step 3. Run the sample app and perform centralized login

In the following procedure, you run the sample app that you configured in the previous step. The app performs a centralized login on your PingFederate server.

Log in as a demo user

  1. In Xcode, select Product  Run.

    Xcode launches the sample app in the iPhone simulator.

  2. In the sample app on the iPhone simulator, in the Select an action menu, select Login with Browser, and then click Perform Action.

    ios samples app login with browser en
    Figure 27. Select the PingFederate environment
    You might see a dialog asking if you want to open a browser. If you do, tap Continue.

    The app launches a web browser and redirects to your PingFederate environment:

    pingfed ios sample app login en
    Figure 28. Browser launched and redirected to PingFederate
  3. Sign on using your PingFederate credentials

    If authentication is successful, the application displays the token issued by PingFederate:

    pingfed ios sample app tokens en
    Figure 29. Sample app showing the token returned from PingFederate
  4. Tap Login with Browser to open the drop-down menu, select User Logout, and then tap Perform Action.

    The app briefly opens a browser to sign the user out of PingFederate, and revoke the tokens.

JavaScript tutorials

Integrate Ping SDK for JavaScript with PingOne Advanced Identity Cloud or PingAM

In this tutorial you update a sample app that uses embedded login.

The sample navigates through a simple authentication journey, and obtains OAuth 2.0 tokens for the user.

Check that you have completed the prerequisites before starting the tutorial.

Step 1. Download the samples

To start this tutorial, you need to download the Ping SDK sample apps repo, which contains the projects you will use.

  1. In a web browser, navigate to the Ping SDK sample apps repository.

  2. Download the source code using one of the following methods:

    Download a ZIP file
    1. Click Code, and then click Download ZIP.

    2. Extract the contents of the downloaded ZIP file to a suitable location.

    Use a Git-compatible tool to clone the repo locally
    1. Click Code, and then copy the HTTPS URL.

    2. Use the URL to clone the repository to a suitable location.

      For example, from the command-line you could run:

The result of these steps is a local folder named sdk-sample-apps.

Step 2. Install the dependencies

In the following procedure, you install the required modules and dependencies, including the Ping SDK for JavaScript.

  1. In a terminal window, navigate to the sdk-sample-apps/javascript folder.

  2. To install the required packages, enter the following:

    npm install

    The npm tool downloads the required packages, and places them inside a node_modules folder.

Step 3. Configure the embedded login sample app

In this step, you configure the sample app to connect to the authentication tree/journey you created when setting up your server configuration.

Copy the .env.example file in the sdk-sample-apps/embedded-login folder and save it with the name .env within this same directory.

Add your relevant values to this new file as it will provide all the important configuration settings to your applications.

Here’s an example; your values may vary:

AM_URL=https://openam-forgerock-sdks.forgeblocks.com/am
REALM_PATH=alpha
SCOPE=openid profile email address
TIMEOUT=5000
TREE=sdkUsernamePasswordJourney
WEB_OAUTH_CLIENT=sdkPublicClient

Here are descriptions for some of the values:

TREE

The simple login journey or tree you created earlier, for example sdkUsernamePasswordJourney.

REALM_PATH

The realm of your server.

Usually, root for AM and alpha or beta for Advanced Identity Cloud.

Step 4. Run the embedded login sample app

In the following procedure, you run the sample app that you configured in the previous step. The sample connects to your server and walks through the authentication journey you created in an earlier step.

After successful authentication, the sample obtains an OAuth 2.0 access token and displays the related user information.

To run the UI Sample

  1. In a terminal window, navigate to the root of your sdk-sample-apps project.

  2. To run the embedded login sample, enter the following:

    npm run start:embedded-login
  3. In a web browser:

    1. Ensure you are NOT currently logged into the server instance.

      If you are logged into the PingAM instance in the browser, the sample will not work. Logout of the PingAM instance before you run the sample.
    2. Navigate to the following URL:

      https://localhost:8443

      A form appears with "Username" and "Password" fields, as defined by the page node in the sdkUsernamePasswordJourney you created in a previous step:

      Running the app
    3. Authenticate as a non-administrative user, and click Sign In.

      Default login credentials:

      • "Username" - demo

      • "Password" - Ch4ng3it!

        If the app displays the user information, authentication was successful:

        Successful OAuth 2.0 authentication
        To see the application calling the authorize and authenticate endpoints, open the Network tab of your browser’s developer tools.
  4. To revoke the OAuth 2.0 token, click the Sign Out button.

    The application calls the endSession endpoint to revoke the OAuth 2.0 token, and returns to the sign-in form.

Recap

Congratulations!

You have now used the Ping SDK for JavaScript to authenticate to your server instance.

You have seen how to obtain OAuth 2.0 tokens, view the related user information, and log a user out of the server.

Integrate Ping SDK for JavaScript with PingOne

In this tutorial you update a sample app that uses OIDC-based login to obtain tokens by redirecting to the server’s UI for authentication.

The sample connects to the .well-known endpoint of your PingOne server to obtain the correct URIs to authenticate the user, and redirects to your PingOne server’s login UI.

After authentication, PingOne redirects the browser back to your application, which then obtains an OAuth 2.0 access token and displays the related user information.

Check that you have completed the prerequisites before starting the tutorial.

Step 1. Download the samples

To start this tutorial, you need to download the Ping SDK sample apps repo, which contains the projects you will use.

  1. In a web browser, navigate to the Ping SDK sample apps repository.

  2. Download the source code using one of the following methods:

    Download a ZIP file
    1. Click Code, and then click Download ZIP.

    2. Extract the contents of the downloaded ZIP file to a suitable location.

    Use a Git-compatible tool to clone the repo locally
    1. Click Code, and then copy the HTTPS URL.

    2. Use the URL to clone the repository to a suitable location.

      For example, from the command-line you could run:

      git clone https://github.com/ForgeRock/sdk-sample-apps.git

The result of these steps is a local folder named sdk-sample-apps.

Step 2. Install the dependencies

In the following procedure, you install the required modules and dependencies, including the Ping SDK for JavaScript.

  1. In a terminal window, navigate to the sdk-sample-apps folder.

  2. To install the required packages, enter the following:

    npm install

    The npm tool downloads the required packages, and places them inside a node_modules folder.

Step 3. Configure the central login sample app

In this step, you configure the sample app to connect to the OAuth 2.0 application you created in PingOne.

  1. In the IDE of your choice, open the sdk-sample-apps folder you cloned in the previous step.

  2. Make a copy of the /javascript/central-login-oidc/.env.example file, and name it .env.

    The .env file provides the values used by the forgerock.Config.setAsync() method in javascript/central-login-oidc/src/main.js.

  3. Update the .env file with the details of your PingOne instance.

    SCOPE="$SCOPE"
    TIMEOUT=$TIMEOUT
    WEB_OAUTH_CLIENT="$WEB_OAUTH_CLIENT"
    WELL_KNOWN="$WELL_KNOWN"
    SERVER_TYPE="$SERVER_TYPE"

    Replace the following strings with the values you obtained when you registered an OAuth 2.0 application in PingOne.

    $SCOPE

    The scopes you added to your OAuth 2.0 application in PingOne.

    For example, openid profile email address revoke

    $TIMEOUT

    How long to wait for OAuth 2.0 timeouts, in milliseconds.

    For example, 3000

    $WEB_OAUTH_CLIENT

    The client ID from your OAuth 2.0 application in PingOne.

    For example, 6c7eb89a-66e9-ab12-cd34-eeaf795650b2

    $WELL_KNOWN

    The .well-known endpoint from your OAuth 2.0 application in PingOne.

    For example, https://auth.pingone.com/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/as/.well-known/openid-configuration

    $SERVER_TYPE

    Ensures the sample app uses the correct behavior for the different servers it supports, for example what logout parameters to use.

    For PingOne and PingFederate servers, specify PINGONE.

    The result resembles the following:

    .env
    SCOPE="openid profile email address revoke"
    TIMEOUT=3000
    WEB_OAUTH_CLIENT="6c7eb89a-66e9-ab12-cd34-eeaf795650b2"
    WELL_KNOWN="https://auth.pingone.com/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/as/.well-known/openid-configuration"
    SERVER_TYPE="PINGONE"
  4. (Optional) Specify which of the configured policies PingOne uses to authenticate users.

    In the /javascript/central-login-oidc/src/main.js file add an acr_values query parameter to the getTokens() calls.

    There are multiple calls to the getTokens() function.

    Only update the calls that have the login: 'redirect' parameter.

    await forgerock.TokenManager.getTokens({
      login: 'redirect',
      query: {
        acr_values: "<Policy IDs>"
      }
    });

    Replace <Policy IDs> with either a single DaVinci policy, by using its flow policy ID, or one or more PingOne policies by specifying the policy names, separated by spaces or the encoded space character %20.

    Examples:

    DaVinci flow policy ID

    acr_values: "d1210a6b0b2665dbaa5b652221badba2"

    PingOne policy names

    acr_values: "Single_Factor%20Multi_Factor"

    For more information, refer to Editing an application - OIDC.

Step 4. Run the central login sample app

  1. In a terminal window, navigate to the root of your sdk-sample-apps project.

  2. To run the embedded login sample, enter the following:

    npm run start:central-login-oidc
  3. In a web browser, navigate to the following URL:

    The sample displays a page with two buttons:

    Running the central login app
  4. Click Login.

    The sample app redirects the browser to your PingOne instance.

    To see the application calling the authorize endpoint, and the redirect back from PingOne with the code and state OAuth 2.0 parameters, open the Network tab of your browser’s developer tools.
  5. Authenticate as a known user in your PingOne system.

    After successful authentication, PingOne redirects the browser to the client application.

    If the app displays the user information, authentication was successful:

    Successful OAuth 2.0 authentication
  6. To revoke the OAuth 2.0 token, click the Sign Out button.

    The application redirects to the PingOne server to revoke the OAuth 2.0 token and end the session, and then returns to the URI specified by the logoutRedirectUri parameter of the logout method.

    In this tutorial, PingOne redirects users back to the client application, ready to authenticate again.

Recap

Congratulations!

You have now used the Ping SDK for JavaScript to obtain an OAuth 2.0 access token on behalf of a user from your PingOne server.

You have seen how to obtain OAuth 2.0 tokens, and view the related user information.

Integrate Ping SDK for JavaScript with PingFederate

In this tutorial you update a sample app that uses OIDC-based login to obtain tokens by redirecting to the server’s UI for authentication.

The sample connects to the .well-known endpoint of your PingFederate server to obtain the correct URIs to authenticate the user, and redirects to your PingFederate server’s login UI.

After authentication, PingFederate redirects the browser back to your application, which then obtains an OAuth 2.0 access token and displays the related user information.

Check that you have completed the prerequisites before starting the tutorial.

Step 1. Download the samples

To start this tutorial, you need to download the Ping SDK sample apps repo, which contains the projects you will use.

  1. In a web browser, navigate to the Ping SDK sample apps repository.

  2. Download the source code using one of the following methods:

    Download a ZIP file
    1. Click Code, and then click Download ZIP.

    2. Extract the contents of the downloaded ZIP file to a suitable location.

    Use a Git-compatible tool to clone the repo locally
    1. Click Code, and then copy the HTTPS URL.

    2. Use the URL to clone the repository to a suitable location.

      For example, from the command-line you could run:

The result of these steps is a local folder named sdk-sample-apps.

Step 2. Install the dependencies

In the following procedure, you install the required modules and dependencies, including the Ping SDK for JavaScript.

  1. In a terminal window, navigate to the sdk-sample-apps folder.

  2. To install the required packages, enter the following:

    npm install

    The npm tool downloads the required packages, and places them inside a node_modules folder.

Step 3. Configure the central login sample app

In this step, you configure the sample app to connect to the OAuth 2.0 application you created in PingFederate.

  1. In the IDE of your choice, open the sdk-sample-apps folder you cloned in the previous step.

  2. Make a copy of the /javascript/central-login-oidc/.env.example file, and name it .env.

    The .env file provides the values used by the forgerock.Config.setAsync() method in javascript/central-login-oidc/src/main.js.

  3. Update the .env file with the details of your PingFederate instance.

    SCOPE="$SCOPE"
    TIMEOUT=$TIMEOUT
    WEB_OAUTH_CLIENT="$WEB_OAUTH_CLIENT"
    WELL_KNOWN="$WELL_KNOWN"
    SERVER_TYPE="$SERVER_TYPE"

    Replace the following strings with the values you obtained when you registered an OAuth 2.0 application in PingFederate.

    $SCOPE

    The scopes you added to your OAuth 2.0 application in PingFederate.

    For example, address email openid phone profile

    $TIMEOUT

    How long to wait for OAuth 2.0 timeouts, in milliseconds.

    For example, 3000

    $WEB_OAUTH_CLIENT

    The client ID from your OAuth 2.0 application in PingFederate.

    For example, sdkPublicClient

    $WELL_KNOWN

    The .well-known endpoint from your OAuth 2.0 application in PingFederate.

    How do I find my PingFederate .well-known URL?

    You can form your .well-known endpoint by adding /.well-known/openid-configuration to your server’s base URL.

    To view the base URL of your PingFederate server:

    1. Log in to your PingFederate administration console.

    2. Navigate to System  Server  Protocol Settings.

    3. Make a note of the Base URL value.

      For example, https://pingfed.example.com

      Do not use the admin console URL.
    4. Append /.well-known/openid-configuration to your base URL.

    For example, https://pingfed.example.com/.well-known/openid-configuration

    $SERVER_TYPE

    Ensures the sample app uses the correct behavior for the different servers it supports, for example what logout parameters to use.

    For PingOne and PingFederate servers, specify PINGONE.

    The result resembles the following:

    .env
    SCOPE="address email openid phone profile"
    TIMEOUT=3000
    WEB_OAUTH_CLIENT="sdkPublicClient"
    WELL_KNOWN="https://pingfed.example.com/.well-known/openid-configuration"
    SERVER_TYPE="PINGONE"

Step 4. Run the central login sample app

In the following procedure, you run the sample app that you configured in the previous step.

The sample connects to your PingFederate server to obtain the correct URIs to authenticate the user, and redirects the browser to your PingFederate server.

After authentication, PingFederate redirects the browser back to your application, which then obtains an OAuth 2.0 access token and displays the related user information.

Run the sample

  1. In a terminal window, navigate to the root of your sdk-sample-apps project.

  2. To run the embedded login sample, enter the following:

    npm run start:central-login-oidc
  3. In a web browser, navigate to the following URL:

    The sample displays a page with two buttons:

    Running the central login app
  4. Click Login.

    The sample app redirects the browser to your PingFederate instance.

    To see the application calling the authorize endpoint, and the redirect back from PingFederate with the code and state OAuth 2.0 parameters, open the Network tab of your browser’s developer tools.
  5. Authenticate as a known user in your PingFederate system.

    After successful authentication, PingFederate redirects the browser to the client application.

    If the app displays the user information, authentication was successful:

    Successful OAuth 2.0 authentication
  6. To revoke the OAuth 2.0 token, click the Sign Out button.

    In this tutorial, PingFederate redirects users back to the client application, ready to authenticate again.

Recap

Congratulations!

You have now used the Ping SDK for JavaScript to obtain an OAuth 2.0 access token on behalf of a user from your PingFederate server.

You have seen how to obtain OAuth 2.0 tokens, and view the related user information.

Implement your use cases with the Ping SDKs

The SDKs enable you to implement many authentication, registration, and self-service use cases into your mobile and web apps.

Visit the following pages for more information on implementing different use cases using the Ping SDKs:

Focal Point

Applies to: Android | iOS | JavaScript

With centralized login you reuse the PingAM UI or your own web application for login requests in multiple apps and sites.

Find out how to configure your application to use centralized login.

Mobile Biometrics

Applies to: Android | iOS

Discover how to allow users to authenticate by using an authenticator device. For example, the fingerprint scanner on their laptop or a phone.

Leverage passkey support to synchronize across multiple devices.

Web Access

Applies to: JavaScript

Discover how to allow users to authenticate by using WebAuthn.

Leverage passkey support to synchronize across multiple devices.

Laptop Mobile Pin Protected

Applies to: Android | iOS | JavaScript

Instruct your client applications to collect device profile information for decision-making in authentication journeys.

User Network

Applies to: Android | iOS | JavaScript

Add support for authenticating to your apps by using trusted Identity Providers (IdP), like Apple, Facebook, and Google.

Browser Basic Self Service

Applies to: Android | iOS | JavaScript

Learn how to pause a user’s progress through an authentication tree, and later resume from the same point.

Approval

Applies to: Android | iOS | JavaScript

Configure transactional authorization support in your app. Transactional authorization requires a user to authorize individual access attempts to specific protected resources.

It is part of an PingAM policy that grants single-use or one-shot access.

Laptop Mobile Pin Protected

Applies to: JavaScript

Learn how to handle callbacks that require a QR code to be displayed.

A number of journeys make use of QR codes, such as device registration for multi-factor authentication.

Use centralized login

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

The following diagram shows a sample OAuth 2.0 flow that the Ping SDK uses for your native app:

OAuth 2.0 for Native Apps

Note that the SDKs also support OAuth 2.0 security with PKCE.

Select your platform below for instructions on setting up centralized login:

Set up centralized login in Android apps

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

This section describes how to configure your Ping SDK for Android application to use centralized login by leveraging the AppAuth library:

  1. Add the build dependency to the build.gradle file:

    implementation 'net.openid:appauth:0.11.1'
  2. Associate your application with the scheme your redirect URIs use.

    To ensure that only your app is able to obtain authorization tokens during centralized login we recommend you configure it to use Android App Links.

    If you do not want to implement Android App Links, you can instead use a custom scheme for your redirect URIs.

    • Custom Scheme

    Complete the following steps to configure a custom scheme:

    1. Configure the AppAuth library to use the custom scheme for capturing redirect URIs by using either of these two methods:

      • Add the custom scheme your app will use to your build.gradle file:

        android.defaultConfig.manifestPlaceholders = [
            'appAuthRedirectScheme': 'com.forgerock.android'
        ]

      Or:

      • Add an <intent-filter> for AppAuth.RedirectUriReceiverActivity to your AndroidManifest.xml:

        <activity
           android:name="net.openid.appauth.RedirectUriReceiverActivity"
           tools:node="replace">
            <intent-filter>
               <action android:name="android.intent.action.VIEW"/>
               <category android:name="android.intent.category.DEFAULT"/>
               <category android:name="android.intent.category.BROWSABLE"/>
               <data android:scheme="com.forgerock.android"/>
            </intent-filter>
        </activity>

      For more information, refer to Capturing the authorization redirect.

    2. For Android 11 or higher, add the following to the AndroidManfest.xml file:

      <queries>
           <intent>
               <action android:name="android.intent.action.VIEW" />
               <category android:name="android.intent.category.BROWSABLE" />
               <data android:scheme="com.forgerock.android" />
           </intent>
       </queries>
    3. Configure your application to use the redirect URI, either in the strings.xml file, or by using FROptions:

      strings.xml:
      <string name="forgerock_oauth_redirect_uri" translatable="false">com.forgerock.android:/oauth2redirect</string>
      FROptions:
      let options = FROptions(
          ...,
          oauthRedirectUri: "com.forgerock.android:/oauth2redirect",
          ...,
      )
    4. Add the custom scheme to the Redirection URIs property of your OAuth 2.0 client. For example, com.forgerock.android:/oauth2redirect

  3. Configure your application to use browser mode:

    // Use FRUser.browser() to enable browser mode:
    FRUser.browser().login(context, new FRListener<FRUser>());
    
    // Use standard SDK interface to retrieve an AccessToken:
    FRUser.getCurrentUser().getAccessToken()
    
    // Use standard SDK interface to logout a user:
    FRUser.getCurrentUser().logout()

    The SDK uses the OAuth 2.0 parameters you configured in your application.

    You can amend the example code above to customize the integration with AppAuth; for example, adding OAuth 2.0 or OpenID Connect parameters, and browser colors:

     FRUser.browser().appAuthConfigurer()
         .authorizationRequest(r -> {
             // Add a login hint parameter about the user:
             r.setLoginHint("demo@example.com");
             // Request that the user re-authenticates:
             r.setPrompt("login");
         })
         .customTabsIntent(t -> {
             // Customize the browser:
             t.setShowTitle(true);
             t.setToolbarColor(getResources().getColor(R.color.colorAccent));
         }).done()
         .login(this, new FRListener<FRUser>() {
             @Override
             public void onSuccess(FRUser result) {
                 //success
             }
    
             @Override
             public void onException(Exception e) {
                 //fail
             }
         });

Set up centralized login in iOS apps

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

This section describes how to configure your Ping SDK for iOS application to use centralized login:

  1. Associate your application with the scheme your redirect URIs use.

    To ensure that only your app is able to obtain authorization tokens during centralized login we recommend you configure it to use Universal Links.

    If you do not want to implement Universal Links, you can instead use a custom scheme for your redirect URIs.

    • Custom scheme

    Configure a custom URL type, for example frauth, so that users are redirected to your application:

    1. In Xcode, in the Project Navigator, double-click your application to open the Project pane.

    2. On the Info tab, in the URL Types panel, configure your custom URL scheme:

      Custom URL Scheme
    3. Add the custom URL scheme to the Redirection URIs property of your OAuth 2.0 client:

      OAuth 2.0 Redirection URI
  2. Update your application to call the validateBrowserLogin() function:

    1. In your AppDelegate.swift file, call the validateBrowserLogin() function:

      AppDelegate.swift
      class AppDelegate: UIResponder, UIApplicationDelegate {
      
        func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
          // Parse and validate URL, extract authorization code, and continue the flow:
          Browser.validateBrowserLogin(url)
        }
      }
    2. If you are using Universal Links, also add code similar to the following to set the URL:

      AppDelegate.swift
      func application(
        _ application: UIApplication,
        continue userActivity: NSUserActivity,
        restorationHandler:
        @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool
        {
          // Get URL components from the incoming user activity.
          guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
          let incomingURL = userActivity.webpageURL else {
            return false
          }
          Browser.validateBrowserLogin(url)
        }
      )
    3. If your application is using SceneDelegate, in your SceneDelegate.swift file call the validateBrowserLogin() function:

      SceneDelegate.swift
      class SceneDelegate: UIResponder, UIWindowSceneDelegate {
      
        func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
          if let url = URLContexts.first?.url {
            Browser.validateBrowserLogin(url)
          }
        }
      }
  3. To enable centralized login, add code similar to the following to your app:

    //  BrowserBuilder
    let browserBuilder = FRUser.browser()
    browserBuilder.set(presentingViewController: self)
    browserBuilder.set(browserType: .authSession)
    browserBuilder.setCustomParam(key: "custom_key", value: "custom_val")
    
    //  Browser
    let browser = browserBuilder.build()
    
    // Login
    browser.login{ (user, error) in
      if let error = error {
        // Handle error
      }
      else if let user = user {
        // Handle authenticated status
      }
    }

    You can specify what type of browser the client iOS device opens to handle centralized login by using the browserType parameter.

    Each browser has slightly different characteristics, which make them suitable to different scenarios, as outlined in this table:

    browserType Characteristics

    .authSession

    Opens a web authentication session browser.

    Designed specifically for authentication sessions, however it prompts the user before opening the browser with a modal that asks them to confirm the domain is allowed to authenticate them.

    This is the default option in the Ping SDK for iOS.

    .ephemeralAuthSession

    Opens a web authentication session browser, but enables the prefersEphemeralWebBrowserSession parameter.

    This browser type does not prompt the user before opening the browser with a modal.

    The difference between this and .authSession is that the browser does not include any existing data such as cookies in the request, and also discards any data obtained during the browser session. This means that an ephemeralAuthSession is not suitable when you require single sign-on (SSO) between your iOS apps.

    Use this browser type when you do not want the user’s existing sessions to affect the authentication.

    .nativeBrowserApp

    Opens the installed browser that is marked as the default by the user. Often Safari.

    The browser opens without any interaction from the user. However, the browser does display a modal when returning to your application.

    .sfViewController

    Opens a Safari view controller browser.

    Your client app is not able to interact with the pages in the sfViewController or access the data or browsing history.

    The view controller opens within your app without any interaction from the user. As the user does not leave your app, the view controller does not need to display a warning modal when authentication is complete and control returns to your application.

Set up centralized login in JavaScript apps

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

This section describes how to configure your Ping SDK for JavaScript application with centralized login:

  1. To initiate authentication by redirecting to the centralized login UI, add a login property that specifies how authentication happens in your app:

    const tokens = TokenManager.getTokens({
      forceRenew: false, // Immediately return stored tokens, if they exist
      login: 'redirect' // Redirect to {am_name} or the web app that handles authentication
    });

    Supported values are as follows:

    Setting Description

    redirect

    Your app uses a redirect to PingAM, or another web application, to handle authentication.

    embedded

    Your app handles authentication natively, using SDK functionality.

    If you do not specify a value, embedded is assumed, for backwards-compatibility.

  2. When the user is returned to your app, complete the OAuth 2.0 flow by passing in the code and state values that were returned.

    Use the query property to complete the flow:

    const tokens = TokenManager.getTokens({
      query: {
        code: 'lFJQYdoQG1u7nUm8 ... ', // Authorization code from redirect URL
        state: 'MTY2NDkxNTQ2Nde3D ... ', // State from redirect URL
      },
    });

What are mobile biometrics?

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

Mobile biometric authentication lets users authenticate by using a mobile device’s biometric authenticator. Communication with the platform authenticator, such as fingerprint reader or facial recognition system, is handled by the SDK. The SDK communicates with PingAM to perform biometric registration and authentication using WebAuthn nodes. You can configure the nodes in PingAM to request that the SDK activates authenticators with certain criteria.

To enable mobile biometrics, the user’s authenticator must first be registered through an authentication journey with the WebAuthn Registration node. Registration involves the selected authenticator creating a key pair. This key pair is specific to the origin of the application performing the authentication. The private key is used to sign the challenge from PingAM and create attestation for the authenticator.

The public key of the pair is sent to PingAM and stored in the user’s profile. The private key is securely stored within the mobile device’s and never leaves the device at any time.

When authenticating using mobile biometrics, the registered user encounters the WebAuthn Authentication node via an authentication journey. A challenge from PingAM is created and sent to the user’s device. The device then signs an assertion from that challenge with its stored, private key. This assertion is then sent to PingAM for verification using the public key stored in the user’s profile. If the data is verified as being from the registered authenticator and passes attestation checks, the authentication is considered successful.

Differences between mobile biometrics and device binding

There are many similarities between WebAuthn and Device Binding and JWS verification. We provide authentication nodes to implement both technologies in your journeys.

Both can be used for usernameless and passwordless authentication, they both use public key cryptography, and both can be used as part of a multi-factor authentication journey.

One major difference is that with device binding, the private key never leaves the device.

With WebAuthn, there is a possibility that the private key is synchronized across client devices because of Passkey support, which may be undesirable for your organization.

For more details of the differences, refer to the following table:

Comparison of WebAuthn and Device Binding/JWS Verification
Feature WebAuthn / FIDO Device Binding / JWS Verifier Details

Industry-standards based

You can refer to the WebAuthn W3C specification.

Device binding and JWS verification are proprietary implementations.

Public key cryptography

Both methods use Public key cryptography.

Usernameless support

After registration, the username can be stored in the device and obtained during authentication without the user having to enter their credentials.

Keys are bound to the device

With WebAuthn, if Passkeys are used, they can be shared across devices.

With device binding, the private keys do not leave the device.

Sign custom data

With device binding, you can:

  • Customize the challenge that the device must sign. For example, you could include details of a transaction, such as the amount in dollars.

  • Add custom claims to the payload when signing a challenge. This gives additional context that the server can make use of by using a scripted node.

Format of signed data

WebAuthn authenticator data

JSON Web Signature (JWS)

Integration

With device binding, after verification, the signed JWT is available in:

  • Audit Logs

  • Transient node state

This enables the data within to be used for integration into your processes and business logic.

Platform support

✅ Android

✅ iOS

✅ Web browsers

✅ Android

✅ iOS

❌ Web browsers

As it is challenging to store secure data in a browser as a client app, device binding is not supported in web browsers.

Authenticator support

Determined by the platform.

Configuration limited to:

  • Biometric with Fallback to Device Pin

Determined by the authentication node.

Full configuration options:

  • Biometric Authentication

  • Biometric with Fallback to Device Pin

  • Application Pin

  • Silent

With device binding, you can specify what authentication action the user must perform to get access to the private keys.

This provides greater flexibility in your security implementation and can reduce authentication friction for your users.

Key storage

Web browsers and iOS synchronize to the cloud.

Android has the option to synchronize to the cloud.

Android

KeyStore

iOS

Secure enclave: hardware-backed and not synchronized to the cloud.

Both technologies store the private keys securely on the client.

WebAuthn supports synchronizing the private keys to the cloud for use on other devices.

This can reduce authentication friction for your users but may also increase the risk of a breach.

Managing device keys

Managed by the device OS.

Apps cannot delete local client keys programmatically and do not have a reference to the remote server key for deletion.

Managed by the Ping SDKs.

Provides an interface to delete local client and remote server keys.

The ability to programmatically delete both client and server keys can greatly simplify the process of registering a new device if an old device is lost or stolen.

Passkey Support

WebAuthn supports synchronizing the private keys to the cloud for use on other devices.

Device binding keeps the private key locked in the device.

App integrity verification

Android

Requires an assetlinks.json file.

iOS

Requires apple-app-site-association file.

Not provided by the device binding or verification nodes.

It can be added as part of the journey by using app integrity nodes.

App integrity verification helps ensure your users are only using a supported app rather than a third-party or potentially malicious version.

Key attestation

Android

SafteyNet

iOS

None

Android

Uses hardware-backed key pairs with Key Attestation.

iOS

It can be added as part of the journey by using app integrity nodes to support key attestation.

Key attestation verifies that the private key is valid and correct, is not forged, and was not created in an insecure manner.

Complexity

Medium

Low

WebAuthn requires a bit more configuration, for example, creating and uploading the assetlinks.json and apple-app-site-association files.

Device binding only requires the journey and the SDK built into your app.

Prerequisites

To create a journey with mobile biometric authentication, you need the following:

  1. A server that supports the WebAuthn nodes (PingOne Advanced Identity Cloud or PingAM 7.1 or later).

  2. A mobile device that has biometric authentication, such as Face ID or a fingerprint reader, and the user has registered their biometrics on the device.

  3. An application with the latest native mobile SDK that includes the Biometric Authentication API (v3 or later).

The SDKs can only support the WebAuthn Registration and WebAuthn Authentication nodes when the Return challenge as JavaScript option is disabled.

When this option is disabled, the WebAuthn nodes return a MetadataCallback, which the SDK converts to a WebAuthnRegistrationCallback or a WebAuthnAuthenticationCallback.

Prepare the server

The following example journey covers the "usernameless" authentication case. This is a simple prototype flow that does not cover all edge cases that might be present in a production environment. If WebAuthn authentication is not possible for any reason, the flow falls back to a normal login journey.

To access this configuration, you need to log in to PingOne Advanced Identity Cloud or PingAM as an administrator, and create a new journey.

  1. In the editor drag the following nodes into the journey:

    • WebAuthn Registration node

    • WebAuthn Authentication node

    • Inner Tree Evaluator node

    • Two Choice Collector nodes

  2. Connect the nodes similar to the following example:

    Creating a biometrics journee
  3. Configure the nodes:

    • In both the WebAuthn Registration and WebAuthn Authentication nodes, the Return challenge as JavaScript option must be disabled.

    • In the WebAuthn Registration node, Authentication attachment must be either UNSPECIFIED or PLATFORM.

    • In the WebAuthn Registration node, enable the Username to device option.

    • In the WebAuthn Authentication node, enable the Username from device option.

    • Use Choice Collector nodes to handle the user input in case of registration failure, and to give users the option to enable biometrics for this journey.

    • Set the Relying party identifier to the domain of your PingAM instance.

      For example: openam.example.com.

If you want your users to provide a username, deactivate the Username from/to device options, and add a Username Collector node before the WebAuthn Registration node and WebAuthn Authentication node.

The WebAuthn Registration and WebAuthn Authentication nodes might result in a Client Error. Client errors can happen for a number of reasons.

In order to parse the error and act upon it, make use of a Scripted Decision node to access the shared state within the journey, and read the WebAuthenticationDOMException thrown.

For more information regarding the use of the Scripted Decision node, see Scripted Decision Node API Functionality in the PingAM documentation.

Biometrics using the Ping SDK for Android

This section covers how to implement mobile biometric authentication using the Ping SDK for Android.

Support for mobile biometrics lets users authenticate through the WebAuthn Registration and WebAuthn Authentication nodes to register the user’s device, and use the device as an authenticator.

Associate your app with your server

To associate your server with your Android app you need to make public, verifiable statements by using a Digital Asset Links JSON file (assetlinks.json).

Example assetlinks.json file
[
    {
        "relation": [
            "delegate_permission/common.handle_all_urls",
            "delegate_permission/common.get_login_creds"
        ],
        "target": {
            "namespace": "android_app",
            "package_name": "com.example.app",
            "sha256_cert_fingerprints": [
                "E6:5A:5D:37:22:FC...22:99:20:03:E6:47"
            ]
        }
    }
]

Get SHA-256 fingerprint of your signing certificates

The assetlinks.json file includes SHA-256 fingerprints of the certificates you use to sign your Android applications. The steps for obtaining the fingerprint depend on the method you use to distribute your application.

  • Android App Bundles

  • Local debug keys

If you are using Android App Bundles to distribute your apps, then the hashes of the certificate used to sign your application are available in the Android Developer console.

Follow these steps to obtain the SHA-256 hash of your signing certificate:

  1. Configure your Android App Bundle for signing. Google has a number of methods for managing the signing certificates, including uploading your own or having Google manage them for you.

    For information on how to set up signing, refer to Sign your app in the Google Developer Documentation.

  2. In the Google Play Console:

    1. Select the app that will be supporting mobile biometrics.

    2. Navigate to Setup > App integrity > App signing.

      android signing certificates en
      Figure 30. App signing keys in the Google Play Console
    3. In the App signing key certificate section, copy the SHA-256 certificate fingerprint value.

      In the Digital Asset Links JSON section is a file that you can copy with the SHA-256 fingerprint already in place.
  3. Create or update an assetlinks.json with the values copied from the Google Play Console for your app.

For more information on creating an assetlinks.json file, refer to Google Digital Asset Links.

You must manually generate a SHA-256 fingerprint of your signing key in the following scenarios:

  • You are signing your APK with the default debug.jks that Android Studio created for the project

  • You are signing your APK with your own keys that you have generated that have not been uploaded to the Google Play Console

Follow these steps to obtain the SHA-256 hash of your signing certificate:

  1. In the build.gradle file for your application, check the settings defined in the signingConfigs property:

    Example signingConfigs when using the default debug.jks
    signingConfigs {
        debug {
            storeFile file('../debug.jks')
            storePassword 'android'
            keyAlias 'androiddebugkey'
            keyPassword 'android'
        }
    }
  2. In a terminal window, navigate to the location of the JKS file, and then run the following command:

    keytool -list -v -alias <keyAlias> -keystore <storeFile> | grep SHA256

    Swap the <keyAlias> and <storeFile> placeholders with the values you obtained from your project. For example:

    keytool -list -v -alias "androiddebugkey" -keystore "./debug.jks" | grep SHA256

  3. When requested, enter the keystore password, as specified in the keyPassword property in the build.gradle file.

    The command prints the SHA-256 fingerprint of the signing key:

    Enter keystore password:  android
    SHA256: E6:5A:5D:37:22:FC...22:99:20:03:E6:47
    Signature algorithm name: SHA256withRSA
  4. Create or update an assetlinks.json with the SHA-256 fingerprint, and the details of your app.

For more information on creating an assetlinks.json file, refer to Google Digital Asset Links.

  • For PingOne Advanced Identity Cloud deployments, refer to Upload an Android assetlinks.json file.

  • For self-managed deployments, host the file at https://<your domain>/.well-known/assetlinks.json.

Summary

You have now created and uploaded a digital asset links JSON file.

Configure biometric authentication journeys

To use mobile biometrics with the Ping SDK for Android configure the authentication nodes in your journeys as follows:

  1. In each WebAuthn Registration node and WebAuthn Authentication node:

    • Ensure the Return challenge as JavaScript option is not enabled

      The SDK expects a JSON response from these nodes, enabling this option would cause the journey to fail

    • Set the Relying party identifier option to be the domain hosting the assetlinks.json file

      For example, openam-docs.forgeblocks.com

      You do not need the protocol or the path.

  2. In each WebAuthn Registration node

    • Set the Authentication attachment option to either UNSPECIFIED or PLATFORM

    • Ensure the Accepted signing algorithms option includes either ES256 or RS256

    • Ensure the Limit registrations option is not enabled

Configure origin domains

To enable WebAuthn on Android devices, you must configure the nodes with the base64-encoded SHA-256 hash of the signing certificate as the origin domain.

The steps for obtaining the base64-encoded SHA-256 hash depend on the method you use to distribute your application.

  • Android App Bundles

  • Local debug keys

Follow these steps to download the app signing certificate, and then generate a base64-encoded SHA-256 hash:

  1. In the Google Play Console:

    1. Select the app that will be supporting mobile biometrics.

    2. Navigate to Setup > App integrity > App signing.

    3. In the App signing key certificate section, click Download certificate.

      This downloads a local copy of the signing certificate, named deployment_cert.der.

  2. In a terminal window, navigate to the location of the deployment_cert.der file, and then run the following command:

    cat deployment_cert.der | openssl sha256 -binary | openssl base64 | tr '/+' '_-' | tr -d '='

    The command prints the base64-encode SHA-256 fingerprint of the signing key:

    jEFEYh80K55iHYkxsBRLGtAP6wvjOS5Pj-ZKHHjwi0k
  3. Add a prefix of android:apk-key-hash: to the base64-encode SHA-256 fingerprint. For example:

    android:apk-key-hash:jEFEYh80K55iHYkxsBRLGtAP6wvjOS5Pj-ZKHHjwi0k
  4. In each WebAuthn Registration node and WebAuthn Authentication node, set the Origin domains option to the value created in the previous step:

    android webauthn node config en
    Figure 31. Example WebAuthn Registration node configuration

Follow these steps to extract the app signing certificate from the JKS and generate a base64-encoded SHA-256 hash:

  1. In the build.gradle file for your application, check the settings defined in the signingConfigs property:

    Example signingConfigs when using the default debug.jks
    signingConfigs {
        debug {
            storeFile file('../debug.jks')
            storePassword 'android'
            keyAlias 'androiddebugkey'
            keyPassword 'android'
        }
    }
  2. In a terminal window, navigate to the location of the JKS file, and then run the following command:

    keytool -exportcert -alias <keyAlias> -keystore <storeFile> | openssl sha256 -binary | openssl base64 | tr '/+' '_-' | tr -d '='

    Swap the <keyAlias> and <storeFile> placeholders with the values you obtained from your project. For example:

    keytool -exportcert -alias "androiddebugkey" -keystore "./debug.jks" | openssl sha256 -binary | openssl base64 | tr '/+' '_-' | tr -d '='

  3. When requested, enter the keystore password, as specified in the keyPassword property in the build.gradle file.

    The command prints the base64-encoded SHA-256 fingerprint of the signing key:

    Enter keystore password:  android
    jEFEYh80K55iHYkxsBRLGtAP6wvjOS5Pj-ZKHHjwi0k
  4. Add a prefix of android:apk-key-hash: to the base64-encode SHA-256 fingerprint. For example:

    android:apk-key-hash:jEFEYh80K55iHYkxsBRLGtAP6wvjOS5Pj-ZKHHjwi0k
  5. In each WebAuthn Registration node and WebAuthn Authentication node, set the Origin domains option to the value created in the previous step:

    android webauthn node config en
    Figure 32. Example WebAuthn Registration node configuration

Summary

You have now configured your WebAuthn journey for use with the Ping SDK for Android.

Configure the Ping SDK for Android for WebAuthn

  1. Add the following dependency to the build.gradle file:

    implementation 'com.google.android.gms:play-services-fido:20.0.1'
  2. Link to assetlinks.json in the Android app, adding the following line to the manifest file under your application:

    <meta-data android:name="asset_statements" android:resource="@string/asset_statements" />
  3. Add an asset_statements string resource to the string.xml file:

    <string name="asset_statements" translatable="false">
    [{
      \"include\": \"https://<custom-domain-fqdn>/.well-known/assetlinks.json\"
    }]
    </string>

Register a WebAuthn device

To register a WebAuthn device on receipt of a WebAuthnRegistrationCallback from the server, use the register() method.

Optionally, use the deviceName parameter to assign a name to the device to help the user identify it.

  • Android - Java

  • Android - Kotlin

WebAuthnRegistrationCallback callback =
    node.getCallback(WebAuthnRegistrationCallback.class);

callback.register(requireContext(), deviceName, node, new FRListener<Void>() {
    @Override
    public void onSuccess(Void result) {
        // Registration is successful
        // Continue the journey by calling next()
    }

    @Override
    public void onException(Exception e) {
        // An error occurred during the registration process
        // Continue the journey by calling next()
    }
});
fun WebAuthnRegistrationCallback(
    callback: WebAuthnRegistrationCallback,
    node: Node,
    onCompleted: () -> Unit
) {

    val context = LocalContext.current
    var deviceName by remember { mutableStateOf(Build.MODEL) }

    try {
        callback.register(context, deviceName, node)
        // Registration is successful
        currentOnCompleted()
    } catch (e: CancellationException) {
        // User cancelled registration
    } catch (e: Exception) {
        // An error occurred during the registration process
        currentOnCompleted()
    }
}

Passkey support

The Ping SDK for Android supports passkeys when the app is running on Android P or later. For more information on passkeys, refer to Passkey support on Android and Chrome.

If the WebAuthn Registration node has the Username to device option enabled and the app is running on Android P or later, then the SDK sets the RESIDENT_KEY_REQUIRED flag and enables passkeys for WebAuthn.

In this case, the user is asked to create a new passkey on their device and is required to perform biometric authentication to confirm. The device syncs the generated passkey to the user’s Google Account for use on their supported devices.

android create passkey en
Figure 33. Creating a new passkey on Android

If the device is not running Android P or later, the SDK sets the RESIDENT_KEY_DISCOURAGED flag, meaning passkeys are not used nor synchronized to the Google Account.

For more information about resident keys and client-side discoverable credentials, refer to ResidentKeyRequirement in the Google developer documentation.

Override passkey support

You can use the setResidentKeyRequirement() method to override the automatic behavior. For example, if you do not want to use passkeys on Android P devices, you might use the following code:

callback.setResidentKeyRequirement(ResidentKeyRequirement.RESIDENT_KEY_DISCOURAGED)

Authenticate by using a WebAuthn device

After the user registers their mobile device they can use it as an authenticator, with its registered key pair, through the WebAuthn Authentication node, which the Ping SDK for Android returns as a WebAuthnAuthenticationCallback.

If the device supports passkeys, the operating system displays a list of available passkeys:

android select passkey en
Figure 34. Select the passkey to use for WebAuthn

Note that removing credentials stored on the client device does not remove the associated data from the server. You will need to register the device again after removing credentials from the client.

As part of authentication process, the SDK provides the WebAuthnAuthenticationCallback for authenticating the device as a credential.

  • Android - Java

  • Android - Kotlin

WebAuthnAuthenticationCallback callback = node.getCallback(WebAuthnAuthenticationCallback.class);
callback.authenticate(requireContext(), node, webAuthnKeySelector.DEFAULT, new FRListener<Void>() {
    @Override
    public void onSuccess(Void result) {
        // Authentication is successful
        // Continue the journey by calling next()
    }

    @Override
    public void onException(Exception e) {
        // An error occurred during the authentication process
        // Continue the journey by calling next()
    }
});
fun WebAuthnAuthenticationCallback(
    callback: WebAuthnAuthenticationCallback,
    node: Node,
    onCompleted: () -> Unit
) {

    val context = LocalContext.current

    try {
        callback.authenticate(context, node)
        // Authentication successful
        currentOnCompleted()
    } catch (e: CancellationException) {
        // User cancelled authentication
    } catch (e: Exception) {
        // An error occurred during the authentication process
        currentOnCompleted()
    }
}

The WebAuthnAuthenticationCallback.authenticate() method has a parameter, Node.

If the current node has both WebAuthnAuthenticationCallback and HiddenValueCallback callbacks then the SDK automatically sets the outcome of the authentication process for both success and failure to the designated HiddenValueCallback.

WebAuthnKeySelector

An optional WebAuthnKeySelector parameter can be provided for authentication.

The WebAuthnKeySelector.select() method is invoked when Username from device is enabled in the WebAuthn Authentication node. This feature requires that Username to device is enabled in the WebAuthn Registration node as well. With these options enabled, the registered key pair is associated with the username, and the SDK can present a list of registered keys to the user to continue the authentication process without collecting a username.

The sourceList is a list of PublicKeyCredentialSource constructed during registration. You can alter the string value and present the altered value to the user; however, you must return the selected PublicKeyCredentialSource as it was provided in the original list to the provided listener.

callback.authenticate(this, node, new WebAuthnKeySelector() {
    @Override
    public void select(@NonNull FragmentManager fragmentManager,
                       @NonNull List<PublicKeyCredentialSource> sourceList,
                       @NonNull FRListener<PublicKeyCredentialSource> listener) {
        //Always pick the first one.
        listener.onSuccess(sourceList.get(0));
    }
}, new FRListener<Void>() {
    @Override
    public void onSuccess(Void result) {
        //...
    }

    @Override
    public void onException(Exception e) {
        //...
    }
});

Handle WebAuthn errors

When an error occurs during the registration or authentication process, the Ping SDK for Android returns the WebAuthnResponseException exception. In most cases, errors are returned as per the specification. The error code can be found from WebAuthnResponseExcetpion.getErrorCode().

Convert exceptions for handling by the PingAM server

When you use WebAuthnRegistrationCallback.register() or WebAuthnAuthenticationCallback.authenticate(), the SDK automatically parses the error into the appropriate format for PingAM. When PingAM receives the completed callback from the SDK the authentication flow follows the WebAuthn registration process to reach the appropriate outcome.

However, if the error has to be handled manually, the WebAuthnResponseException class provides a convenience method called toServerError() to convert the error into the appropriate format.

callback.register(this, node, new FRListener<Void>() {
    @Override
    public void onSuccess(Void result) {
        next();
    }

    @Override
    public void onException(Exception e) {
        if (e instanceof WebAuthnResponseException) {
            WebAuthnResponseException exception = (WebAuthnResponseException) e;
            exception.getErrorCode(); // Do something with the error or proceed to the next node.
        }
    }
});

WebAuthnResponseExcetpion.getErrorCode() == com.google.android.gms.fido.fido2.api.common.ErrorCode#NOT_SUPPORTED_ERR results in an Unsupported outcome in both WebAuthn Registration node and WebAuthn Authentication node.

Any other WebAuthnResponseExcetpion.getErrorCode() results in a Client Error outcome in the nodes.

Unregister a WebAuthn device

To unregister a WebAuthn device from a user’s profile, use the deleteCredentials function in your application. The function requires the publicKeyCredentialSource as a parameter.

Use the loadAllCredentials method and pass in the relying party identifier (rpId) string to return an array of publicKeyCredentialSource values. The rpId string must match the configuration you used when you configured the authentication journeys earlier.

You can only remove a device if it has the username embedded in the profile.

You must enable the Username to Device option in the WebAuthn Registration node to be able to remove the device from a user’s profile on the server using the SDKs.

The SDK attempts to delete the record of the device from the server. If that succeeds, it will then remove the local keys held by the client device. If it fails to remove the records from the server, it will not remove the local keys by default.

However, you can pass the forceDelete: true boolean parameter to the function to delete the local keys even if the call to the server fails.

val rpId = "openam-docs.forgeblocks.com"

frWebAuthn.loadAllCredentials(rpId).let {
    frWebAuthn.deleteCredentials(it.first(), true)
}

Removing keys from either the server or the device means you will need to register it again for WebAuthn journeys. Refer to Register a WebAuthn device.

Biometrics using the Ping SDK for iOS

This section covers how to implement mobile biometric authentication using the Ping SDK for iOS.

Support for mobile biometrics lets users authenticate through the WebAuthn Registration and WebAuthn Authentication nodes to register the user’s device, and use the device as an authenticator.

Prepare an apple-app-site-association file

You can create an apple-app-site-association file that creates a secure association between your domain and your app. This allows you to share credentials, and use universal links to open your app from your website.

To create the secure association, you upload the apple-app-site-association file to your domain, and add matching Associated Domains Entitlement keys to your app.

  1. Prepare an apple-app-site-association file. For example:

    {
      "applinks": {
        "details": [
          {
            "appIDs": [
              "XXXXXXXXXX.com.example.AppName"
            ],
            "components": [
              {
                "/": "/reset/*",
                "comment": "Success after reset password journey"
              }
            ]
          }
        ]
      },
      "webcredentials": {
        "apps": [
          "XXXXXXXXXX.com.example.AppName"
        ]
      }
    }

    For more information, refer to Supporting associated domains.

  2. Host the file at your domain.

    • For PingOne Advanced Identity Cloud deployments, refer to Upload an apple-app-site-association file in the Identity Cloud documentation.

    • For self-managed deployments, host the file at https://<your domain>/.well-known/apple-app-site-association.

  3. Configure the associated domains entitlement key in your app.

    For more information, refer to Associated Domains Entitlement.

Configure biometric authentication journeys

To use mobile biometrics with the Ping SDK for iOS configure the authentication nodes in your journeys as follows:

  1. In each WebAuthn Registration node and WebAuthn Authentication node:

    • Ensure the Return challenge as JavaScript option is not enabled.

      The SDK expects a JSON response from these nodes; enabling the Return challenge as JavaScript option would cause the journey to fail.

    • Set the Relying party identifier option to be the domain hosting the apple-app-site-association file; for example, openam-docs.forgeblocks.com.

      You do not need the protocol or the path.

    • To enable passkey support, enable Username to device in the WebAuthn Registration node, and Username from device in the WebAuthn Authentication node.

  2. In each WebAuthn Registration node:

    • Set the Authentication attachment option to either UNSPECIFIED or PLATFORM.

    • Ensure the Accepted signing algorithms option includes ES256.

    • Ensure the Limit registrations option is not enabled.

Configure origin domains

To enable WebAuthn on iOS devices, you must configure the nodes with a specially-formatted string containing the bundle identifier of your application, which you can find in XCode, on the Signing & Capabilities tab of your apps target page:

ios bundle id en
Figure 35. Bundle identifier field in XCode

Prefix this value with the string ios:bundle-id:. For example:

ios:bundle-id:com.forgerock.ios.sdk.Quickstart

To enable passkey support, add the fully-qualified domain name of the PingOne Advanced Identity Cloud or PingAM instance as an origin domain. For example, https://openam-docs.forgeblocks.com.

Add these values to the Origin domains property in each WebAuthn Registration node and WebAuthn Authentication node in the journey.

Register a WebAuthn device

To register a WebAuthn device on receipt of a WebAuthnRegistrationCallback from the server, use the register() method.

if let registrationCallback = callback as? WebAuthnRegistrationCallback {

    registrationCallback.delegate = self

    registrationCallback.register(
        node: node,
        window: UIApplication.shared.windows.first,
        deviceName: UIDevice.current.name,
        usePasskeysIfAvailable: false)
    { (attestation) in
        // Registration is successful
        // Submit the Node using Node.next()
    } onError: { (error) in
        // An error occurred during the registration process
        // Submit the Node using Node.next()
    }
}

Use the optional deviceName parameter to assign a name to the device to help the user identify it.

Set the usePasskeysIfAvailable parameter to true to enable passkeys on supported devices.

Enable Passkey support

The Ping SDK for iOS supports passkeys when the app is running on iOS 16 or later, or recent versions of macOS. For more information, refer to Passkeys in the Apple developer documentation.

To enable the use of passkeys during registration, you should: - In PingAM, enable the Username to device option in the WebAuthn Registration node in your authentication journeys. - In the SDK, set the usePasskeysIfAvailable parameter to true in the registrationCallback.register function. - Run your app on a passkey-enabled version of iOS or macOS.

When passkeys are enabled the user is asked to create a new passkey on their device and is required to perform biometric authentication to confirm. The device syncs the generated passkey to the user’s iCloud account for use on their supported devices.

ios create passkey touch id en
Figure 36. Creating a new passkey on iOS

Detect WebAuthn keys on passkey-enabled devices

The localKeyExistsAndPasskeysAreAvailable() method is invoked when the SDK detects an existing WebAuthn key on the device, and that the device supports passkeys.

// MARK: PlatformAuthenticatorAuthenticationDelegate
func localKeyExistsAndPasskeysAreAvailable() {
    // You can prompt the user to Register a new Key to use with Apple Passkeys. From then on, use the `register` and `authenticate` methods passing the `usePasskeysIfAvailable: true`.
}

You can use this delegate method to offer a journey that reregisters the device. Make sure you set usePasskeysIfAvailable to true.

You might need to ask the user for consent to perform certain actions depending on the configuration of the authentication journey.

The Ping SDK for iOS provides the PlatformAuthenticatorRegistrationDelegate protocol for requesting user consent:

public protocol PlatformAuthenticatorRegistrationDelegate {
    func excludeCredentialDescriptorConsent(consentCallback: @escaping WebAuthnUserConsentCallback)
    func createNewCredentialConsent(keyName: String, rpName: String, rpId: String?, userName: String, userDisplayName: String, consentCallback: @escaping WebAuthnUserConsentCallback)
}

The SDK invokes the excludeCredentialDescriptorConsent() method when Limit registrations is enabled in the WebAuthn Registration node.

This setting prevents a device from being registered if the server has a set of matching keys already stored for it.

During registration, the server returns a list of key descriptor identifiers that the SDK compares with its stored keys. If there is a match, you must get consent from the user to generate a new set of identifiers without explaining the reason, which is they already exist.

For more information, refer to section (6.3.2.3) in the WebAuthn specification.

The following example shows how to request consent:

func excludeCredentialDescriptorConsent(consentCallback: @escaping WebAuthnUserConsentCallback) {
    let alert = UIAlertController(title: "Create Credentials", message: nil, preferredStyle: .alert)
    let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: { (_) in
        consentCallback(.reject)
    })
    let allowAction = UIAlertAction(title: "Allow", style: .default) { (_) in
        consentCallback(.allow)
    }
    alert.addAction(cancelAction)
    alert.addAction(allowAction)

    guard let vc = self.viewController else {
        return
    }

    DispatchQueue.main.async {
        viewController.present(alert, animated: true, completion: nil)
    }
}

If the user selects Allow, the SDK returns WebAuthnError.notAllowed. If the user selects Cancel, the SDK returns WebAuthnError.invalidState.

The SDK invokes the createNewCredentialConsent() method to obtain user consent prior to the SDK generating a key-pair.

In addition to the consent, the SDK might prompt for biometric authentication if the WebAuthn Registration node’s User verification requirement is set to PREFERRED or REQUIRED.

For more information, refer to section 6.3.2.6 in the WebAuthn specification.

The following example shows how to request consent:

func createNewCredentialConsent(
    keyName: String,
    rpName: String,
    rpId: String?,
    userName: String,
    userDisplayName: String,
    consentCallback: @escaping WebAuthnUserConsentCallback)
    {
        let alert = UIAlertController(
            title: "Create Credentials",
            message: "KeyName: \(keyName) | Relying Party Name: \(rpName) | User Name: \(userName)",
            preferredStyle: .alert)

        let cancelAction = UIAlertAction(
            title: "Cancel",
            style: .cancel,
            handler: { (_) in
                consentCallback(.reject)
        })

        let allowAction = UIAlertAction(
            title: "Allow",
            style: .default) { (_) in
                consentCallback(.allow)
        }
        alert.addAction(cancelAction)
        alert.addAction(allowAction)

        guard let vc = self.viewController else {
            return
        }

        DispatchQueue.main.async {
            viewController.present(alert, animated: true, completion: nil)
        }
    }

If the user selects Allow, the SDK creates the key pair and performs the attestation. If the user selects Cancel, the SDK returns WebAuthnError.cancelled.

Authenticate by using a WebAuthn device

After the user’s mobile device has been registered in PingAM, the device can be used as an authenticator with its registered key pair through the WebAuthn Authentication node, which is returned as a WebAuthnAuthenticationCallback by the Ping SDK for iOS.

If the device supports Passkeys, the operating system displays passkeys that can be used:

ios select passkey en
Figure 37. Select a passkey to use for WebAuthn

Note that removing credentials stored on the client device does not remove the associated data from the server. You will need to register the device again after removing credentials from the client.

With WebAuthnAuthenticationCallback, you must implement the following protocol method to handle the authentication process:

public protocol PlatformAuthenticatorAuthenticationDelegate {
    func selectCredential(keyNames: [String], selectionCallback: @escaping WebAuthnCredentialsSelectionCallback)
}

As part of authentication process, the SDK provides the WebAuthnAuthenticationCallback for authenticating the device as a credential.

if let authenticationCallback = callback as? WebAuthnAuthenticationCallback {

    authenticationCallback.delegate = self

    // Note that the `Node` parameter in `.authenticate()` is an optional parameter.
    // If the node is provided, the SDK automatically returns the assertion
    // in the HiddenValueCallback.

    authenticationCallback.authenticate(
        node: node,
        window: UIApplication.shared.windows.first,
        preferImmediatelyAvailableCredentials: false,
        usePasskeysIfAvailable: true
    ) { (assertion) in
        // Authentication is successful
        // Submit the Node using Node.next()
    } onError: { (error) in
        // An error occurred during the authentication process
        // Submit the Node using Node.next()
    }
}

Set the usePasskeysIfAvailable parameter to true to enable passkeys on supported devices.

When passkeys are enabled, the device offers the ability to sign in using passkeys stored an a separate supported device, by first scanning a QR code.

ios passkeys from another device en
Figure 38. Use a stored passkey from another device by first scanning the QR code

To prevent this behavior and only accept passkeys stored on the initial client device, set the preferImmediatelyAvailableCredentials parameter to true.

The WebAuthnAuthenticationCallback.authenticate() method has an optional parameter, Node.

If the current node contains both WebAuthnAuthenticationCallback and HiddenValueCallback callbacks, and this node is passed as a parameter to the WebAuthnAuthenticationCallback.authenticate() method, then the SDK automatically returns the outcome of the authentication process for both success and failure into the designated HiddenValueCallback.

If the node is not provided, the assertion or error outcome must be set manually.

Select credentials

The func selectCredential() method is invoked when Username from device is enabled in the WebAuthn Authentication node. This feature requires that Username to device is enabled in the WebAuthn Registration node as well. With these options enabled, the registered key pair is associated with the username, and the SDK can present a list of registered keys to the user to continue the authentication process without collecting a username.

The keyName is an array of strings constructed as <User’s displayName> <Registered Timestamp>.

You may alter the string value, and present the altered value to the user, but you must return the key name string as it was provided in the original array.

func selectCredential(keyNames: [String], selectionCallback: @escaping WebAuthnCredentialsSelectionCallback) {
    let actionSheet = UIAlertController(title: "Select Credentials", message: nil, preferredStyle: .actionSheet)

    for keyName in keyNames {
        actionSheet.addAction(UIAlertAction(title: keyName, style: .default, handler: { (action) in
            selectionCallback(keyName)
        }))
    }

    actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (action) in
        selectionCallback(nil)
    }))

    guard let vc = self.viewController else {
        return
    }

    if actionSheet.popoverPresentationController != nil {
        actionSheet.popoverPresentationController?.sourceView = self
        actionSheet.popoverPresentationController?.sourceRect = self.bounds
    }

    DispatchQueue.main.async {
        viewController.present(actionSheet, animated: true, completion: nil)
    }
}

Error handling

When an error occurs during the registration or authentication process, the Ping SDK for iOS returns the following error. In most cases, errors are returned as per the specification:

public enum WebAuthnError: FRError {
    case badData
    case badOperation
    case invalidState
    case constraint
    case cancelled
    case timeout
    case notAllowed
    case unsupported
    case unknown
}

Error handling with PingAM node outcome

When you use WebAuthnRegistrationCallback.register() or WebAuthnAuthenticationCallback.authenticate(), the SDK automatically parses the error into the appropriate format for PingAM. When PingAM receives the node, the authentication flow follows the WebAuthn registration process to reach the appropriate outcome.

However, if the error has to be handled manually, the WebAuthnError provides a convenience method called WebAuthnError.convertToWebAuthnOutcome() to convert the error into an appropriate format.

WebAuthnError.unsupported results in Unsupported outcome in both the WebAuthn Registration and WebAuthn Authentication nodes.

Any other WebAuthnError results in a Client Error outcome in the nodes.

// keep HiddenValueCallback to set the value later
let hiddenValueCallback = HiddenValueCallback
if let registrationCallback = callback as? WebAuthnRegistrationCallback {
    //  Perform registration
    registrationCallback.register { (attestation) in
        // Registration successful

        // only if HiddenValueCallback is designated for WebAuthn outcome
        if hiddenValueCallback.isWebAuthnOutcome {
            // set attestation manually
            hiddenValueCallback.setValue(attestation)
        }
        // Submit the Node using Node.next()
    } onError: { (error) in
        // only if HiddenValueCallback is designated for WebAuthn outcome
        // and, the error is WebAuthnError
        if hiddenValue.isWebAuthnOutcome, let webAuthnError = error as? WebAuthnError {
            // set error outcome manually
            hiddenValueCallback.setValue(webAuthnError.convertToWebAuthnOutcome())
        }
        // Submit the Node using Node.next()
    }
}

Unregister a WebAuthn device

To unregister a WebAuthn device from a user’s profile, use the deleteCredential function in your application. The function requires the publicKeyCredentialSource as a parameter.

Use the loadAllCredentials method and pass in the relying party identifier (rpId) string to return an array of publicKeyCredentialSource values. The rpId string must match the configuration you used when you configured the authentication journeys earlier.

You can only remove a device if it has the username embedded in the profile.

You must enable the Username to Device option in the WebAuthn Registration node to be able to remove the device from a user’s profile on the server using the SDKs.

The SDK attempts to delete the record of the device from the server. If that succeeds, it will then remove the local keys held by the client device. If it fails to remove the records from the server, it will not remove the local keys by default.

However, you can pass the forceDelete: true boolean parameter to the function to delete the local keys even if the call to the server fails.

let rpId = "openam-docs.forgeblocks.com"

if let credentialSource = FRWebAuthn.loadAllCredentials(
  by: rpId
).first {
  try? FRWebAuthn.deleteCredential(
    publicKeyCredentialSource: credentialSource,
    forceDelete: true
  )
}

Removing keys from either the server or the device means you will need to register it again for WebAuthn journeys. Refer to Register a WebAuthn device.

Web biometrics

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

What are web biometrics?

Web biometrics let users authenticate by using an authenticator device. For example:

  • The fingerprint scanner on a laptop or phone.

  • Face ID.

Communication with the authentication devices is handled by the SDK. You can configure the nodes in PingAM to request that the SDK activates authenticators with certain criteria. For example, the authenticator can be:

  • Built into the platform.

  • A cross-platform USB device.

  • Bluetooth.

  • NFC.

You can also configure PingAM to request that the device verify the identity of the user, or that a user is present.

To use web biometrics, users must first register their authenticators. If recovery codes are enabled, users must also make a copy of their codes.

Registration involves the selected authenticator creating or minting, a key pair. The key pair is specific to the origin of the site that uses it. This helps fight against phishing attacks.

The public key of the pair is sent to PingAM and stored in the user’s profile. The private key is stored securely, either in the authenticator itself or in the platform managing the authenticators. The private key does not leave the client at any time.

When authenticating using web biometrics, PingAM sends a challenge to the authenticator, expecting it to use this challenge to create a signed assertion with its stored, private key.

The assertion is then sent to PingAM for verification using the public key stored in the user’s profile. If the data is verified as being from the correct device, and passes any attestation checks, the authentication is successful.

Differences between web biometrics and device binding

There are many similarities between WebAuthn and Device Binding and JWS verification. We provide authentication nodes to implement both technologies in your journeys.

Both can be used for usernameless and passwordless authentication, they both use public key cryptography, and both can be used as part of a multi-factor authentication journey.

One major difference is that with device binding, the private key never leaves the device.

With WebAuthn, there is a possibility that the private key is synchronized across client devices because of Passkey support, which may be undesirable for your organization.

For more details of the differences, refer to the following table:

Comparison of WebAuthn and Device Binding/JWS Verification
Feature WebAuthn / FIDO Device Binding / JWS Verifier Details

Industry-standards based

You can refer to the WebAuthn W3C specification.

Device binding and JWS verification are proprietary implementations.

Public key cryptography

Both methods use Public key cryptography.

Usernameless support

After registration, the username can be stored in the device and obtained during authentication without the user having to enter their credentials.

Keys are bound to the device

With WebAuthn, if Passkeys are used, they can be shared across devices.

With device binding, the private keys do not leave the device.

Sign custom data

With device binding, you can:

  • Customize the challenge that the device must sign. For example, you could include details of a transaction, such as the amount in dollars.

  • Add custom claims to the payload when signing a challenge. This gives additional context that the server can make use of by using a scripted node.

Format of signed data

WebAuthn authenticator data

JSON Web Signature (JWS)

Integration

With device binding, after verification, the signed JWT is available in:

  • Audit Logs

  • Transient node state

This enables the data within to be used for integration into your processes and business logic.

Platform support

✅ Android

✅ iOS

✅ Web browsers

✅ Android

✅ iOS

❌ Web browsers

As it is challenging to store secure data in a browser as a client app, device binding is not supported in web browsers.

Authenticator support

Determined by the platform.

Configuration limited to:

  • Biometric with Fallback to Device Pin

Determined by the authentication node.

Full configuration options:

  • Biometric Authentication

  • Biometric with Fallback to Device Pin

  • Application Pin

  • Silent

With device binding, you can specify what authentication action the user must perform to get access to the private keys.

This provides greater flexibility in your security implementation and can reduce authentication friction for your users.

Key storage

Web browsers and iOS synchronize to the cloud.

Android has the option to synchronize to the cloud.

Android

KeyStore

iOS

Secure enclave: hardware-backed and not synchronized to the cloud.

Both technologies store the private keys securely on the client.

WebAuthn supports synchronizing the private keys to the cloud for use on other devices.

This can reduce authentication friction for your users but may also increase the risk of a breach.

Managing device keys

Managed by the device OS.

Apps cannot delete local client keys programmatically and do not have a reference to the remote server key for deletion.

Managed by the Ping SDKs.

Provides an interface to delete local client and remote server keys.

The ability to programmatically delete both client and server keys can greatly simplify the process of registering a new device if an old device is lost or stolen.

Passkey Support

WebAuthn supports synchronizing the private keys to the cloud for use on other devices.

Device binding keeps the private key locked in the device.

App integrity verification

Android

Requires an assetlinks.json file.

iOS

Requires apple-app-site-association file.

Not provided by the device binding or verification nodes.

It can be added as part of the journey by using app integrity nodes.

App integrity verification helps ensure your users are only using a supported app rather than a third-party or potentially malicious version.

Key attestation

Android

SafteyNet

iOS

None

Android

Uses hardware-backed key pairs with Key Attestation.

iOS

It can be added as part of the journey by using app integrity nodes to support key attestation.

Key attestation verifies that the private key is valid and correct, is not forged, and was not created in an insecure manner.

Complexity

Medium

Low

WebAuthn requires a bit more configuration, for example, creating and uploading the assetlinks.json and apple-app-site-association files.

Device binding only requires the journey and the SDK built into your app.

Before using web biometrics

  • The device must have a biometric (platform) authenticator.

  • The OS must provide access to the platform authenticator via the API.

  • The browser must support WebAuthn capabilities, and support the OS’s platform authenticator’s API.

If any of the above prerequisites is missing, web biometrics will not work.

For more information on support in PingAM, see Minimum Web Authentication User Agent Versions in the PingAM documentation.

Prepare for web biometrics

You must create an authentication journey in PingAM to authenticate users by using a web biometrics device. The journey also allows users to register a device if they have not already done so.

For more information, see Creating Trees for Web Authentication (WebAuthn) in the PingAM documentation.

Handle web biometrics

The Ping SDK for JavaScript has the following methods for handling web biometrics:

FRWebAuthn.register(step, deviceName)

For registering new devices. Optionally, assign a name to the device to help the user identify it. If you do not provide a custom name, the server assigns a generic value such as New Security Key.

FRWebAuthn.authenticate(step)

For authenticating using a previously registered device.

Use the FRWebAuthn.getWebAuthnStepType() convenience method to determine which method to use:

// Determine if a step is a web biometrics step
const stepType = FRWebAuthn.getWebAuthnStepType(step);

if (stepType === WebAuthnStepType.Registration) {
  // Registering a new device, with optional device name
  step = await FRWebAuthn.register(step, 'myDeviceName');
} else if (stepType === WebAuthnStepType.Authentication) {
  // Authenticating with a registered device
  step = await FRWebAuthn.authenticate(step);
}

// `step` has now been populated with the web biometrics credentials

// Send this new step to the {fr_server}
nextStep = FRAuth.next(step);

Using the device name

The device name is available for display in other callbacks received from a journey.

For example, you can get the device name when displaying recovery codes as follows:

// Determine if step is a display recovery codes step
const isDisplayRecoveryCodesStep = FRRecoveryCodes.isDisplayStep(step);

if (isDisplayRecoveryCodesStep) {
  // Obtain recovery codes
  const recoveryCodes = FRRecoveryCodes.getCodes(step);

  // Obtain device display name
  const deviceName = FRRecoveryCodes.getDisplayName(step);

  // Display `recoveryCodes` and `deviceName` to the user
}
The Ping (ForgeRock) Login Widget has built-in support and associated UI for displaying the device name alongside the recovery codes.

Set up passwordless authentication with passkeys

What is passwordless?

Passwordless authentication is the term used to describe a group of identity verification methods that don’t rely on users entering passwords. There are many ways to go passwordless, such as supporting biometrics, hardware security keys, or the use of specialized mobile applications like authenticators that can all provide a secure alternative to inputting a password and sending it over the network.

We offer extensive support for passwordless, including OTPs either via email or SMS, an authenticator application, push notifications with number challenges and biometric unlock, magic links, WebAuthn, and more.

All these methods can be used in passwordless scenarios or as additional factors of authentication (2FA/MFA) to secure your systems further. Some of these require an authenticator application, such as the ForgeRock Authenticator, while others just on existing channels like email or SMS. Using the Ping SDKs, developers can include the functionality of the ForgeRock Authenticator within their own applications.

In this blog post, we focus more on using biometrics for going passwordless. The Ping SDKs support the WebAuthn protocol, offering out-of-the-box nodes in both PingAM and PingOne Advanced Identity Cloud. Furthermore, using the SDKs, developers can utilize the power of passkeys on every supported platform.

Biometrics and WebAuthn

What are these technologies, and how can we use them? Let’s dive a bit deeper into that.

WebAuthn is an abbreviation of Web Authentication. It is a specification issued by W3C. It specifies a set of interfaces for browsers and apps to implement.

To use the WebAuthn protocol, the user requires access to a strong authenticator. Newer laptops and most Android and iOS mobile devices include biometric sensors that can be used for this. Those biometric scanners, more commonly known by their marketing names such as FaceID or TouchID, are used to register the user’s biometric data with the mobile operating system. They can be used to unlock the device itself, unlock information stored in the secure storage, and more.

WebAuthn requires two distinct ceremonies:

Registration

During registration, the device (called "authenticator") generates a cryptographic keypair. The public key is sent to the server, and the private key is safely stored locally.

Authentication

During authentication, the server asks the client to sign a message with a nonce (called "challenge") with its private key.

This signed message with the nonce can then be verified by the server using the public key obtained through registration.

This has really strong security properties because the private key is hardware bound and never leaves the device. The signing of the message is done directly by the authenticator, the device, and protected by some form of local user verification (PIN or Biometrics).

Differences between WebAuthn keys and Passkeys

So, what is the difference between WebAuthn keys, as described above, and passkeys? Until now, the private key created during the registration process was stored on the device. This has one shortcoming; if the user changes the device or loses it, they cannot authenticate again. Moreover, the server needs to allow for registration of more than one key if users have multiple devices for authenticating to a website or service.

Apple, Google, and Microsoft chose passkeys, an implementation of WebAuthn with the additional feature of storing the user’s private keys in their respective cloud services. That means that those "passkeys" are available to use on all the devices logged in to the same cloud account.

This means Apple, Google, and Microsoft are responsible for keeping the user’s private keys safe. Also, it is up to the user to ensure their account on these providers is secure by using strong passwords, MFA, and so on.

Although this makes the attack vector broader, this way of handling keys makes the whole passwordless experience more accessible and, therefore, more likely to be used by the everyday user. Additionally, it makes account recovery due to a single lost device a thing of the past.

Differences between WebAuthn and Device Binding

There are many similarities between WebAuthn and Device Binding and JWS verification. We provide authentication nodes to implement both technologies in your journeys.

Both can be used for usernameless and passwordless authentication, they both use public key cryptography, and both can be used as part of a multi-factor authentication journey.

One major difference is that with device binding, the private key never leaves the device.

With WebAuthn, there is a possibility that the private key is synchronized across client devices because of Passkey support, which may be undesirable for your organization.

For more details of the differences, refer to the following table:

Comparison of WebAuthn and Device Binding/JWS Verification
Feature WebAuthn / FIDO Device Binding / JWS Verifier Details

Industry-standards based

You can refer to the WebAuthn W3C specification.

Device binding and JWS verification are proprietary implementations.

Public key cryptography

Both methods use Public key cryptography.

Usernameless support

After registration, the username can be stored in the device and obtained during authentication without the user having to enter their credentials.

Keys are bound to the device

With WebAuthn, if Passkeys are used, they can be shared across devices.

With device binding, the private keys do not leave the device.

Sign custom data

With device binding, you can:

  • Customize the challenge that the device must sign. For example, you could include details of a transaction, such as the amount in dollars.

  • Add custom claims to the payload when signing a challenge. This gives additional context that the server can make use of by using a scripted node.

Format of signed data

WebAuthn authenticator data

JSON Web Signature (JWS)

Integration

With device binding, after verification, the signed JWT is available in:

  • Audit Logs

  • Transient node state

This enables the data within to be used for integration into your processes and business logic.

Platform support

✅ Android

✅ iOS

✅ Web browsers

✅ Android

✅ iOS

❌ Web browsers

As it is challenging to store secure data in a browser as a client app, device binding is not supported in web browsers.

Authenticator support

Determined by the platform.

Configuration limited to:

  • Biometric with Fallback to Device Pin

Determined by the authentication node.

Full configuration options:

  • Biometric Authentication

  • Biometric with Fallback to Device Pin

  • Application Pin

  • Silent

With device binding, you can specify what authentication action the user must perform to get access to the private keys.

This provides greater flexibility in your security implementation and can reduce authentication friction for your users.

Key storage

Web browsers and iOS synchronize to the cloud.

Android has the option to synchronize to the cloud.

Android

KeyStore

iOS

Secure enclave: hardware-backed and not synchronized to the cloud.

Both technologies store the private keys securely on the client.

WebAuthn supports synchronizing the private keys to the cloud for use on other devices.

This can reduce authentication friction for your users but may also increase the risk of a breach.

Managing device keys

Managed by the device OS.

Apps cannot delete local client keys programmatically and do not have a reference to the remote server key for deletion.

Managed by the Ping SDKs.

Provides an interface to delete local client and remote server keys.

The ability to programmatically delete both client and server keys can greatly simplify the process of registering a new device if an old device is lost or stolen.

Passkey Support

WebAuthn supports synchronizing the private keys to the cloud for use on other devices.

Device binding keeps the private key locked in the device.

App integrity verification

Android

Requires an assetlinks.json file.

iOS

Requires apple-app-site-association file.

Not provided by the device binding or verification nodes.

It can be added as part of the journey by using app integrity nodes.

App integrity verification helps ensure your users are only using a supported app rather than a third-party or potentially malicious version.

Key attestation

Android

SafteyNet

iOS

None

Android

Uses hardware-backed key pairs with Key Attestation.

iOS

It can be added as part of the journey by using app integrity nodes to support key attestation.

Key attestation verifies that the private key is valid and correct, is not forged, and was not created in an insecure manner.

Complexity

Medium

Low

WebAuthn requires a bit more configuration, for example, creating and uploading the assetlinks.json and apple-app-site-association files.

Device binding only requires the journey and the SDK built into your app.

How to implement Passkeys using Ping SDKs

The first step is having access to a PingAM instance or a PingOne Advanced Identity Cloud tenant, as well as an existing application that uses these for authentication. In this example, we use PingOne Advanced Identity Cloud.

Download the sample app

We provide a sample app we’ve built that implements authentication using the Ping SDK for iOS. You can download the full iOS project from the SDK Sample Apps repo on GitHub.

Create WebAuthn registration and authentication journeys

In PingOne Advanced Identity Cloud, we will create new journeys for both WebAuthn device registration and authentication.

To speed up the process of creating the required journeys, we provide a pre-configured JSON file that you can import into your ID Cloud tenant.

This automatically creates both of the required journeys, as well as the scripts you require in the scripted nodes.

To import the JSON file, in the PingOne Advanced Identity Cloud admin UI, go to Journeys, and then click Import.

After successfully importing the journeys into your tenant, skip ahead and Configure the WebAuthn nodes in the journeys.

For more information on importing journeys, refer to Import journeys.

Create the registration journey manually

If you decided not to import the journey file into your tenant, you need to create the journey manually.

Using the Journey editor, create a new journey in the alpha realm and name it BlogWebAuthnRegistration. Then, drag the following nodes from the list and connect them as displayed on the screenshot below:

  • Four scripted Decision nodes

  • WebAuthn Registration Node

  • Get Session Data Node

  • Username Collector Node

  • Password Collector Node

  • Data Store Decision Node

The `BlogWebAuthnRegistration` journey
Figure 39. The BlogWebAuthnRegistration journey

We need to assign the scripts for the scripted decision nodes. First, setUUIDtoDisplayName, and second, WebAuthnErrorHandler. The first one ensures the creation of a user-friendly name for our passkey, and the second allows developers to handle the WebAuthn client error cases in more detail.

Source for the userUUIDtoDisplayName script
var user = nodeState.get('username').asString();
nodeState.putShared('displayName', user.toString());

outcome = 'true';
Source for the WebAuthnErrorHandler script
// Error format example:
// ERROR::InvalidStateError:No Credential is registered

var error = sharedState.get("WebAuthenticationDOMException");
logger.message(error);

// Match word or phrase between "::" and ":"
var result = error.match(/::([\w\s]{1,}):{0,}/);
outcome = result ? result[1] : 'UnknownError';

logger.message("Outcome: " + outcome + "| ERROR: " + error);

Next, we set up the "HasSession" and "SharedStateHasUsername" scripts.

Source for the HasSession script
if (typeof existingSession !== 'undefined') {
  outcome = "hasSession";
} else {
  outcome = "noSession";
}
Source for the SharedStateHasUsername script
var user = nodeState.get('username');

if (user != null) {
  outcome = 'true';
} else {
  outcome = 'false';
}
Create the authentication journey manually

If you decided not to import the journey file into your tenant, you need to create the journey manually.

Using the journey editor, create a new journey named BlogWebAuthnAuthentication. For this journey, use the following nodes:

  • Scripted Decision node

  • WebAuthn Authentication node

  • Inner Tree Evaluator node (this calls the BlogWebAuthnRegistration you created previously)

Connect them as follows:

The `BlogWebAuthnAuthentication` journey
Figure 40. The BlogWebAuthnAuthentication journey
Configure the WebAuthn nodes in the journeys

You must configure the WebAuthn Registration node and the WebAuthn Authentication node in the new journeys with values matching your environment.

Open each newly created tree and configure the WebAuthn nodes within to use identical configuration.

The important configuration options in this case are the following fields:

Relying party identifier

This needs to be set to the domain that will be the "Relying Party" for the registration. Set this to the domain name of your tenant. If you are using custom domains, set this to match the custom domain configured for the realm.

Origin domains

This needs to be set to the origin of the application that registers passkeys.

For iOS and Android, this involves special configuration depending on the platform and whether you use plain WebAuthn keys or passkeys.

For more information, refer to the following:

When implementing passkeys, set this to the origin that serves the apple-app-site-association file.

Return challenge as JavaScript

Ensure this is NOT enabled.

Shared state attribute for display name

Set to displayName as indicated by the script above.

Lastly, in order to allow the application to register and authenticate against the server using passkeys, we need to configure and upload the apple-app-site-association file.

For more details on how to do this in PingOne Advanced Identity Cloud, refer to Prepare an apple-app-site-association file.

With both journeys configured, the server is able to register a device for passkeys and authentication.

In the BlogWebAuthnAuthentication journey, you will notice that if the authentication step fails, the user proceeds to the registration step automatically. This is acceptable based on the requirements for the scope we have in this blogpost, but in other scenarios allowing the user to authenticate with other means such as a password or OTP is advisable.

The BlogWebAuthnRegistration journey is built in a way that allows applications to call it directly when a user session exists or call it internally from another journey.

Test the journeys in a browser

Using the out-of-the-box platform user interface you can test the functionality in a browser. Start by copying the Preview URL from the journey editor for the BlogWebAuthnAuthentication journey.

Running for the first time, the flow should look something like this:

Registering a new passkey on the first attempt to authenticate
Figure 41. Registering a new passkey on the first attempt to authenticate

In subsequent authentication attempts, you are able to authenticate using your newly created passkey:

Using the new passkey on a subsequent attempt to authenticate
Figure 42. Using the new passkey on a subsequent attempt to authenticate

Using Passkeys with the Ping SDK for iOS

At this point it is advisable to download the complete project from GitHub. Open the project in Xcode and have a look at the LoginViewController and SettingsViewController class. The logic described below can be found in those two controllers. If you have an existing project using the Ping SDK, the code should look familiar. This tutorial focuses on the logic regarding passkey (WebAuthn) authentication and registration.

Add support for the callbacks

In order to use passkeys with the Ping SDK for Android or iOS, developers need to handle the WebAuthnAuthentication and WebAuthnRegistration callbacks.

The first node the app needs to handle on the authentication journey is the NameCallback from the username node. We assume your iOS application already handles basic authentication with username and password, so we expect this to be implemented.

The first new callback to be handled is the WebAuthnAuthentication callback. In the handleNode method add some code to do so:

Handling the WebAuthnAuthentication callback
else if let authenticationCallback = callback as? WebAuthnAuthenticationCallback {
  authenticationCallback.delegate = self
  ...
  ...
}

In order to start the WebAuthn authentication flow, we need to call the authenticationCallback.authenticate() method:

Starting the WebAuthn flow with Passkey support
authenticationCallback.authenticate(node: node, preferImmediatelyAvailableCredentials: false, usePasskeysIfAvailable: self.usePasskeysIfAvailable) { (assertion) in
     // Authentication is successful
     // Submit the Node using Node.next()
     node.next { (user: FRUser?, node, error) in
           self.handleNode(user: user, node: node, error: error)
     }
  } onError: { (error) in
     // An error occurred during the authentication process
     // Submit the Node using Node.next()
     node.next { (user: FRUser?, node, error) in
           self.handleNode(user: user, node: node, error: error)
     }
  }

The full code should look something like this:

Code for handling the authentication journey callbacks
if let authenticationCallback = callback as? WebAuthnAuthenticationCallback {
  authenticationCallback.delegate = self

  // Note that the `Node` parameter in `.authenticate()` is an optional parameter.
  // If the node is provided, the SDK automatically sets the assertion to the designated HiddenValueCallback
  authenticationCallback.authenticate(
    node: node,
    usePasskeysIfAvailable: PebbleBankUtilities.usePasskeysIfAvailable
  ) { (assertion) in
    // Authentication is successful
    // Submit the Node using Node.next()
    node.next { (token: Token?, node, error) in
      self.handleNode(token: token, node: node, error: error)
    }
  } onError: { (error) in
    // An error occurred during the authentication process
    // Submit the Node using Node.next()
    let alert = UIAlertController(
      title: "WebAuthnError",
      message: "Something went wrong authenticating the device",
      preferredStyle: .alert
    )
    let okAction = UIAlertAction(
      title: "OK",
      style: .default,
      handler: { (action) in
        node.next { (token: Token?, node, error) in
          self.handleNode(token: token, node: node, error: error)
        }
      }
    )
    alert.addAction(okAction)
    DispatchQueue.main.async {
      self.present(alert, animated: true, completion: nil)
    }
  }
}

In a similar way, we need to add support for the WebAuthnRegistration callbacks.

Code for handling the registration journey callbacks
if let registrationCallback = callback as? WebAuthnRegistrationCallback {
  registrationCallback.delegate = self

  // Note that the `Node` parameter in `.register()` is an optional parameter.
  // If the node is provided, the SDK automatically sets the error outcome or attestation to the designated HiddenValueCallback
  registrationCallback.register(
    node: node,
    deviceName: UIDevice.current.name,
    usePasskeysIfAvailable: PebbleBankUtilities.usePasskeysIfAvailable
  ) { (attestation) in
    // Registration is successful
    // Submit the Node using Node.next()
    node.next { (token: Token?, node, error) in
      self.handleNode(token: token, node: node, error: error)
    }
  } onError: { (error) in
    // An error occurred during the registration process
    // Submit the Node using Node.next()
    let alert = UIAlertController(
      title: "WebAuthnError",
      message: "Something went wrong registering the device",
      preferredStyle: .alert
    )
    let okAction = UIAlertAction(
      title: "OK",
      style: .default,
      handler: { (action) in
        node.next { (token: Token?, node, error) in
          self.handleNode(token: token, node: node, error: error)
        }
      }
    )
    alert.addAction(okAction)
    DispatchQueue.main.async {
      self.present(alert, animated: true, completion: nil)
    }
  }
}

The application can now handle the callbacks returned by each of the nodes that appear on the journey. The full list of expected callbacks is as follows:

  • NameCallback

  • PasswordCallback

  • WebAuthnAuthenticationCallback

  • WebAuthnRegistrationCallback

Furthermore, we allow the iOS application to call different journeys based on the situation. For example, when the users haven’t registered for biometrics, the app calls the default Login journey. When the user has followed the BlogWebAuthnRegistration journey and has registered for biometrics, the app uses the BlogWebAuthnAuthentication journey for authentication.

The sample app has been implemented to call the BlogWebAuthnRegistration journey directly from its settings screen.

This allows the user to register the device for biometrics after successfully authenticating.

Call the journeys

When using the SDKs, we can call a journey directly by using the FRSession.authenticate method. In order to call the passkey registration journey, we can use the following code:

Calling the passkey registration journey
FRSession.authenticate(authIndexValue: "BlogWebAuthnRegistration") { result, node, error in
    self.handleNode(token: result, node: node, error: error)
}

In order to call the passkey authentication journey:

Calling the passkey authentication journey
FRSession.authenticate(authIndexValue: "BlogWebAuthnAuthentication") { result, node, error in
       self.handleNode(token: result, node: node, error: error)
}

In the iOS app, upon successful completion of the BlogWebAuthnRegistration journey, the SDK saves a flag on the iOS device (in UserDefaults) noting that this device is now registered with a passkey.

This client side logic allows us to swap to a passkey authentication journey as the main way of authenticating from this device.

Configure the project

With the sample project open, select the PebbleBankUtilities file. This file contains the SDK configuration options. Configure these to point to your environment.

Additionally, this file contains the ForceAuthInterceptorBiometricRegistration request interceptor. When using the SDK, developers have the option to create request interceptors that enrich the REST calls the SDK makes. In this case we have added the following:

  • A URL query parameter to force the use of the journey despite the presence of an existing valid session:

    ForceAuth=true

  • A header to inject the session cookie:

    [Cookie Name]: <SessionToken>

This request interceptor is only used when the app calls BlogWebRegistrationJourney, and injects the existing user session and the ForceAuth parameter.

Lastly, the Xcode project needs to be configured to allow WebCredentials based on your server configuration. We also need to create an apple-app-association file and upload it to PingOne Advanced Identity Cloud.

You can find more details on how to configure Xcode in the Apple developer docs. You can also find more details on how to configure and upload the apple-app-association file in the SDK documentation.

Test the app

With the Xcode project fully configured, we can now run and test the flow. A reminder that a complete version of this project can be found on GitHub. Complete documentation on mobile biometrics for iOS and Android can be found in the SDK documentation.

Below is a complete demonstration of the functionality using the demo app:

Complete demo of app using passkeys
Figure 43. Complete demo of app using passkeys

Summary

Building passwordless flows for users is not trivial, as they will need to register a device that will act as an authenticator, replacing the password.

Furthermore, users need to be driven down a passwordless journey by choice, or automatically if they have enabled this option in the app.

Using PingOne Advanced Identity Cloud and the Ping SDKs for Android and iOS, developers have a set of tools to make these flows as frictionless as possible and by writing minimal code.

When registering a device with passkeys to replace the traditional username and password, the following considerations should come to mind:

  • Is the user or the device registering a valid and authenticated user, or is it a bad actor attempting an account take over?

  • What happens if the user attempts to authenticate on a device that does not have the passkey? Will there be an offering for traditional username and password paths?

  • Is the flow clear and easy to understand for all users?

  • Could the use case support usernameless authentication? A step further to make this flow even smoother for end users

Passkeys are here to stay and seem to be a great stepping stone for replacing passwords. Improvements on the user experience from the operating systems and browsers are sure to come in the future.

As this post shows, using the tools provided your applications are ready to go passwordless today!

Bind and verify user devices

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

The Ping SDKs for Android and iOS can cryptographically bind a mobile device to a user account.

Registered devices generate a key pair and a key ID. The SDK sends the public key and key ID to AM for storage in the user’s profile.

The SDK stores the private key on the device in either the Android KeyStore, or the iOS Secure Enclave. Access to the private keys is protected by either biometric security or a PIN.

A user can bind multiple devices to their account, and each device can bind to multiple users.

After binding a device your authentication journeys in AM can verify ownership of the bound device by requesting that it signs a challenge using the private key.

Differences between device binding and WebAuthn

There are many similarities between WebAuthn and Device Binding and JWS verification. We provide authentication nodes to implement both technologies in your journeys.

Both can be used for usernameless and passwordless authentication, they both use public key cryptography, and both can be used as part of a multi-factor authentication journey.

One major difference is that with device binding, the private key never leaves the device.

With WebAuthn, there is a possibility that the private key is synchronized across client devices because of Passkey support, which may be undesirable for your organization.

For more details of the differences, refer to the following table:

Comparison of WebAuthn and Device Binding/JWS Verification
Feature WebAuthn / FIDO Device Binding / JWS Verifier Details

Industry-standards based

You can refer to the WebAuthn W3C specification.

Device binding and JWS verification are proprietary implementations.

Public key cryptography

Both methods use Public key cryptography.

Usernameless support

After registration, the username can be stored in the device and obtained during authentication without the user having to enter their credentials.

Keys are bound to the device

With WebAuthn, if Passkeys are used, they can be shared across devices.

With device binding, the private keys do not leave the device.

Sign custom data

With device binding, you can:

  • Customize the challenge that the device must sign. For example, you could include details of a transaction, such as the amount in dollars.

  • Add custom claims to the payload when signing a challenge. This gives additional context that the server can make use of by using a scripted node.

Format of signed data

WebAuthn authenticator data

JSON Web Signature (JWS)

Integration

With device binding, after verification, the signed JWT is available in:

  • Audit Logs

  • Transient node state

This enables the data within to be used for integration into your processes and business logic.

Platform support

✅ Android

✅ iOS

✅ Web browsers

✅ Android

✅ iOS

❌ Web browsers

As it is challenging to store secure data in a browser as a client app, device binding is not supported in web browsers.

Authenticator support

Determined by the platform.

Configuration limited to:

  • Biometric with Fallback to Device Pin

Determined by the authentication node.

Full configuration options:

  • Biometric Authentication

  • Biometric with Fallback to Device Pin

  • Application Pin

  • Silent

With device binding, you can specify what authentication action the user must perform to get access to the private keys.

This provides greater flexibility in your security implementation and can reduce authentication friction for your users.

Key storage

Web browsers and iOS synchronize to the cloud.

Android has the option to synchronize to the cloud.

Android

KeyStore

iOS

Secure enclave: hardware-backed and not synchronized to the cloud.

Both technologies store the private keys securely on the client.

WebAuthn supports synchronizing the private keys to the cloud for use on other devices.

This can reduce authentication friction for your users but may also increase the risk of a breach.

Managing device keys

Managed by the device OS.

Apps cannot delete local client keys programmatically and do not have a reference to the remote server key for deletion.

Managed by the Ping SDKs.

Provides an interface to delete local client and remote server keys.

The ability to programmatically delete both client and server keys can greatly simplify the process of registering a new device if an old device is lost or stolen.

Passkey Support

WebAuthn supports synchronizing the private keys to the cloud for use on other devices.

Device binding keeps the private key locked in the device.

App integrity verification

Android

Requires an assetlinks.json file.

iOS

Requires apple-app-site-association file.

Not provided by the device binding or verification nodes.

It can be added as part of the journey by using app integrity nodes.

App integrity verification helps ensure your users are only using a supported app rather than a third-party or potentially malicious version.

Key attestation

Android

SafteyNet

iOS

None

Android

Uses hardware-backed key pairs with Key Attestation.

iOS

It can be added as part of the journey by using app integrity nodes to support key attestation.

Key attestation verifies that the private key is valid and correct, is not forged, and was not created in an insecure manner.

Complexity

Medium

Low

WebAuthn requires a bit more configuration, for example, creating and uploading the assetlinks.json and apple-app-site-association files.

Device binding only requires the journey and the SDK built into your app.

Relevant authentication nodes and callbacks

The following table covers the authentication nodes and callbacks that AM provides for creating device binding journeys.

Node Callback Description

Device Binding node

DeviceBindingCallback

Registers a device to the user and optionally stores the public key and key ID in the user’s profile

Device Binding Storage node

Non-interactive

Stores the public key and key ID in the user’s profile if they were stored in node state

Device Signing Verifier node

DeviceSigningVerifierCallback

Verifies ownership of a device by requesting it signs a challenge and verifying the result

The SDKs support the default Authentication Type options provided by the authentication nodes. These options define how the user must authenticate on their device to gain access to the private keys stored on it:

Biometric only

Request that the client secures access to private keys with biometric security, such as a fingerprint.

Biometric with PIN fallback

Request that the client secures access to the private keys with biometric security, such as a fingerprint, but allow use of the device PIN if biometric is unavailable.

Application PIN

Request that the client secures access to the private keys with an application-specific PIN.

On Android devices, the private keys used for binding and verification are stored in a keystore file protected by the application PIN specified by the user - it does not use hardware-backed encryption. However, this keystore file is encrypted using keys from the hardware-backed AndroidKeyStore.

The application-specific PIN applies only to your app, and is not linked to the device PIN used to unlock the device.

The application-specific PIN is stored only on the client device and is not sent to AM.

If the user forgets their application-specific PIN, they must bind the device again.

None

The user does not need to authenticate to gain access to the private keys on their device.

The SDKs provide the UI to handle these application types by default. You can also override the default UI and provide your own implementations. Refer to Custom authentication UI.

Add device binding dependencies

To bind a device and perform signing verification, you must add the device binding module to your project.

Add Android dependencies

To add the device binding dependencies to your Android project:

  1. In the Project tree view of your Android Studio project, open the Gradle Scripts/build.gradle file for the module.

  2. In the dependencies section, add the required dependencies:

    Example dependencies section after editing:
    dependencies {
        implementation 'org.forgerock:forgerock-auth:4.6.0'
    
        // Device binding core dependencies
        implementation 'com.nimbusds:nimbus-jose-jwt:9.23'
        implementation 'androidx.security:security-crypto:1.0.0'
    
        // BIOMETRIC_ONLY, BIOMETRIC_WITH_FALLBACK
        implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha04'
    
        // APPLICATION_PIN
        implementation 'com.madgag.spongycastle:bcpkix-jdk15on:1.58.0.0'
    }

Add iOS dependencies

You can use CocoaPods or the Swift Package Manager to add the device binding dependencies to your iOS project.

Add dependencies using CocoaPods
  1. If you do not already have CocoaPods, install the latest version.

  2. If you do not already have a Podfile, in a terminal window run the following command to create a new Podfile:

    pod init
  3. Add the following lines to your Podfile:

    pod 'FRDeviceBinding' // Add-on for Device Binding feature
  4. Run the following command to install pods:

    pod install
Add dependencies using Swift Package Manager
  1. With your project open in Xcode, select File > Add Package Dependencies.

  2. In the search bar, enter the Ping SDK for iOS repository URL: https://github.com/ForgeRock/forgerock-ios-sdk.

  3. Select the forgerock-ios-sdk package, and then click Add Package.

  4. In the Choose Package Products dialog, ensure that the FRDeviceBinding library is added to your target project.

  5. Click Add Package.

  6. In your project, import the library:

    // Import the library
    import FRDeviceBinding

Handle device binding callbacks

To bind a device on receipt of a DeviceBindingCallback, use the DeviceBindingCallback.bind() function.

This binds the device to the account using the default implementation.

Examples

  • Android - Java

  • Android - Kotlin

  • iOS - Swift

DeviceBindingCallback callback = node.getCallback(DeviceBindingCallback.class);
callback.setDeviceName("My Android Device");
callback.bind(this.getActivity(), new FRListener<Void>() {
    @Override
    public void onSuccess(Void result) {
        // Proceed to the next node
        node.next();
    }

    @Override
    public void onException(Exception e) {
        // Proceed to the next node
        node.next();
    }
});
try {
    // Provide a friendly name for the device
    callback.setDeviceName("My Android Device")
    // Bind the device
    callback.bind(context)
    // Proceed to the next node
} catch (e: CancellationException) {
    // Ignore, due to configuration change
} catch (e: DeviceBindingException) {
    // Proceed to the next node
}
// Provide a friendly name for the device
callback.setDeviceName("My iOS Device")

// Bind the device
callback.bind() { result in
    switch result {
        case .success:
            // Proceed to the next node
        case .failure(let error):
            // Handle the error and proceed to the next node
    }
}
The examples above use the default user interface for authenticating users in order to create and securely store private keys. For information on providing your own UI for authenticating access to the private keys, refer to Implement custom UI.

Handle device signing verifier callbacks

To sign the challenge on receipt of a DeviceSigningVerifierCallback, use the DeviceBindingCallback.sign() function.

Examples

  • Android - Java

  • Android - Kotlin

  • iOS - Swift

callback.sign(requireContext(), new FRListener<Void>() {
    @Override
    public void onSuccess(Void result) {
        // Proceed to the next node
    }

    @Override
    public void onException(Exception e) {
        // Proceed to the next node
    }
});
try {
    callback.sign(context)
    // Proceed to the next node
} catch (e: CancellationException) {
    // Ignore, due to configuration change
} catch (e: DeviceBindingException) {
    // Map custom client errors:
    when (e.status) {
        is UnRegister -> {
            callback.setClientError("UnReg")
        }
        is UnAuthorize -> {
            callback.setClientError("UnAuth")
        }
    }
    // Proceed to the next node
}
callback.sign() { result in
    switch result {
        case .success:
            // Proceed to the next node
        case .failure(let error):
            // Handle the error and proceed to the next node
    }
}
The examples above use the default user interface for authenticating users in order to access the private keys. For information on providing your own UI for authenticating access to the private keys, refer to Implement custom UI.

Add custom claims when signing

When signing a challenge on receipt of a DeviceSigningVerifierCallback, you can also add custom claims to the payload to provide additional context to the server.

A script in your authentication journey can access these claims and use them to implement additional functionality or logic.

The Device Signing Verifier node places the contents of the signed JWT in shared state in a variable named DeviceSigningVerifierNode.JWT.

Examples

  • Android - Java

  • Android - Kotlin

  • iOS - Swift

Map<String, String> customClaims  = new HashMap<>() {{
    put("os", "value1");
}};

callback.sign(context, customClaims, new FRListener<Void>() {
    @Override
    public void onSuccess(Void result) {
        // Proceed to the next node
    }

    @Override
    public void onException(Exception e) {
        // Check for DeviceBindingErrorStatus.InvalidCustomClaims status
        // and fix invalid custom claims if needed
        // Proceed to the next node
    }
});
try {
    callback.sign(context, mapOf("os" to "android"))
    // Proceed to the next node
} catch (e: CancellationException) {
    // Ignore, due to configuration change
} catch (e: DeviceBindingException) {
    // Map custom client errors:
    when (e.status) {
        is UnRegister -> {
            callback.setClientError("UnReg")
        }
        is UnAuthorize -> {
            callback.setClientError("UnAuth")
        }
        is DeviceBindingErrorStatus.InvalidCustomClaims -> {
          // Fix the invalid custom claims
        }
    }
    // Proceed to the next node
}
callback.sign(
  customClaims: [
    "platform": "iOS",
    "isCompanyPhone": true,
    "lastUpdated": Int(Date().timeIntervalSince1970)
  ]
) { result in
      switch result
      {
          case .success:
              // Proceed to the next node
          case .failure(let error):
              // Handle the error and proceed to the next node
              if error == .invalidCustomClaims {
                  // Fix the invalid custom claims
                  print(error.errorMessage)
                  return
              }
      }
  }

Unbind devices by deleting keys

Registered devices store a public key and key ID on the AM server, and the private key in either the Android KeyStore or the iOS Secure Enclave.

To completely unbind a device from a user, you must delete the keys from both the client device and the server.

The following table outlines scenarios where the client deletes the local keys:

Scenario Android Device iOS Device

User deletes the client application

Local key is deleted

Local key is NOT deleted.

Reinstall an app with the same AppID and signature to gain access to the original keys.

The device is still bound to the user.

User factory resets the client device

Local key is deleted

Local key is deleted

User restores a backup from the original device to a new device

Local keys are not exported to the cloud during backup and cannot be restored to another device.

New device will require new keys.

Keys remain on the original device.

Local keys are stored in Secure Enclave and are not exported to the cloud during backup and cannot be restored to another device.

New device will require new keys.

Keys remain on the original device.

Removing keys from the client device manually does not remove the keys from the server.

Use the SDK to remove both sets of keys from within your application, or an Administrator can remove server keys by using the REST API.

To completely unbind a device from a user, use the SDK delete method to contact the AM server to delete the keys.

When the keys are successfully removed from the server, the SDK removes the private keys from the device.

Step 1. Retrieve a list of keys

Call the FRUserKeys().loadAll() method to obtain a list of keys that are stored on the device:

  • Android - Kotlin

  • iOS - Swift

val frUserKeys = FRUserKeys(context)
var keys: List<UserKey> = frUserKeys.loadAll()
let userKeys = FRUserKeys().loadAll()

Step 2. Delete the key from both the server and the device

Call the FRUserKeys().delete(userKey, forceDelete) method to delete a key from the server.

The parameters are as follows:

userKey

Which key to delete.

forceDelete

Whether to delete the local key if deleting the key from the server fails.

When set to true, the local key is deleted even if removal from the server was not successful.

Defaults to false, meaning the local key is not deleted if removal from the server fails.

Example:

  • Android - Kotlin

  • iOS - Swift

val frUserKeys = FRUserKeys(context)
frUserKeys.delete(userKey, false)
do {
    try FRUserKeys().delete(
        userKey: userKey,
        forceDelete: false
    )
}

catch {
    print("Failed to delete public key from server")
}

After deleting keys, the user needs to rebind the device for use in authentication journeys.

Implement custom UI

To ease implementation, the OS and Ping SDKs provide default user interfaces for authenticating to access private keys, and also for selecting the private key to use if there is more than one.

The user interface for authenticating to access the private keys uses the text strings returned by the callback. You can configure these strings in the configuration of the relevant nodes on the server, or you can override these values using the SDK for providing the prompts.

You can also implement your own user interface for requesting an application PIN, and key selection when there are multiple available.

Custom authentication prompts

The default text strings used when prompting the user to authenticate to gain access to the private keys come from the callbacks.

You can override or localize these strings by using the Prompt object. You can then pass the customized object into the deviceBindingCallback.bind and deviceSigningVerifierCallback.sign calls.

  • Android - Kotlin

  • iOS

Binding:

val deviceBindingCallback = node.getCallback(DeviceBindingCallback::class.java)

deviceBindingCallback.bind(
    activity,
    prompt = Prompt("Custom Title", "Custom Subtitle", "Custom Description"),
    listener =
        object : FRListener<Void?> {
            override fun onSuccess(result: Void?) {
                node.next(activity, activity)
            }

            override fun onException(e: java.lang.Exception) {
                node.next(activity, activity)
            }
        }
)

Signing:

val deviceSigningVerifierCallback = node.getCallback(DeviceSigningVerifierCallback::class.java)

deviceSigningVerifierCallback.sign(
    activity,
    prompt = Prompt("Custom Title", "Custom Subtitle", "Custom Description"),
    listener =
        object : FRListener<Void?> {
            override fun onSuccess(result: Void?) {
                node.next(activity, activity)
            }

            override fun onException(e: java.lang.Exception) {
                node.next(activity, activity)
            }
        }
)

Binding:

if callback.type == "DeviceBindingCallback",
let deviceBindingCallback = callback as? DeviceBindingCallback {
    let customPrompt = Prompt (
        title: "Custom Title",
        subtitle: "Custom Subtitle",
        description: "Custom Description"
    )
    deviceBindingCallback.bind (prompt: customPrompt)
    { result in
        /// process the result
    }
    return
}

Signing:

if callback.type == "DeviceSigningVerifierCallback",
let deviceSigningVerifierCallback = callback as? DeviceSigningVerifierCallback {
    let customPrompt = Prompt (
        title: "Custom Title",
        subtitle: "Custom Subtitle",
        description: "Custom Description"
    )
    deviceSigningVerifierCallback.sign (prompt: customPrompt)
    { result in
        /// process the result
    }
    return
}

Apple iOS restrictions on custom prompts

On iOS devices, some of the prompts displayed to the user are system controlled and cannot be customized.

The following table outlines the situations where iOS uses your customized prompt:

Biometric Only Biometric with allow fallback Application PIN

FaceID-registered devices display no system-provided or custom text:

FaceID shows no system or custom text.

TouchID-registered devices show a system-provided title and the custom description text:

TouchID shows system title and custom description text.

When allow fallback is enabled, the biometric prompts match the biometric-only display.

If authentication falls back to using the device PIN, then the device shows a system-provided title and the custom description text:

Fallback to device PIN shows system title and custom description.

When using an application PIN, the device shows both the custom title and custom description text:

Application PIN shows both custom title and description text.

Custom authentication UI

When binding a device or verifying ownership of a device with signing, the user is asked to authorize access to their private keys.

For biometric-backed authentication such as touch or face ID, the UI is provided by the OS. When using APPLICATION_PIN as the authentication method you can customize the UI as required.

For example, the Ping SDK for Android uses the following UI when requesting an application PIN:

android device binding authentication en
Figure 44. Android OS UI for BIOMETRIC_ONLY, BIOMETRIC_ALLOW_FALLBACK, and a custom APPLICATION_PIN

When providing your own application PIN UI, you can use the same mechanism for both binding and signing.

The following code shows how to implement a custom application PIN UI:

  • Android - Java

  • Android - Kotlin

  • iOS - Swift

callback.bind(requireContext(), deviceBindingAuthenticationType -> {
    switch (deviceBindingAuthenticationType) {
        case APPLICATION_PIN: {
            return new CustomAppPinDeviceAuthenticator();
        }
        default:
            return callback.getDeviceAuthenticator(deviceBindingAuthenticationType);
    }
}, new FRListener<Void>() {
    @Override
    public void onSuccess(Void result) {
      // Proceed to the next node
    }

    @Override
    public void onException(Exception e) {
      // Proceed to the next node
    }
});
public class CustomAppPinDeviceAuthenticator extends ApplicationPinDeviceAuthenticator {

    public CustomAppPinDeviceAuthenticator() {
        super((prompt, fragmentActivity, $completion) -> {
            $completion.resumeWith("1234".toCharArray());
            return IntrinsicsKt.getCOROUTINE_SUSPENDED();
        });
    }
}
class CustomPinCollector: PinCollector {
    override suspend fun collectPin(prompt: Prompt, fragmentActivity: FragmentActivity): CharArray {}
}

class CustomAppPinDeviceAuthenticator: ApplicationPinDeviceAuthenticator(CustomPinCollector())

callback.bind(context) {
    when (it) {
        // Implement your custom app PIN UI...
        APPLICATION_PIN -> CustomAppPinDeviceAuthenticator()
        else -> {
            callback.getDeviceAuthenticator(it)
        }
    }
}
callback.bind(deviceAuthenticator: { type in
    switch type {
        case .applicationPin:
            return ApplicationPinDeviceAuthenticator(pinCollector: CustomPinCollector())
        default:
            return callback.getDeviceAuthenticator(type: type)
    }
}, completion: { result in
    switch result {
        case .success:
            // Proceed to the next node
        case .failure(let error):
            // Handle the error and proceed to the next node
    }
})
class CustomPinCollector: PinCollector {
    func collectPin(prompt: Prompt, completion: @escaping (String?) -> Void) {
        // Implement your custom app PIN UI...
        completion("1234")
    }
}

Custom key selection UI

When verifying ownership of a device using signing, the user could be asked to select which private key to use if they have more than one on their device.

android device bind user select en
Figure 45. Default Android UI for selecting the private key

You can override the default key selection UI to implement your own.

  • Android - Java

  • Android - Kotlin

  • iOS - Swift

callback.sign(requireContext(), new CustomUserKeySelector(),  new FRListener<Void>() {
   @Override
   public void onSuccess(Void result) {

   }

   @Override
   public void onException(Exception e) {

   }
});
// Custom user selector that always returns the most recently created key
public class CustomUserKeySelector implements UserKeySelector {

    @Nullable
    @Override
    public Object selectUserKey(@NonNull UserKeys userKeys, @NonNull FragmentActivity fragmentActivity, @NonNull Continuation<? super UserKey> $completion) {
        $completion.resumeWith(userKeys.getItems().get(0));
        return IntrinsicsKt.getCOROUTINE_SUSPENDED();
    }
}
callback.sign(context, CustomUserKeySelector())
// Custom user selector that always returns the most recently created key
class CustomUserKeySelector : UserKeySelector {
    override suspend fun selectUserKey(userKeys: UserKeys,
                                       fragmentActivity: FragmentActivity): UserKey {
        return userKeys.items[0]
    }
}
callback.sign(userKeySelector: CustomUserKeySelector()) { result in
    switch result {
        case .success:
            // Proceed to the next node
        case .failure(let error):
            // Handle the error and proceed to the next node
    }
}
// Custom user selector that always returns the most recently created key
class CustomUserKeySelector: UserKeySelector {
    func selectUserKey(userKeys: [UserKey], selectionCallback: @escaping UserKeySelectorCallback) {
        selectionCallback(userKeys.first)
    }
}

Error handling

If an error occurs when binding a device or signing a secret for verification, the SDK raises an exception. Check the status property of the exception for information about the problem.

The following table lists the possible values, and the outcomes these map to in the authentication nodes:

Description Exception status Mapped node outcome

The client device does not support device binding.

For example, it does not provide biometric sensors, or the SDK cannot generate the required key pair.

Unsupported

Unsupported

Binding or signing did not complete before the timeout expired.

Timeout

Timeout

The user cancelled binding or signing before completion.

Abort

Abort

The SDK could not locate an existing private key.

Either the device has not yet been bound, or the private key was removed.

UnRegister

Unsupported

The user failed the authentication required to access the private key.

For example, they used an unrecognized fingerprint, or the wrong application PIN.

UnAuthorize

Unsupported

An unknown, unexpected error occurred.

Abort

Abort

You can map exceptions to custom client error outcomes in the nodes. For example, the following code maps the UnRegister status to an outcome named CustomUnReg in the node:

  • Android - Kotlin

  • iOS - Swift

deviceBindingCallback.bind(activity, object : FRListener<Void> {
    override fun onSuccess(result: Void?) {
        node.next(activity, activity)
    }

    override fun onException(e: java.lang.Exception?) {
        // Custom Error
        if (e is DeviceBindingException) {
            if (e.status is UnRegister) {
                deviceBindingCallback.setClientError("CustomUnReg")
            }
        }
        node.next(activity, activity)
    }
})
// Bind the device
callback.bind() { result in
    switch result {
        case .success:
            // Proceed to the next node
        case .failure(let error):
            // Custom Error
            if error == DeviceBindingStatus.unRegister {
                callback.setClientError("CustomUnReg")
            }
    }
}

You must add the name of the custom client error, for example CustomUnReg, to the Client error outcomes property in the node configuration:

custom client error en
Figure 46. Custom client error outcome in the device binding node.

Device profile client configuration

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

PingOne Advanced Identity Cloud and PingAM 7 and later include nodes that instruct your client applications to collect device profile information for decision-making in authentication journeys.

Device profile information can help you build authentication journeys that:

  • Allow easy-pass for users on trusted devices.

  • Introduce stronger authentication requirements, or deny access, for users on suspicious devices.

This section explains how to work with device profile information in your client app.

Prepare the server

In this step, you set up your server to perform device profiling.

Configure a journey to perform device profiling

To profile devices you must configure an authentication journey in your server.

The following table covers the authentication nodes and callbacks available for profiling devices in your authentication journeys.

Node Callback Description

Device Profile Collector node

DeviceProfileCallback

Gather location data and other metadata from the client device.

Device Match node

Non-interactive

Compare collected device data with that stored in the user’s profile.

Device Profile Save node

Non-interactive

Persist collected device data to a user’s profile.

In your server, log in as an administrator and create a new authentication journey similar to the following example:

An example device profiling journey
Figure 47. An example device profiling journey
  • You must identify the user to be able to retrieve any stored device profiles they already have.

    In this example, we ask for their username and password, and verify the credentials against the data store.

  • The Device Profile Collector node 1 instructs the SDK to collect and return metadata from the client device.

  • The Device Match node 2 compares the collected metadata against any stored in the user’s profile.

    You can write scripts to customize how the node compares captured and stored metadata.

    For a complete sample script, as well as instructions for its use and a development toolkit, refer to https://github.com/ForgeRock/forgerock-device-match-script on GitHub.

  • The example journey continues after comparing metadata, depending on the outcome:

    True

    The client device matches a device they have saved previously, and authentication succeeds without further interaction.

    False or Unknown Device

    The client device does not match, or they have not saved a device profile, so authentication requires additional steps, such as MFA verification.

  • After successful MFA verification, the Device Profile Save node 3 offers to save the metadata to the user’s profile, marking the client device as trusted for future journeys.

Customize device profile matching

The default Device Match node logic matches devices based on the number of differences in the captured attribute values. You can modify the threshold for difference by using the Acceptable Variance field in the node configuration.

The node also supports a custom matching script, where you can customize or write your own logic for matching device profiles. The script type must be Decision node script for authentication trees in self-managed AM servers or Journey Decision Node in Advanced Identity Cloud deployments.

Learn more about creating scripts:

Download and modify a sample device match script

ForgeRock provides a sample repository that builds a device profile matching script you can download and customize for use in your environment.

Requirements

You require the following prerequisites to build this project:

  • Node v13.10 or higher

  • NPM v6 or higher

Customize the script

Without any modifications, the sample script performs matching on both metadata and location data.

You can modify src/index.js to prevent either of the matching types from running:

Perform both metadata and location matching
// Metadata and location matching
const [ metadataMatch, locationMatch ] = deviceMatcher();
const isMetadataMatching = metadataMatch(client.metadata, stored.metadata);
const isLocationMatching = locationMatch(client.location, stored.location);
Perform only metadata matching
const [ metadataMatch ] = deviceMatcher();
const isMetadataMatching = metadataMatch(client.metadata, stored.metadata);
Perform only location matching
const [ _, locationMatch ] = deviceMatcher();
const isLocationMatching = locationMatch(client.location, stored.location);

To modify the logic the script uses, edit the following files:

  • Metadata matching: src/metadata.js

  • Location matching: src/location.js

Configure the matching

The logic for both metadata and location matching can be configured according to your requirements.

Metadata matching

The metadata matching script uses recursive iteration to compare the metadata obtained from the client with the stored metadata. It is written with small to moderately large sized JavaScript objects in mind, and not optimized for very large or very deep structures.

When the recursion reaches a primitive value in the JSON, such as a string, number, or boolean, it does a comparison of the associated values. If there’s a mismatch, it increments a counter.

You can modify this behavior as follows:

  1. Configure the maxUnmatchedAttrs parameter to specify the maximum allowed number of allowed mismatches.

  2. Alter the "weight" of specific attributes in the JSON, by using the attrWeights parameter.

    The weightings assigned work alongside the configured maximum number of mismatches. For example, if maxUnmatchedAttrs is set to 2, this could be exceeded by having three attributes with the default weight of 1 that do not match (n=1+1+1), or you could have a single property with a weight of 3 that does not match (n=3).

    Configuring the weighting means you can assign lower importance to certain profile properties, like display width or height, which might vary if the user changes displays from the prior authentication.

    If you’re less concerned with the display properties because they can easily change, and more concerned with things that will remain unchanged, like the device’s memory, you can assign greater weight to them as appropriate.

Example:

const config = {
  attrWeights: {
    // Custom weights for metadata attributes (object keys)
    deviceMemory: 3 // type `number`
    // ... as many attributes as you want
    // all attributes default to 1
  },
  maxUnmatchedAttrs: 2, // type `number`; default to 0 (exact match)
};
const [ metadataMatch ] = deviceMatcher(config);
const isMetadataMatching = metadataMatch(client.metadata, stored.metadata);
Location matching

The location matching script does not internally compare geolocation coordinates. It uses an external library called "geolib", which is well-built and very powerful.

The script uses the getDistance() function from the library, and wraps it with a comparison of the distance between two points to that of the maximum allowed radius.

Specify the maximum radius by using the allowedRadius parameter. All measurements are in meters. Example:

const config = {
  allowedRadius: 250, // type `number`; defaults to 100 (meters)
};
const [ _, locationMatch ] = deviceMatcher(config);
const isLocationMatching = locationMatch(client.location, stored.location);
Build the script

To build the sample device match script, follow these steps:

  1. Download the device match script project from the GitHub repository:

    git clone https://github.com/ForgeRock/forgerock-device-match-script.git

  2. In a terminal window, navigate to the root of the device match script project:

    cd forgerock-device-match-script

  3. Run npm to download and install the required packages and modules:

    npm install

  4. Build the device match script with npm:

    npm run build:widget

  5. Copy and paste the contents of the dist/script.js file into your ForgeRock server.

    The script type must be Decision node script for authentication trees in self-managed AM servers or Journey Decision Node in Advanced Identity Cloud deployments.

  6. In your Device Match node configuration, select Use Custom Matching Script, and in the Custom Matching Script field, select the script you created in the previous step.

  7. Click Save.

For information on testing the script as well as some frequently asked questions, refer to the README.md in the repo.

Uniquely identifying devices

The Device Match node looks up a user’s stored device profiles using a device identifier as a key. The Ping SDKs generate the device identifier as part of the device profile that it returns to the Device Profile Collector node as part of the JSON payload.

For example:

{
  "identifier": "d50cdb5ce8d055a3-86bd35e1b975a14d76b40940112c2380264c8efd",
  ....
}

Device identifier generation

This section covers the identifiers used on each platform, and how they are generated.

  • Android

  • iOS

  • JavaScript

On Android, a static device ID is not possible.

Static device ID

An ID that never changes, even during a factory reset or app re-installation.

Instance ID

An identifier for an instance of an application.

Instead of using a device ID, Android uses an instance ID. The instance ID provides a unique identifier for each instance of app, or app group.

Instance ID generation algorithm:

  1. Generate a public/private key pair, and store the KeyPair in the AndroidKeyStore (Shared Storage).

  2. Hash the public key with SHA1.

  3. Encode with Base64.

  4. Compile the ANDROID_ID with the hashed public key.

On iOS, FRDeviceIdentifier provides a unique identifier for each device that is defined in same Shared Keychain Access Group.

FRDeviceIdentifier provides a secure mechanism to uniquely generate, persist, and manage the identifier.

Device ID generation algorithm:

  1. Generate an RSA key pair with key size of 2048.

  2. Persist RSA keys in the Shared Keychain Service.

  3. Hash the public key with SHA1.

  4. Convert the hashed data into a hex string.

To view code that shows how iOS generates the device ID, see FRDeviceIdentifier.swift.

In JavaScript, the browser’s crypto library generates the device ID. The ID is stored in the browser’s localStorage.

To view code that shows how JavaScript generates the device ID, see index.ts in the forgerock-javascript-sdk repository.

When can identifiers change?

If the identifier changes, the Device Match node will be unable to match any stored device profiles.

If this happens, your journey must collect and store a new device profile, which contains the new identifier.

This section explains what can cause an identifier to change on each platform.

  • Android

  • iOS

  • JavaScript

In Android, the instance ID is deleted or changes if any of the following occurs:

  • An app is restored on a new device.

  • The user uninstalls and re-installs the app.

  • The user clears app data.

On iOS, the device ID is stored in the Keychain. This means the ID persists when the app is removed.

However, the device ID is deleted or changes if any of the following occurs:

  • The user wipes or factory resets the phone.

  • The user migrates to a new phone.

  • The keychain is programmatically deleted from the phone.

  • The device ID is programmatically deleted from the Keychain.

  • The keychain identifier in the forgerock_keychain_access_group configuration property changes.

In JavaScript, the device ID is deleted or changes if any of the following occurs:

  • The browser window creates the device ID while in "private" or "incognito" mode. Closing the browser removes the ID.

  • The browser removes the ID when cleaning up old data to make room for new data.

  • The browser is uninstalled and reinstalled. The ID is removed.

  • The user removes the device ID by clearing the browser data.

Set up device profiling in Android apps

This page shows how to detect the DeviceProfileCallback, how to collect the device profile, and how to send the profile to PingAM.

PingAM includes collected device information in its audit logs by default.

To configure PingAM to filter out this information and ensure no personally identifiable information (PII) is written to the audit logs, refer to Prevent auditing of device data.

Handle a device profile callback

If an authentication journey uses the device profile node, the SDK returns DeviceProfileCallback to collect device attributes.

You use various SDK methods to handle the callback.

Use the default device profile callback

Call the DeviceProfileCallback.execute() method to collect the device profile:

callback.execute(context, new FRListener<Void>() {
@Override
public void onSuccess(Void result) {
// call next

}

@Override
public void onException(Exception e) {
}
});

Customize the device profile callback

  1. Extend the callback that you want to override, providing two constructors that match the parent constructors.

  2. Annotate the constructor with the @Keep annotation.

  3. Override the default implementation:

    public class MyCustomDeviceProfileCallback extends DeviceProfileCallback {
    
     public MyCustomDeviceProfileCallback() {
     }
    
     @Keep
     public MyCustomDeviceProfileCallback(JSONObject jsonObject, int index) {
         super(jsonObject, index);
     }
    
     @Override
     public void execute(Context context, FRListener<Void> listener) {
         super.execute(context, listener);
     }
    }
  4. Register the callback:

    CallbackFactory.getInstance().register(MyCustomDeviceProfileCallback.class);

Manually collect device profile information

Instead of responding to a device callback, your app can get the device profile using default collectors.

You can also modify the default collectors. A set of collectors are predefined.

The FRDevice uses the default predefined collector to collect device profile:

The following code collects the device profile using the default collectors:

FRDeviceCollector.DEFAULT.collect(context, listener);

Default collectors

Collector name Description

FRDeviceCollector

Main collector that includes other collectors and provides a collector version.

BluetoothCollector

Collect BLE support information of the device.

BrowserCollector

Collect browser information of the device; specifically, the User-Agent.

CameraCollector

Collect camera information of the device.

DisplayCollector

Collect display information of the device.

HardwareCollector

Collect hardware-related information, such as the number of CPUs, number of active CPUs, and so on.

LocationCollector

Collect location Information of the device.

NetworkCollector

Collect network information of the device.

PlatformCollector

Collect platform-related information, such as device jailbreak status, time zone/locale, OS version, device name, and device model.

TelephonyCollector

Collect telephony information of the device.

Sample device profile

{
  "identifier": "d50cdb5ce8d055a3-86bd35e1b975a14d76b40940112c2380264c8efd",
  "metadata": {
    "platform": {
      "platform": "Android",
      "version": 31,
      "device": "emulator64_x86_64_arm64",
      "deviceName": "sdk_gphone64_x86_64",
      "model": "sdk_gphone64_x86_64",
      "brand": "google",
      "locale": "en_US",
      "timeZone": "America/Vancouver",
      "jailBreakScore": 1
    },
    "hardware": {
      "hardware": "ranchu",
      "manufacturer": "Google",
      "storage": 5951,
      "memory": 1968,
      "cpu": 4,
      "display": {
        "width": 1080,
        "height": 2148,
        "orientation": 1
      },
      "camera": {
        "numberOfCameras": 2
      }
    },
    "browser": {
      "userAgent": "Mozilla/5.0 (Linux; Android 12; sdk_gphone64_x86_64 Build/SPB5.210812.003; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Mobile Safari/537.36"
    },
    "bluetooth": {
      "supported": true
    },
    "network": {
      "connected": true
    },
    "telephony": {
      "networkCountryIso": "us",
      "carrierName": "T-Mobile"
    }
  },
  "location": {
    "latitude": 37.4219711,
    "longitude": -122.0849955
  },
  "lastSelectedDate": 1634068456582,
  "alias": "sdk_gphone64_x86_64"
}

Create a custom collector

  1. Create a custom "DeviceCollector" class that implements the DeviceCollector interface:

    public class MyCustomMetadataCollector implements DeviceCollector {
    
     private static final List<DeviceCollector> COLLECTORS = new ArrayList<>();
    
     static {
         //Pick from existing Collector or implement your own collector
         COLLECTORS.add(new PlatformCollector());
         COLLECTORS.add(new NetworkCollector());
         COLLECTORS.add(new TelephonyCollector());
     }
    
     @Override
     public String getName() {
         return "metadata";
     }
    
     @Override
     public void collect(Context context, FRListener<JSONObject> listener) {
         collect(context, listener, new JSONObject(), COLLECTORS);
     }
    }
  2. Use FRDeviceCollectorBuilder to add your custom Collector:

    public class MyCustomDeviceProfileCallback extends DeviceProfileCallback {
    
     public MyCustomDeviceProfileCallback() {
     }
    
     @Keep
     public MyCustomDeviceProfileCallback(JSONObject jsonObject, int index) {
         super(jsonObject, index);
     }
    
     @Override
     public void execute(Context context, FRListener<Void> listener) {
         FRDeviceCollector.FRDeviceCollectorBuilder builder = FRDeviceCollector.builder();
         if (isMetadata()) {
             builder.collector(new MyCustomMetadataCollector());
         }
         if (isLocation()) {
             builder.collector(new LocationCollector());
         }
    
         builder.build().collect(context, new FRListener<JSONObject>() {
             @Override
             public void onSuccess(JSONObject result) {
                 setValue(result.toString());
                 Listener.onSuccess(listener, null);
             }
    
             @Override
             public void onException(Exception e) {
                 Listener.onException(listener, null);
             }
         });
     }
    }

Device profile attributes

By default, the Ping SDK collects the following device attributes:

Attribute Value

identifier

A unique ID for the device.

To learn more about the device identifier, refer to Uniquely identifying devices.

location

The location of a device (longitude and latitude values).

This is configured in the node and requires user permissions.

metadata

Metadata for the device, including:

platform

The device OS, such as Android or iOS.

deviceName

The name of the device.

locale

The locale of the device, such as en.

timeZone

The time zone of the device, such as Africa/Johannesburg.

brand

The brand of the device, such as Apple.

jailBreakScore

A value between 0 and 1 that denotes the tampering level for a device.

Obtain user permission for the device location

Your app requires the user’s authorization to access the device location.

For information about how to request the authorization, refer to Request location permissions.

Implement default jailbreak/rooted device detection

The FRRootDetector class is responsible for analyzing whether the device is tampered.

The class analyzes the device by using multiple device tamper detectors, and returns the highest score in the range between 0.0 to 1.0 from all the detectors.

You can customize the metadata.platform.jailBreakScore with Root Detector.

Sample using default tamper detection:

// The DEFAULT Detector uses all available detectors in the SDK to determine if the device is rooted.
RootDetector rootDetector = FRRootDetector.DEFAULT;

// Check if device is rooted
double rootedScore = rootDetector.isRooted(context)

// Evaluate the result
if rootedScore == 0.0 {
    // Detectors score result with 0.0, likely device is not rooted
}
else if rootedScore <= 0.5 {
    // Some of the detectors returned a possible positive result that indicates the device might be rooted
}
else {
    // Most of the detectors returned a possible positive result that indicates the device is likely rooted
}

Customize jailbreak/rooted detection

The SDKs provide a set of industry-standard detectors that allow you to customize the detectors to use.

Sample custom tamper detection code:

// Using Builder to choose two detectors
RootDetector rootDetector = FRRootDetector.builder()
        .detector(new SuCommandDetector())
        .detector(new RootAppDetector())
        .build();

// Get result
double rootedScore = rootDetector.isRooted(context)

Implement custom detectors

You can provide your own detectors by implementing the RootDetector interface on Android. The interface represents the definition of an individual analyzer for detecting when the device is rooted or jailbroken.

Each detector determines whether the device is rooted or jailbroken. Each collector returns a result score as a Double, within the range of 0.0 to 1.0.

Sample custom detector code:

// Add custom detector to RootDetector
RootDetector rootDetector = FRRootDetector.builder()
        .detectors(FRRootDetector.DEFAULT_DETECTORS)
        .detector(new RootDetector() {
            @Override
            public double isRooted(Context context) {
                return 0;
            }
        })
        .build();

// Get result
double rootedScore = rootDetector.isRooted(context);

More information

Set up device profiling in iOS apps

This page shows how to detect the DeviceProfileCallback, how to collect the device profile, and how to send the profile to PingAM.

PingAM includes collected device information in its audit logs by default.

To configure PingAM to filter out this information and ensure no personally identifiable information (PII) is written to the audit logs, refer to Prevent auditing of device data.

Handle a device profile callback

If an authentication journey uses the device profile node, the SDK returns DeviceProfileCallback to collect device attributes.

You use various SDK methods to handle the callback.

Use the default device profile callback

deviceProfileCallback.execute { _ in node.next
  { (user: FRUser?, node, error) in
   self.handleNode(user: user, node: node, error: error) //Handle the node
  }

}

Customize the device profile callback

  1. The DeviceCollector protocol is a baseline class implementation protocol for the FRDeviceCollector.

    Create a new class inheriting the protocol and implement the protocol methods:

    public class CustomCollector: DeviceCollector {
          public var name: String = "customCollector"
          public func collect(completion: @escaping DeviceCollectorCallback) {
            var result: [String: Any] = [:]
            result["customID"] = "MyCustomIDValue"
            completion(result)
          }
     }
  2. Add the custom collector in the DeviceProfileCallback array of ProfileCollectors:

    deviceProfileCallback.profileCollector.collectors.append(CustomCollector())
  3. To collect the device profile and submit the node, use the DeviceProfileCallback.execute() method:

    deviceProfileCallback.execute { _ in node.next
      { (user: FRUser?, node, error) in
        self.handleNode(user: user, node: node, error: error) // Handle the node
      }
    }

If you want to remove any of the default profile collectors, access the profile collectors array of the DeviceProfileCallback node. The default list contains the following collectors:

  • PlatformCollector()

  • HardwareCollector()

  • BrowserCollector()

  • TelephonyCollector()

  • NetworkCollector()

Manually collect device profile information

Instead of responding to a device callback, your app can get the device profile using default collectors.

The FRAuth SDK provides a device profile feature that enables you to identify and obtain details about the device.

The FRDevice class includes methods for getting information about a device.

Use the FRDevice.currentDevice?.getProfile() method to return device profile:

  1. In your app, add the following code after the SDK initialization:

    FRDevice.currentDevice?.getProfile(completion: { (deviceProfile) in
        print(deviceProfile)
    })
  2. When the above code is triggered, the app prints JSON text, including the device profile, in the console.

    The device identifier is not the same as Apple’s device vendor identifier. As long as the Keychain Service configuration remains unchanged, the device identifier should remain the same for this device, even if you delete and reinstall the application.

Default collectors

Collector name Description

FRDeviceCollector

Main collector that includes other collectors and provides a collector version.

BluetoothCollector

Collect BLE support information of the device.

BrowserCollector

Collect browser information from the device; specifically, the User-Agent.

CameraCollector

Collect camera information of the device.

DisplayCollector

Collect display information of the device.

HardwareCollector

Collect hardware-related information such as the number of CPUs, number of active CPUs, and so on.

LocationCollector

Collect location information of the device.

NetworkCollector

Collect network information of the device.

PlatformCollector

Collect platform-related information, such as device jailbreak status, time zone/locale, OS version, device name, and device model.

TelephonyCollector

Collect telephony information of the device.

Sample device profile

{
    "identifier": "uiCToe3h2kdfYLOzvHpaloxsKVE=",
    "version": "1.0",
    "platform": {
        "jailbreakScore": 0.0,
        "systemInfo": {
            "sysname": "Darwin",
            "release": "18.6.0",
            "version": "Darwin Kernel Version 18.6.0: Thu Apr 25 22:14:08 PDT 2019; root:xnu-4903.262.2~2/RELEASE_ARM64_T8015",
            "nodename": "James-Go-iPhone-X",
            "machine": "iPhone10,6"
        },
        "timezone": "America/Vancouver",
        "model": "iPhone10,6",
        "version": "12.3.1",
        "locale": "en",
        "platform": "iOS",
        "device": "iPhone",
        "brand": "Apple",
        "deviceName": "James Go iPhone X"
    },
    "hardware": {
        "cpu": 6,
        "storage": 60975.11328125,
        "manufacturer": "Apple",
        "activeCPU": 6,
        "display": {
            "orientation": 1,
            "width": 375.0,
            "height": 812.0
        },
        "memory": 2823.0,
        "multitaskSupport": true,
        "camera": {
            "numberOfCameras": 4
        }
    },
    "bluetooth": {
        "supported": true
    },
    "browser": {
        "agent": "FRExample/1.0 (com.forgerock.frexample; iPhone; build:1; iOS 12.3.1) CFNetwork/978.0.7 Darwin/18.6.0 FRAuth/1.0"
    },
    "telephony": {
        "mobileCountryCode": "302",
        "voipEnabled": true,
        "isoCountryCode": "ca",
        "mobileNetworkCode": "220",
        "carrierName": "TELUS"
    },
    "network": {
        "connected": true
    },
    "location": {
        "latitute": 37.7873589,
        "longitude": -122.408227
    }
}

Modify the default collectors

You can collect the device profile using default collectors. You can also modify the default collectors.

The following code collects the device profile using the default collectors:

FRDeviceCollector.shared.collect { (result) in
    // Result is returned as [String: Any]
}

The collectors array in the FRDeviceCollector class is a public property. You can add, remove, or change any of the collectors:

@objc
  public var collectors: [DeviceCollector]

Create a custom collector

class CustomCollector: DeviceCollector {
    var name: String = "custom"

    func collect(completion: @escaping DeviceCollectorCallback) {
        var result: [String: Any] = [:]

        // Perform logic to collect any device profile
        result["key"] = "value"

        completion(result)
    }
    }
}

Device profile attributes

By default, the Ping SDK collects the following device attributes:

Attribute Value

identifier

A unique ID for the device.

To learn more about the device identifier, refer to Uniquely identifying devices.

location

The location of a device (longitude and latitude values).

This is configured in the node and requires user permissions.

metadata

Metadata for the device, including:

platform

The device OS, such as Android or iOS.

deviceName

The name of the device.

locale

The locale of the device, such as en.

timeZone

The time zone of the device, such as Africa/Johannesburg.

brand

The brand of the device, such as Apple.

jailBreakScore

A value between 0 and 1 that denotes the tampering level for a device.

Obtain user permission for the device location

Your app requires the user’s authorization to access the device location.

For information about how to request the authorization, refer to Requesting Authorization for Location Services.

Implement default jailbreak/rooted device detection

The FRJailbreakDetector class is responsible for analyzing whether the device is tampered.

The class analyzes the device by using multiple device tamper detectors and returns the highest score in the range between 0.0 to 1.0 from all the detectors.

On iOS, a device simulator always returns 1.0.

Sample using default tamper detection:

// Check if device is jailbroken
let jailbrokenScore = FRJailbreakDetector.shared.analyze()

// Evaluate the result
if jailbrokenScore == -1.0 {
    // no detectors found
}
else if jailbrokenScore == 0.0 {
    // Means that the detectors score result is 0.0
}
else {
    // Some detectors returned a possible positive result that indicates the device might be jailbroken
}

Customize jailbreak/rooted detection

The SDKs provide a set of well-known detectors. You can choose the detectors to use.

Sample custom tamper detection code:

// Remove everything and only add your selected detectors
FRJailbreakDetector.shared.detectors.removeAll()
FRJailbreakDetector.shared.detectors.append(BashDetector())
FRJailbreakDetector.shared.detectors.append(SSHDetector())

// Or loop through detectors and remove specific detector from default
for detector in FRJailbreakDetector.shared.detectors {
    // Remove specific detector if required
}

// Get result
let jailbrokenScore = FRJailbreakDetector.shared.analyze()

Implement custom detectors

Developers can implement their own detectors by extending the JailbreakDetector protocol on iOS. This protocol represents the definition of a individual analyzer for detecting if a device is rooted or jailbroken.

Each detector should analyze its logic to determine whether the device is rooted or jailbroken. Each detector returns the result score as a Double, within the range of 0.0 to 1.0.

Sample custom detector code:

// Custom detector
class CustomDetector: JailbreakDetector {
    public func analyze() -Double {
        var result = 0.0
        // do the custom logic
        return result
    }
}

// Add custom detector to JailbreakDetector
FRJailbreakDetector.shared.detectors.append(CustomDetector())

// Get the result
let jailbrokenScore = FRJailbreakDetector.shared.analyze()


Known limitations

For ForgeRock SDK for iOS v2.2.0 and earlier, the iOS SDK has discrepancies in attribute names inside the JSON payload. ForgeRock SDK for iOS v3.0.0 and later correct the attribute names to align with PingAM’s expectation and other platforms. The following attribute names were changed as of 3.0.0:

Attribute Names for 2.2.0 and earlier Attribute Names for 3.0.0 and later

timezone

timeZone

jailbreakScore

jailBreakScore

If your application still uses iOS SDK 2.2.0 or older, an adjustment in the Custom Matching Script is required in the Device Match Node to collect and analyze correct attribute values from the SDK.

More information

Set up device profiling in JavaScript apps

This page shows how to detect the DeviceProfileCallback, how to collect the device profile, and how to send the profile to your server.

The server includes collected device information in its audit logs by default.

To configure PingAM to filter out this information and ensure no personally identifiable information (PII) is written to the audit logs, refer to Prevent auditing of device data.

Handle a device profile callback

  1. Get the callback and any messages to display to the user:

    const deviceCollectorCb = step.getCallbackOfType('DeviceProfileCallback');
    const message = deviceCollectorCb.getMessage();
  2. From the callback, determine if the intention is to collect the device location, the device metadata, or both:

    const isLocationRequired = deviceCollectorCb.isLocationRequired();
    const isMetadataRequired = deviceCollectorCb.isMetadataRequired();
  3. Create a new instance of the FRDevice class.

    const device = new FRDevice();

    To return specific device data, pass in configuration options to the FRDevice constructor. For example, you can return device platform, display, browser, and hardware data.

Manually collect device profile information

Instead of responding to a device callback, your app can get the device profile using default collectors.

  1. Call the getProfile() method of the FRDevice class, passing the boolean values from the callback that indicate if device location and/or device metadata is required:

    const profile = await device.getProfile({
     location: isLocationRequired,
     metadata: isMetadataRequired,
    });
    To return specific device data, override the FRDevice class methods for getting the device metadata, device browser plugins, device name, device hardware, and so on.
  2. Set the device profile information for the step:

    step.getCallbackOfType('DeviceProfileCallback').setProfile(profile);

Default collectors

Collector name Description

fontNames

Collect font information.

displayProps

Collect display information of the device.

browserProps

Collect browser information of the device.

hardwareProps

Collect hardware related information.

platformProps

Collect platform related information

Sample device profile

{
  "identifier": "714524572-2799534390-3707617532",
  "metadata": {
    "hardware": {
      "cpuClass": null,
      "deviceMemory": 8,
      "hardwareConcurrency": 16,
      "maxTouchPoints": 0,
      "oscpu": null,
      "display": {
        "width": 1080,
        "height": 1920,
        "pixelDepth": 24,
        "angle": 270
      }
    },
    "browser": {
      "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36 Edg/80.0.361.111",
      "appName": "Netscape",
      "appCodeName": "Mozilla",
      "appVersion": "5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36 Edg/80.0.361.111",
      "appMinorVersion": null,
      "buildID": null,
      "product": "Gecko",
      "productSub": "20030107",
      "vendor": "Google Inc.",
      "vendorSub": "",
      "browserLanguage": null,
      "plugins": "internal-pdf-viewer;mhjfbmdgcfjbbpaeojofohoefgiehjai;internal-nacl-plugin;"
    },
    "platform": {
      "deviceName": "Mac (Browser)"
      "language": "en-US",
      "platform": "MacIntel",
      "userLanguage": null,
      "systemLanguage": null,
      "fonts": "cursive;monospace;sans-serif;fantasy;Arial;Arial Black;Arial Narrow;Arial Rounded MT Bold;Comic Sans MS;Courier;Courier New;Georgia;Impact;Papyrus;Tahoma;Trebuchet MS;Verdana;",
      "timezone": 300
    }
  },
  "location": {
    "latitude": 30.49843,
    "longitude": -97.639371
  }
}

Create a custom collector

  1. Create an instance of FRDevice and customize the profile:

    const device = new FRDevice({
      // Example customization:
      // Collect just the presence of Arial and Helvetica
      fontNames: [ 'Arial', 'Helvetica' ],
      // Do not collect any display properties
      displayProps: [],
      // Just collect User Agent
      browserProps: [ 'userAgent' ]
    });
  2. Replace the methods for further customization:

    device.getHardwareMeta = function () {
      let obj;
      // Custom logic to collect hardware profile obj
      return obj;
    }
  3. Run the getProfile() method using the custom configuration and collector methods:

    const profile = await device.getProfile({
      location: isLocationRequired,
      metadata: isMetadataRequired,
    });

Device profile attributes

By default, the Ping SDK collects the following device attributes:

Attribute Value

identifier

A unique ID for the device.

To learn more about the device identifier, refer to Uniquely identifying devices.

location

The location of a device (longitude and latitude values).

This is configured in the node and requires user permissions.

metadata

Metadata for the device, including:

platform

The device OS, such as Android or iOS.

deviceName

The name of the device.

locale

The locale of the device, such as en.

timeZone

The time zone of the device, such as Africa/Johannesburg.

brand

The brand of the device, such as Apple.

jailBreakScore

A value between 0.0 and 1.0 that denotes the tampering level for a device.

Obtain user permission for the device location

Your app requires the user’s authorization to access the device location.

If the user denies location access, the SDK still collects the device profile data; however, the collected data will not include any location coordinates.

If the user provides the permission, the SDK collects the location coordinates.

Known limitations

  • Location access requires user permissions. If the user denies permission or doesn’t respond to the browser’s request in time, geolocation coordinates are not collected and the profile is generated without it.

  • Generating the profile can take time, especially when the location is requested. If this is a step all to itself, showing a spinner with message is recommended.

  • To reduce authentication round-trips/latency, you can collect the device profile at the same time you collect other information.

  • The device profile ID is generated if one doesn’t exist, and stored in the browser’s localStorage. If this is done within a browser’s "private" or "incognito" mode, the ID does not persist once that window is closed. This creates a new ID, and therefore a new profile, when the profile is generated again.

  • As JavaScript runs within a browser, it is more a browser profile than a device profile. A different browser on the same device produces a substantially different profile.

  • Some profile attributes are more volatile than others. Plugging an external display into a laptop, for example, alters the generated profile.

More information

Prevent device data from appearing in audit logs

When using device profiling as part of your authentication journeys, the captured information is included in the audit logs by default.

You can configure PingAM to filter out this information to ensure no personally identifiable information (PII) is written to the audit logs.

The following JSON is a sample audit log entry, from the authentication topic:

{
  "_id": "c12f6ef2-262e-4263-b924-ed2236365d1a-1276",
  "timestamp": "2020-07-01T16:57:43.565Z",
  "eventName": "{am_name}-NODE-LOGIN-COMPLETED",
  "transactionId": "c12f6ef2-262e-4263-b924-ed2236365d1a-1274",
  "trackingIds": [
    "c12f6ef2-262e-4263-b924-ed2236365d1a-1259"
  ],
  "principal": [
    "bjensen"
  ],
  "entries": [
    {
      "info": {
        "nodeOutcome": "outcome",
        "treeName": "Test",
        "displayName": "Device Profile Collector",
        "nodeType": "DeviceProfileCollectorNode",
        "nodeId": "b9c49dc6-e557-4f98-bb05-504cd715e8d9",
        "authLevel": "0",
        "nodeExtraLogging": {
          "forgeRock.device.profile": {
            "identifier": "f505e455f33004c9-01ab094b8797382b1fab71cc8b3753ffb2bd774b",
            "version": "1.0",
            "metadata": {
              "platform": {
                "platform": "Android",
                ...

In the sample above, you can see the start of the device profile data, under the nodeExtraLogging entry.

You can filter this out of the audit logs, by using JSON pointer-like syntax:

  1. Log in to the PingAM console as an administrator, for example amAdmin.

  2. Navigate to Configure > Global Services > Audit Logging.

  3. In the Field blacklist filters list, add an entry that starts with the relevant topic, and then a JSON-pointer like syntax to specify the data to exclude.

    For example, to exclude the device data from audit logs, enter:

    /authentication/entries/0/info/nodeExtraLogging/forgeRock.device.profile
  4. Save your changes.

    Device profile data will no longer appear in the authentication audit logs.

More information

Set up social login

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

What is social login?

We provide the capability within authentication journeys/trees to support trusted Identity Providers (IdP), like Apple, Facebook, Google, and many others, for authentication and identity verification on behalf of ForgeRock. This is often referred to as social login or social authentication. These IdPs return the necessary user information to your server.

Depending on the device platform—​Android, iOS, or web—​the user is either redirected from the current web application, or the login page to the IdP’s authorization server, or, if on a native mobile app, the user is directed to the IdP’s authentication SDK, if available. Once on the IdP through a web page or the SDK, the user authenticates, and provides the necessary consent required for sharing the information with ForgeRock. When complete, the user is redirected back to your app or to the server to complete the authentication journey.

It’s common to offer these social login options in addition to traditional authentication with username and password, but they can be used alone.

A screen capture of a login page with a common combination of methods:

Social login

Limitations

Before implementing social login, read Limitations.

Support matrices

Platform matrix

Ping SDK for JavaScript Ping SDK for Android Ping SDK for iOS

SDK Version

3.0.0 and above

3.0.0 and above

3.0.0 and above

PingAM Version

6.5.2 and above

7.1.0 and above

7.1.0 and above

Platform Setup

Not required

Required

Required

Callback matrix

Ping SDK for JavaScript Ping SDK for Android Ping SDK for iOS

SelectIdPCallback

Yes

Yes

Yes

IdPCallback

No

Yes

Yes

RedirectCallback

Yes

No

No

Supported providers matrix

The Ping SDK social login feature supports the following providers:

Ping SDK for JavaScript Ping SDK for Android Ping SDK for iOS

Google

Yes

Yes

Yes

Facebook

Yes

Yes

Yes

Apple

Yes

Yes

Yes

Sign in with Apple is only supported on iOS 12+ devices.

Instructions

This how-to covers setting up an PingOne Advanced Identity Cloud tenant with the IdPs that the SDKs support: Apple, Facebook, and Google.

PingOne Advanced Identity Cloud supports additional IdPs.

Configure social login identity providers

In this section, you set your identity providers (IdP) to work with your apps through PingOne Advanced Identity Cloud.

Create an Apple client

Sign up for an Apple developer account

You must enroll in the Apple Developer program.

Apple Developer Enterprise Program accounts are not able to configure Sign in with Apple.

Set up application redirection

After Apple processes the initial authorization request and the user is successfully authenticated, Sign in with Apple sends an HTTP POST request to PingOne Advanced Identity Cloud or PingAM containing the authorization results.

For a web application (SPA) or an Android device, the POST request is sent to a dynamically created endpoint, specified in the Apple Sign In configuration as the redirect URL.

The redirect URL

To complete Apple client set up, you need the full redirect URL. This URL is not made available until you fully set up the provider in PingAM. If you have already set up your Apple provider, the redirect URL resembles the following:

https://<tenant-env-fqdn>/am/oauth2/<realm>/client/form_post/<secondary-configuration-name>

Set up Apple sign in

Create an app ID
  1. Log in to your Apple developer account.

  2. In the Program resources category, under Certificates, Identifiers & Profiles, click Identifiers.

  3. Click the plus button () next to the Identifiers header.

  4. Select App IDs, and click Continue.

  5. Select App type, and click Continue.

  6. Type a description of your app, and provide a Bundle ID using reverse-domain name style.

    For example com.forgerock.ios.sdk.example.

  7. Select Sign in with Apple, and click Continue.

  8. Review your entry, and click Register.

Create a service ID
  1. On the Identifiers page, click the plus button () next to the Identifier header.

  2. Select Service IDs, and click Continue.

  3. Enter a description of your service.

  4. Enter an Identifier that is similar to your app ID.

    For example, <app-id>.service.

  5. Click Continue.

  6. Review your entry, and click Register.

Configure the Apple sign in service
  1. On the Identifiers page, click the dropdown next to the magnifying glass icon, and then select Services IDs.

  2. Select the service ID you created.

  3. Next to Sign in with Apple, click Configure.

  4. Click the plus button next to the Website URLs header.

  5. In Domains and Subdomains:

    • For JavaScript apps, enter the domains that host your app.

      For example, sdkapp.example.org

      During testing, do not use the example.com domain to host your application. Apple treats this domain differently than other domains, which can cause unexpected issues.

      Using example.org or any other domain does not present these same difficulties.

    • For native Android and iOS apps, enter the domain of your PingOne Advanced Identity Cloud or PingAM instance.

      For example, openam-forgerock-sdks.forgeblocks.com

  6. In Return URLs, enter the URL that Apple redirects users to after authentication.

    Users must be redirected back to PingOne Advanced Identity Cloud or PingAM to continue their authentication journey.

    The URL to use is dynamically created by PingOne Advanced Identity Cloud or PingAM when you configure identity providers, and uses the following syntax:

    Advanced Identity Cloud

    https://<tenant-env-fqdn>/am/oauth2/<realm>/client/form_post/<secondary-configuration-name>

    PingAM

    https://<am-fqdn>/openam/oauth2/client/form_post/<secondary-configuration-name>

  7. Click Next.

  8. Review, and click Done.

Create a key

Store your key in a safe location. You cannot download keys more than once.

  1. On the developer account page, in the left navigation panel, click Keys.

  2. Click the plus button next to the Keys header.

  3. Enter your key name, and select Sign in with Apple.

  4. Click Configure, select your primary app ID, and click Save.

  5. Click Continue.

  6. Review, and click Register.

Generate a client secret

The client secret for Apple sign is a JSON Web token (JWT). The JWT is more complex than a simple string. A common way of generating the JWT is to use the jwt/ruby-jwt library.

Before you create the JWT, you need to understand certain requirements. To learn about these requirements, see Apple’s documentation about generating and validating tokens.

Configure the client ID

  • For Native iOS: The client_id should be the AppID (bundle identifier) from the Apple Development portal.

  • For Web or Android: The client_id should be the ServiceID from the Apple Development portal.

Example signing script:

require "jwt"

key_file = [Key file name]
team_id = [Team ID]
client_id = [AppID or Service ID]
key_id = [Key ID]
validity_period = 180 # In days. Max 180 (6 months) according to Apple docs.

private_key = OpenSSL::PKey::EC.new IO.read key_file

token = JWT.encode(
    {
        iss: team_id,
        iat: Time.now.to_i,
        exp: Time.now.to_i + 86400 * validity_period,
        aud: "https://appleid.apple.com",
        sub: client_id
    },
    private_key,
    "ES256",
    header_fields=
    {
        kid: key_id
    }
)
puts token

Create a Facebook client

To use Facebook as an Identity Provider, visit the Facebook for Developers page, and follow these steps:

  1. Click the Create App button.

  2. Select Consumer for app type, and click Next.

  3. Enter your app’s display name and contact email.

  4. Click the Create app button.

  5. On the Add products to your app page, under Facebook Login, click Set up.

  6. In the left navigation panel, click Settings > Basic.

  7. Take note of the App ID and App secret values.

Generate a key hash

The default password for Android Studio is android.

  1. To generate a key hash value, in a terminal window, enter the following command:

    keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore | openssl sha1 -binary | openssl base64
  2. Note the key hash value for later use.

Configure an Android app

  1. On the developer apps page, double-click an Android app.

  2. In the left navigation panel, select Settings > Basic.

  3. At the bottom of the page, click Add platform, select Android, and click Next.

  4. In the Select Android Store dialog, select a store.

    For example, Google Play.

  5. Click Next.

  6. Scroll down to the Android section.

  7. In the Key hashes field, enter the key hash value you generated earlier.

  8. In the Package Names field, enter your app’s Google Play Package Name.

    The name is often a reverse domain name, such as com.example.app.

  9. In the Class Name field, enter your app’s class name.

  10. Click Save changes.

Configure an iOS app

  1. On the developer apps page, select iOS.

  2. Click Next.

  3. Enter your Bundle ID.

    The name is often a reverse domain name, such as com.example.app.

  4. Click Save changes.

  5. In the left navigation panel, under Facebook Login, select Quickstart.

  6. Click iOS.

  7. Read the information, and select your package manager.

  8. Click Next.

  9. Enter your Bundle ID.

  10. Click Save.

  11. Click Continue.

  12. Select your single sign-on settings.

  13. When you get to Configure Your info.plist, configure your info.plist file with the XML snippet that contains data for your app.

Create a Google client

To use Google as an IdP, visit Google’s API Dashboard, and follow these steps:

  1. In the left navigation, click Credentials.

  2. Click CREATE CREDENTIALS > OAuth client ID.

    For an Android app
    1. Select Android as the value for Application Type.

    2. In the Name field, type a name for this application.

    3. Enter the package name from the AndroidManifest.xml file.

    4. Enter the SHA-1 certificate fingerprint.

      Use the following command to get the fingerprint:

      keytool -keystore path-to-debug-or-production-keystore -list -v
    5. Click Create.

    For an iOS app
    1. Select iOS as the value for Application Type.

    2. In the Name field, type a name for this application.

    3. Enter the bundle id as listed in the app’s Info.plist file.

    4. If the app is listed in the Apple App Store, enter the Apple ID of the app.

    5. Enter the Team ID that Apple assigned to your team.

    6. Click Create.

    For a JavaScript app
    1. Select Web application as the value for Application Type.

    2. In the Name field, type a name for this application.

    3. Under Authorized JavaScript Origins, add the origins of the apps that use Google as an IdP.

      Origins include scheme, domain, and port.

    4. Under Authorized redirect URIs, add the full redirect URLs of your apps that handle the redirection from Google after user login.

    5. Click Create.

Native Android social authentication

To enable native Android social authentication, you must create two OAuth 2.0 clients in the Google API console:

  1. Create an OAuth 2.0 client for the Android application.

  2. Create an OAuth 2.0 client for PingAM to communicate with the Google APIs.

Set up PingOne Advanced Identity Cloud for social login

This page explains how to configure your PingOne Advanced Identity Cloud tenant to work with your social IdP.

Enable IdPs

  1. To access the configuration, log in to the PingOne Advanced Identity Cloud tenant as an administrator.

  2. Click Native Consoles.

  3. Click Access Management.

  4. Click Services.

  5. Click Social Identity Providers Service, or create it by clicking Add a Service.

    Once in the service, ensure it is enabled.

  6. To manage your IdPs, click the Secondary Configurations tab.

Setting up your configuration for providers is mostly the same, but there are a few differences with Apple that are described below.

You will likely need a configuration for each platform that you develop. Use a naming scheme that’s easy to remember, such as google_web, google_ios, google_android.

Configure Google and Facebook

  1. Under Secondary Configuration, click Add a Secondary Configuration, and choose the provider to configure.

    The following fields are required:

    Client ID

    This is the ID for the client registered with the IdP. For native Android, set up the client with the second OAuth set of credentials generated using the web configuration. For details, see Set up social login in Android apps.

    Client Secret

    This is the secret for the client registered with the IdP.

    Facebook

    Facebook provides a client secret.

    Android

    You must set up the secret with the second OAuth set of credentials generated using the Web configuration. For details, see Set up social login in Android apps.

    iOS

    The Google Credentials do not provide a client secret.

    Redirect URL

    The redirect URL/URI for your application after the user authenticates with the provider.

    Scope Delimiter

    " " (a literal space character)

  2. For native iOS and Android apps, set Enable Native Nonce to OFF.

    The option is ON by default.

  3. Click Create.

  4. Ensure that the configuration is enabled, and the Transform Script is set to Google Profile Normalization, or Facebook Profile Normalization.

Configure Apple

  1. Under Secondary Configuration, click Add a Secondary Configuration, and choose the Apple Provider Configuration.

    The following fields are required:

    Client ID

    For native iOS, use the app ID created in the Apple developer page.

    For Android or Web, use the service ID.

    Client Secret

    The JWT client secret you generated.

    Redirect URL
    Native iOS

    Use the <application-uri> of your app.

    The <application-uri> can vary as it does not impact the flow.

    Android or Web

    For PingAM, use <forgerock-url>/am/oauth2/client/form_post/<configuration-name>. For example, the configuration name here is apple_web. You can also add the redirect URI from the OAuth2.0 client settings.

    For IDC, use <forgerock-url>/am/oauth2/<realmName>/client/form_post/<configuration-name>.

    (Optional) Redirect after form post URL

    This is only needed for configurations that use an PingOne Advanced Identity Cloud or PingAM endpoint for handling the POST redirection from Apple. The value of this field is the URL/URI of the Android or web app handling the remainder of the login flow.

    Scope Delimiter

    " " (a literal space character)

    OAuth Scopes

    Include the value email in this field to include the user’s Apple ID email in the JWT response and the identity token.

    If you add the email scope after a user agrees to provide scopes, the JWT response and the identity token will not include the user’s Apple ID email.

    To troubleshoot when the JWT response does not include the user’s Apple ID email.

    1. Sign in with your Apple ID used for authentication at the Apple ID page.

    2. Under Security > Sign in with Apple, click Manage apps & websites > Apps & websites using Apple ID.

    3. Select the application you are developing using Sign in with Apple.

    4. Click Stop using Apple ID.

    5. Attempt to re-authenticate in your app.

    Well Known Endpoint

    https://appleid.apple.com/.well-known/openid-configuration

    Issuer

    https://appleid.apple.com

  2. Click Create to save your configuration.

  3. Add these values to complete the configuration:

    Response Mode
    Native iOS

    Use Default.

    Android or Web

    Use FORM_POST.

    Transform Script

    Ensure Apple Profile Normalization is selected.

Create your authentication journey

After configuring the provider services, the next step is to build your authentication journey. There are nearly unlimited number of ways to compose authentication journeys. This page covers the basics.

We have two different sets of authentication nodes that provide social login capabilities. This page focuses on the node sets supported by the Ping SDKs:

The Select Identity Provider node

The Select Identity Provider node has the following important settings.

Include local authentication

When enabled, this includes an additional "provider" in the SelectIdPCallback called localAuthentication. This also enables a different outcome of the node that you can connect to a local authentication path. The node will now have two outcomes: Social Authentication, and Local Authentication.

Filter enabled providers

This setting further reduces the number of providers passed to your application. This is often used when you have providers configured for different platforms, and you only want the platform specific providers sent to the application.

For more information, see the Select Identity Provider node reference.

Social Provider Handler node

The Social Provider Handler node has the following important settings:

Transformation Script

Set Normalized Profile to Managed User.

Client Type

If the browser is used for IdP authentication (always the case for JavaScript), BROWSER is the correct value. If you are using the IdP’s SDK with native mobile for authentication, then NATIVE is the correct choice.

For more information, see the Social Provider Handler node reference.

Create users for a specific realm

To create users from a specific realm, link the Create node and the Patch node to the IDM users and their realm.

For example, for users in the alpha realm, set the Create node and the Patch node as follows:

Create Object node

Set Identity Resource to managed/alpha_user.

Patch Object node

Set Identity Resource to managed/alpha_user.

For more information, see the Create Object node, and Patch Object node references.

A simple social authentication journey

Start with the simplest journey. A user is presented with a choice of providers, the choice is made, and the user is taken to the IdP to authenticate. Upon the user’s return, the user management capabilities read the identity information from the provider to verify, allow, or deny access to the system.

Social login journey

To add a choice of local authentication, enable the feature in the Select Identity Provider node, mentioned above, to create an alternative path for local credentials.

Social login journey with local credentials

Social authentication and local username-password journey

This is one of the more common authentication journeys seen across the web. This is a choice between using a third-party identity provider, or using first-party credentials with username and password directly. The UI for this combination looks like this:

Social login choice

To compose this type of journey, use a Page node to combine the following nodes:

  • Password Collector/Platform Password

  • Select Identity Provider (enable Include local authentication)

Social login journey with choice

Set up social login in Android apps

This page shows how to use the Ping SDK for Android with authentication journeys that provide social login and registration.

The first callback your app encounters is the SelectIdPCallback, which lets the user choose their IdP. You use the getProviders() method to display the available providers, and setValue when the user makes a choice:

List<SelectIdPCallback.IdPValue> providers = callback.getProviders();
callback.setValue(chosenProvider);

The next callback is the IdPCallback. You call the signIn() method on the IdPCallback class:

callback.signIn(null, new FRListener<Void>() {
  @Override
  public void onSuccess(Void result) {
    //proceed to next node
    node.next();
  }

  @Override
  public void onException(Exception e) {
    //handle error
  }
});

This method directs the user to authenticate with the IdP. When the user authenticates with that provider, the result is automatically added to the IdPCallback with the following methods:

idPCallback.setTokenType(String tokenType)
idPCallback.setToken(String token)

In order to override the automatic provider detection, and identify the returned provider manually, you must check the IdPClient provider value in the IdPCallback returned:

IdPHandler idPHandler = null;
  switch (callback.getProvider()) {
    case "facebook-android":
      idPHandler = new FacebookSignInHandler();
      break;
    case "google-android":
      idPHandler = new GoogleSignInHandler();
      break;
    case "apple-android":
      idPHandler = new AppleSignInHandler();
      break;
    default:
      //error
  }

//  If the handler has been found and initialised, call the following to perform login
callback.signIn(idPHandler, new FRListener<Void>() {
  ...
  //  Social Login flow is completed
}

SDK configuration

Google

For Google Sign-In, add the following dependency to the build.gradle file:

implementation 'com.google.android.gms:play-services-auth:20.5.0'

Facebook

For Facebook Login:

  1. Add the Facebook dependency to the build.gradle file:

    implementation 'com.facebook.android:facebook-login:16.0.0'
  2. Add the following to the AndroidManifest.xml file:

    <meta-data android:name="com.facebook.sdk.ApplicationId"
        android:value="@string/facebook_app_id"/>
    
    <meta-data android:name="com.facebook.sdk.ClientToken"
        android:value="@string/facebook_client_token"/>
    
    <activity android:name="com.facebook.FacebookActivity"
        android:configChanges=
        "keyboard|keyboardHidden|screenLayout|screenSize|orientation"
        android:label="@string/app_name" />
    <activity
        android:name="com.facebook.CustomTabActivity"
        android:exported="true">
        <intent-filter>
          <action android:name="android.intent.action.VIEW" />
          <category android:name="android.intent.category.DEFAULT" />
          <category android:name="android.intent.category.BROWSABLE" />
          <data android:scheme="@string/fb_login_protocol_scheme" />
        </intent-filter>
    </activity>
  3. Add the following attributes to the string.xml file:

    <string name="facebook_app_id">Your Facebook App ID</string>
    <string name="fb_login_protocol_scheme">"fb" + Your Facebook App ID</string>
    <string name="facebook_client_token">Your Facebook client token</string>

    To find the values for facebook_app_id and fb_login_protocol_scheme, go to the Facebook Developer Console, and copy the App ID value:

    Obtain the App ID from the Facebook developer console

    Prefix your App ID value with fb to get the fb_login_protocol_scheme value.

    For example, if your App ID is 123456781234567, your fb_login_protocol_scheme is fb123456781234567.

    To find the value for facebook_client_token, go to the Facebook Developer Console, select your app, then navigate to Settings > Advanced > Security. Copy the Client token value:

    Obtain the client tokenfrom the Facebook developer console

    For example:

    <string name="facebook_app_id">123456781234567</string>
    <string name="fb_login_protocol_scheme">fb123456781234567</string>
    <string name="facebook_client_token">ab12cd34ef56ab78ab12cd34ef56ab78</string>

Apple

For Sign in with Apple:

  1. Add the AppAuth dependency to the build.gradle file:

    implementation 'net.openid:appauth:0.7.1'
  2. Add the following to the AndroidManifest.xml file:

    <activity
        android:name="net.openid.appauth.RedirectUriReceiverActivity"
        tools:node="replace"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="@string/apple_scheme" />
        </intent-filter>
    </activity>
  3. Add the following attribute to the string.xml file:

    <string name="apple_scheme">Your Redirect after form post URL Scheme</string>

    To find the value of apple_sheme, go to Services > Social Identity Provider Service > Secondary Configurations > Apple:

    Apple redirect after form post URL scheme

    In this example, the scheme would be org.forgerock.demo.

Set up social login in iOS apps

This page shows how to use the Ping SDK for iOS with authentication journeys that provide social login and registration.

Setup the social providers

Configure Facebook

  1. Create a Facebook client for iOS.

    Facebook provides you with the .plist configuration.

  2. Follow the instructions on the page and copy the values in your app’s Info.plist in Xcode.

    The final Info.plist file in your project, containing the Facebook generated Custom URL Scheme, and the LSApplicationQueriesSchemes, should look something like this:

    FB XcodePlist
  3. Include the FRFacebookSignIn module in your project.

    The FRFacebookSignIn is a new module that is distributed separately of FRAuth.

    Assuming you are using CocoaPods, add the following lines in your projects Podfile:

    pod 'FRAuth'
    pod 'FRFacebookSignIn'
    ...
    ... Other Pods
    ...
  4. Run the following command to install pods:

    pod install
    Alternatively, you can add the FRFacebookSignIn module to your project using the Swift Package Manager in Xcode.
  5. Initialize the Facebook sign-in handler in your app’s AppDelegate file:

    1. Locate the following method:

      func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?)
    2. Add a call to the FacebookSignInHandler.application(_:didFinishLaunchingWithOptions:) method, before the return true line:

      FacebookSignInHandler.application(application, didFinishLaunchingWithOptions: launchOptions)

      The result might resemble the following:

      func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
      
          // Enable logs for all levels
          FRLog.setLogLevel([ .all])
      
          // Initialize the Facebook sign-in handler
          FacebookSignInHandler.application(application, didFinishLaunchingWithOptions: launchOptions)
      
          return true
      }

Configure Google

  1. Create a Google client for iOS.

    For details, refer to Create a Google client.

  2. Access the client in the Google Console, and make a note of the generated custom iOS URL scheme:

    Google ClientId
  3. Configure your Xcode project with the Google generated custom iOS URL scheme:

    1. Select your project file, select the app target, and in the Info pane, expand the URL Types option.

    2. Click on the icon to add a new custom URL scheme, and paste the generated URL scheme in the URL Scheme field.

    The configuration should look something like this:

    Google Xcode
  4. Include the FRGoogleSignIn module in your project.

    The FRGoogleSignIn is a new module that is distributed separately of FRAuth.

    Assuming you are using CocoaPods, add the following lines in your projects Podfile:

    pod 'FRAuth'
    pod 'FRGoogleSignIn'
    ...
    ... Other Pods
    ...
  5. Run the following command to install pods:

    pod install
The FRGoogleSignIn module is not available through the Swift Package Manager.

Configure Apple

  1. Create an Apple Client for iOS.

    For details, refer to Create an Apple client.

  2. Configure your Xcode project with the Google generated custom iOS URL scheme.

  3. Select your project file, select the app target and go to the Signing & Capabilities tab in Xcode.

  4. Click the + Capability button, and search for Sign In with Apple.

    After enabling the capability the Xcode page should look something like this.

    Apple Capabilities

Handle social login with the Ping SDK for iOS

After configuring social providers in PingOne Advanced Identity Cloud, and configuring your Xcode project to work with Facebook, Google, and Apple IdPs, you are ready to use the Ping SDK for iOS to authenticate.

The first callback your app encounters is the SelectIdPCallback, which lets the user choose their IdP. Use the providers array to display the available providers, and setProvider() method when the user makes a choice:

// Within your login flow
let selectIdPCallback = callback as? SelectIdPCallback
let providersArray = selectIdPCallback.providers

// display providers
// user makes choice

// Sets provider on the callback within `selectIdPCallback`
selectIdPCallback.setProvider(provider: providersArray[self.selectedIndex])
node.next { (user: FRUser?, node, error) in

    // Handle node

}

The next callback returned is the IdPCallback.

The SDK automatically identifies the correct IdP for authentication as long as the IdPClient, derived from the Social Identity Provider Service configuration in PingAM, contains facebook, google or apple. Detection is case-insensitive.

//  Node is returned with IdPCallback
let idpCallback = node.callbacks.first as! IdPCallback

//  Call the following to perform login
idpCallback.signIn(handler: nill, presentingViewController: self) {
  (token: String?, tokenType: String?, error: Error?) in

  //  Social Login flow is completed
  node.next { (user: FRUser?, node, error) in
    // Handle node
  }
}

To override the automatic provider detection and identify the returned provider manually, check the IdPClient provider value in the returned IdPCallback as shown in the example below:

//  Node is returned with IdPCallback
let idpCallback = node.callbacks.first as! IdPCallback
//  Based on IdPClient in IdPCallback, choose the correct handler
var handler: IdPHandler?
if idpCallback.idpClient.provider == "facebook-ios" {
  handler = FacebookSignInHandler()
} else if idpCallback.idpClient.provider == "google-ios" {
  handler = GoogleSignInHandler()
} else if idpCallback.idpClient.provider == "apple-ios" {
  handler = AppleSignInHandler()
} else {
  throw error
}

//  If the handler has been found and initialized, call the following to perform login
idpCallback.signIn(handler: handler, presentingViewController: self) {
  (token: String?, tokenType: String?, error: Error?) in

  //  Social Login flow is completed
  node.next { (user: FRUser?, node, error) in
    // Handle node
  }
}

Set up social login in JavaScript apps

This page shows how to use the Ping SDK for JavaScript with authentication journeys that provide social login and registration.

The first callback your app encounters is the SelectIdPCallback, which lets the user choose their IdP. Use the getProviders() method to display the available providers, and setProvider() when the user makes a choice:

// Within your login flow
const cb = step.getCallbackOfType(‘SelectIdPCallback’)
const providers = cb.getProviders();

// display providers
// user makes choice

// Sets provider on the callback within `step`
cb.setProvider(chosenProvider);

FRAuth.next(step);

The next callback is the RedirectCallback. Detect the presence of the callback, and call a special redirect() method on the FRAuth class, passing the whole step object as the argument:

// Within your login flow
if (step.getCallbackOfType('RedirectCallback')) {
  FRAuth.redirect(step);
}

This triggers a full browser redirect to the IdP. When the user authenticates with the IdP, they are redirected back to the app. When your application handles this redirect, check for the query parameters code and state for Facebook and Google, or form_post_entry for Apple. If they are present, call the resume() method on the FRAuth class:

// Application route handler for redirection from provider
// `code`, `state` and `form_post_entry` are "variablized" from URL
if ((code && state) || form_post_entry) {
  step = FRAuth.resume(window.location.href);
}

The resume() method gathers the appropriate URL information, and information from the previous step saved to browser storage prior to the redirect in order to properly resume the authentication journey.

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'}});

Set up transactional authorization

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

The Ping SDKs have builtin support for transactional authorization.

Transactional authorization requires a user to authorize every access to a resource. It is part of an PingAM policy that grants single-use or one-shot access.

For example, a user might approve a financial transaction with a one-time password (OTP) sent to their device, or respond to a push notification to confirm that they have indeed signed on from an unexpected location.

Performing the additional action successfully grants access to the protected resource but only once. Additional attempts to access the resource require the user to perform the configured actions again.

How does transactional authorization work?

The following diagram shows the flow used during transactional authorization:

transactional-how-it-works

When the Ping SDKs attempt to access a resource protected with transactional authorization, PingAM returns JSON that has an empty actions attribute. A unique transaction ID (TxId) is also included under /advices/TransactionConditionAdvice.

For example:

{
    "resource": "https://app-backend.example.com:8000/protected/feature/",
    "actions": {},
    "attributes": {},
    "advices": {
        "TransactionConditionAdvice": [
            "7b8bfd4c-60fe-4271-928d-d09b94496f84"
        ]
    },
    "ttl": 0
}

The Ping SDKs detect that transactional authorization is required, and make a call to the /authenticate endpoint to begin to fulfil the requirements specified in the policy protecting the resource. The call must include the TxId value originally received from PingAM.

PingAM responds to the request with a series of required callbacks to fulfil the policy.

Each callback is handled by the SDK; for example, by rendering UI for the user to complete, or responding to a push notification.

When all the callbacks have been completed, the SDK attempts to access the protected resource again, using the same session or OAuth 2.0 token as before. The SDK adds the transaction ID into the policy evaluation as an environment property:

{
    "resources" : ["https://app-backend.example.com:8000/protected/feature/"],
    "application" : "iPlanetAMWebAgentService",
    "subject" : {
        "ssoToken" : "AQIC5w....*AJTMQAA*"
    },
    "environment": {
        "TxId": ["77b8bfd4c-60fe-4271-928d-d09b94496f84"]
    }
}

As the transaction ID matches an entry in PingAM’s completed transaction list, PingAM returns a new policy evaluation result, including the actions the SDK-based application can now perform:

{
    "resource": "https://app-backend.example.com:8000/protected/feature/",
    "actions": {
        "POST": true,
        "GET": true
    },
    "attributes": {},
    "advices": {},
    "ttl": 0
}

For more information on transactional authorization, and how to set up PingAM to use it, see Transactional authorization in the PingAM documentation.

Handle transactions in an Android app

In this example, an API is protected by PingGateway and PingAM.

The example adds a x-authenticate-response header to the access request. This header causes IG to return the advice as JSON in a header named Www-Authenticate.

The SDK provides interceptors for handling the returned advice:

After obtaining the advice, pass it to the authenticate() method of the FRSession class.

The following example shows how to use the interceptors to handle the advice using OKHttpClient:

val builder: OkHttpClient.Builder = OkHttpClient.Builder()

builder.addInterceptor(object: IdentityGatewayAdviceInterceptor() {
  override fun getAdviceHandler(advice: PolicyAdvice): AdviceHandler {
    return object: AdviceHandler {
      override suspend fun onAdviceReceived(
        context: Context,
        advice: PolicyAdvice) {
        // Authenticate the advice with
        // FRSession.getCurrentSession().authenticate(context, advice, ...)
      }
    }
  }
})

builder.cookieJar(SecureCookieJar.builder().context(context).build())

val client: OkHttpClient = builder.build()
val requestBuilder: Request.Builder = Request.Builder().url(api)

requestBuilder.addHeader("x-authenticate-response", "header");

val request = requestBuilder.build()

client.newCall(request).enqueue(object: Callback {
  override fun onFailure(call: Call, e: IOException) {
    // Handle Failure
  }

  override fun onResponse(call: Call, response: Response) {
    // Handle Response
  }
})

Handle transactions in an iOS app

The following steps demonstrate how to handle transactional authorization in the Ping SDK for iOS.

This example assumes interaction directly with PingAM.

If the resource server is protected by IG, and routes are configured for protected resources, the optional steps are not required, as the SDK is able to deal directly with the responses from IG.

  1. Create an AuthorizationPolicy with the URL to evaluate policies against, and a delegate of AuthorizationPolicyDelegate:

    let authPolicy = AuthorizationPolicy(
      validatingURL: ["https://protectedendpoint"],
      delegate: self
    )
  2. Add AuthorizationPolicy to FRURLProtocol

    FRURLProtocol.authorizationPolicy = authPolicy
  3. Register the FRURLProtocol class:

    URLProtocol.registerClass(FRURLProtocol.self)
  4. Create a URLSessionConfiguration with FRURLProtocol, and create a URLSession with the configuration.

    (Optional) If using IG, add the x-authenticate-response header so that IG returns the advice response as JSON in a header named Www-Authenticate, rather than as a redirect with query parameters:

    // Configure FRURLProtocol for HTTP client
    let config = URLSessionConfiguration.default
    config.protocolClasses = [FRURLProtocol.self]
    
    var request = URLRequest(url: "https://protectedendpoint")
    request.setValue("header", forHTTPHeaderField: "x-authenticate-response")
    
    self.urlSession = URLSession(configuration: config)
    self.urlSession.dataTask(with: request) { (data, response, error) in }.resume()
  5. (Optional) If the SDK is unable to parse the response into policyAdvice, construct it with the given response by implementing the AuthorizationPolicyDelegate.evaluateAuthorizationPolicy() method:

    extension YourClass: AuthorizationPolicyDelegate {
      func evaluateAuthorizationPolicy(
        responseData: Data?,
        response: URLResponse?,
        error: Error?
      ) -> PolicyAdvice? {
        if let httpResponse = response as? HTTPURLResponse,
          httpResponse.statusCode == 401,
          let json = httpResponse.allHeaderFields["Www-Authenticate"] as? String,
          let policyAdvice = PolicyAdviceCreator().parseAsBase64(advice: json)
        {
          return policyAdvice
        } else {
          // If PolicyAdvice cannot be constructed, return 'nil' to stop authZ
          return nil
        }
      }
    }
  6. Initiate authentication tree flow, including policyAdvice, by implementing the AuthorizationPolicyDelegate.onPolicyAdviseReceived() method:

    extension YourClass: AuthorizationPolicyDelegate {
      func onPolicyAdviseReceived(
        policyAdvice: PolicyAdvice, completion: @escaping FRCompletionResultCallback
      ) {
        FRSession.authenticate(policyAdvice: policyAdvice) { (token: Token?, node, error) in
          if error != nil {
            //Authentication failed
            completion(false)
            return
          }
          if let _ = token {
            completion(true)
          } else {
            // Handle node.
            // At the end of the authentication, you should get back a Token.
            // In this case you will need to call the completion handler
          }
        }
      }
    }
  7. (Optional) Decorate the original URLRequest object with updated information, by implementing the AuthorizationPolicyDelegate.updateRequest() method:

    extension YourClass: AuthorizationPolicyDelegate {
      func updateRequest(originalRequest: URLRequest, txId: String?) -> URLRequest {
        // append txId into the request
        return request
      }
    }

    If a delegation method is not defined, the SDK appends the _txid query parameter automatically to the URL.

Handle transactions in a JavaScript app

Transactional authorization is built into the HttpClient module of the Ping SDK for JavaScript.

The HttpClient module detects when transactional authorization is enabled, and depending on your setup, initiates interaction with either PingAM or IG.

Ensure you specify credentials: 'include', so that the request includes the necessary cookies.

When transactional authorization is enabled and callbacks are returned, your client app must implement the necessary user interaction. Ensure that you iterate through returned callbacks until you receive a success or failure response.

When you do receive a success response, make a new request to the initial resource endpoint, which will now be authorized.

The following code shows a sample JavaScript implementation. The included authorization middleware handles the callbacks that the configured transactional authorization returns:

console.log('Make a $200 withdrawal from account');
return HttpClient.request({
    init: {
        method: 'POST',
        body: JSON.stringify({ amount: '200' }),
        credentials: 'include',
    },
    authorization: {
        handleStep: async (step) => {
            console.log('Withdrawal endpoint is set up for transational authorization...');
            step.getCallbackOfType('ValidatedCreateUsernameCallback').setName(un);
            step.getCallbackOfType('ValidatedCreatePasswordCallback').setPassword(pw);
            return Promise.resolve(step);
        },
    },
    timeout: 0,
    url: `${resourceUrl}/withdraw`,
});

Set up QR code handling

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

The Ping SDK for JavaScript has the following methods for handling QR codes:

FRQRCode.isQRCodeStep(step)

For determining if a step requires a QR code.

FRQRCode.getQRCodeData(step)

For extracting the QR code data from the step, such as the URI.

Example:

// Import the module
import { FRQRCode } from ‘@forgerock/javascript-sdk’;

// Determine if a step is a QR code step
const isQRCode: boolean = FRQRCode.isQRCodeStep(step);

if (isQRCode) {
  // Extract QR code data
  const data: {
    message: string,
    use: string,
    uri: string
  } = FRQRCode.getQRCodeData(step);

  // Render a QR code using the `data` from the step

}

Integrating with the Ping SDKs

You can integrate the Ping SDKs with many frameworks and libraries.

Integrate with PingOne Protect

The Ping SDKs can integrate with PingOne Protect to evaluate the risk involved in a transaction.

Use PingOne Protect in your authentication journeys to help prevent identity fraud by incorporating advanced features and real-time detection.

Obtain OAuth 2.0 tokens from with PingOne

The Ping SDK for JavaScript can integrate with PingOne to obtain OAuth 2.0 access tokens on behalf of users.

Follow the steps to setup a sample application that redirects users to PingOne to authenticate, then returns to the client application to obtain an access token and the user’s info.

PingOne Advanced Identity Cloud hosted pages

Learn how to use the SDKs to leverage the hosted pages functionality of PingOne Advanced Identity Cloud.

Get practical advice on theming, see how the SDKs help the integration, and pick up some best practices along the way.

Design a protected system

Authentication, sessions, cookies, OAuth 2.0, authorization code flow, and so on.

This page explains how to make sense of the complexity.

Integrate the Authenticator module

The Ping (ForgeRock) Authenticator module lets you build the power of the ForgeRock Authenticator application into your own apps with ease.

The module supports time-based and HMAC-based one-time passwords, and push authentication.

Protect a Flutter iOS app

This tutorial covers the basics of developing a ForgeRock-protected, mobile app with Flutter.

It focuses on developing the iOS bridge code along with a minimal Flutter UI to authenticate a user.

Protect a React Native iOS app

This tutorial covers the basics of developing a ForgeRock-protected, mobile app with React Native.

This part focuses on developing the iOS bridge code along with a minimal React UI to authenticate a user.

Protect a React web app

This tutorial leads you through the process of integrating the Ping SDKs into a sample React.js single-page application with a Node.js REST API.

Protect an Angular web app

This tutorial leads you through the process of integrating the Ping SDKs into a sample Angular single-page application with a Node.js REST API.

Integrate with PingOne Protect for risk evaluations

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

The Ping SDKs can integrate with PingOne Protect to evaluate the risk involved in a transaction.

PingOne Protect is supported in the following servers:

Advanced Identity Cloud

Use the official PingOne Protect nodes

PingAM 7.5 and later

Use the official PingOne Protect nodes

PingAM 7.2 - 7.4

Use the marketplace PingOne Protect nodes

A flowchart illustrating how risk predictors evaluate many different data points to determine whether to allow a user access or prompt mitigation.
Figure 48. A flowchart illustrating how risk predictors evaluate many different data points.

You can instruct the Ping SDKs to use the embedded PingOne Signals SDK to gather information during a transaction. Your authentication journeys can then gather this information together and request a risk evaluation from PingOne.

Based on the response, you can choose whether to allow or deny the transaction or perform additional mitigation, such as bot detection measures.

You can use the audit functionality in PingOne to view the risk evaluations:

Risk evaluation records in the PingOne audit viewer.
Figure 49. Risk evaluation records in the PingOne audit viewer.

Steps

Step 1. Set up the servers

In this step, you set up your PingOne Advanced Identity Cloud or PingAM server, and your PingOne instance to perform risk evaluations.

For example, you create a worker application in PingOne and configure your server to access it. You also create an authentication journey that uses the relevant nodes.

Step 2. Install dependencies

In this step, you add the required PingOne Protect module and dependencies to your project.

We provide instructions for Android, iOS, and JavaScript projects.

Step 3. Develop the client app

With everything prepared, you can now add Ping SDK code to your client application to evaluate risk by using PingOne Protect.

You’ll learn how to initialize the collection of contextual data, gather and send it to the server for a risk evaluation, and how to pause and resume behavioral data collection.

Step 1. Set up the servers

In this step, you set up your PingOne Advanced Identity Cloud or PingAM server, and your PingOne instance to perform risk evaluations.

Create a worker application in PingOne

To allow your server to access the PingOne administration API you must create a worker application in PingOne.

The worker application provides the client credentials your server uses to communicate with the PingOne admin APIs using the OpenID Connect protocol.

To create a worker application in PingOne:

  1. In the PingOne administration console, navigate to Applications  Applications, and then click Add ().

  2. In the Add Application panel:

    1. In Application name, enter a unique identifier for the worker application.

      For example, Ping SDK Worker.

    2. Optionally, enter a Description for the application and select an Icon.

      These do not affect the operation of the worker application but do help you identify it in the list.

    3. In Application Type, select Worker.

    4. Click Save.

  3. In the application properties panel for the worker application you created:

    1. On the Roles tab, click Grant Roles.

    2. On the Available responsibilities tab, select the Identity Data Admin row, and ensure the environment is correct.

    3. Click Save.

    4. On the Overview tab, ensure your worker application resembles the following image, and then enable it by using the toggle (1):

      Example worker application in PingOne
      Figure 50. Example worker application in PingOne
    5. Make a note of the Environment ID, Client ID, and Client Secret values (2).

      You need these values in the next step when you Configure the PingOne Worker service in your server.

Configure the PingOne Worker service in your server

After you create a worker application in PingOne, you must configure the PingOne Worker service in your server with the credentials.

Prerequisites

You need the following values from the PingOne Worker application you created in PingOne:

Client ID

Client ID of the worker application in PingOne.

Example: 6c7eb89a-66e9-ab12-cd34-eeaf795650b2

Client Secret

Client secret of the worker application in PingOne.

Use the Secret Mask () or Copy to Clipboard () buttons to obtain the value in the PingOne administration console.

Example: Ch15~o5Hm8N4_eS_m8~ARrV0KQAIQS6d.sJWe8TMXurEb~KWexY_p0gelR

Environment ID

Identifier of the environment that contains the worker application in PingOne.

Example: 3072206d-c6ce-ch15-m0nd-f87e972c7cc3

The PingOne Worker Service requires a configured OAuth2 provider service in your server.

  • If you are using a self-managed AM server, you must Configure the OAuth 2.0 provider.

  • The OAuth2 provider service is preconfigured in Advanced Identity Cloud.

Register the client secret in the server

You need to make the client secret of the worker application in PingOne available for use in the PingOne worker service.

Advanced Identity Cloud

If you are using Advanced Identity Cloud you will need to create an environment secret to hold the client secret value, as follows:

  1. In the PingOne Advanced Identity Cloud admin UI, go to Tenant Settings > Global Settings > Environment Secrets & Variables.

  2. Click the Secrets tab.

  3. Click + Add Secret.

  4. In the Add a Secret modal window, enter the following information:

    Name

    Enter a secret name. For example, ping-protect-client-secret.

    Secret names cannot be modified after the secret has been created.

    Description

    (optional) Enter a description of the purpose of the secret.

    Value

    Enter the Client Secret value you obtained when creating the worker application in PingOne.

    For example, Ch15~o5Hm8N4_eS_m8~ARrV0KQAIQS6d.sJWe8TMXurEb~KWexY_p0gelR.

    The field obscures the secret value by default. You can optionally click the visibility toggle () to view the secret value as you enter it.

  5. Click Save to create the variable.

  6. Click View Update, check the details of the new secret, and then click Apply Update.

    Advanced Identity Cloud displays a final confirmation page.

    aic save esv secret en
    Figure 51. Apply updated secrets in Advanced Identity Cloud
  7. Click Apply Now.

    Advanced Identity Cloud propagates the new secret and its value to all servers. You must wait until the secrets have propagated throughout the environment before attempting to use the secret.

    The Environment Secrets & Variables page displays the following message while the update is in progress:

    aic esv propagate en
    Figure 52. Propagating secrets in progress in Advanced Identity Cloud.
Self-managed AM

For information on adding secret values for use in services in a self-managed AM instance, refer to Create key aliases in the AM documentation.

Configure the PingOne worker service

To configure the PingOne worker service:

  1. If you are using PingOne Advanced Identity Cloud, in the administration console navigate to Native Consoles > Access Management.

  2. In the AM admin UI, click Services.

  3. If the PingOne Worker Service is in the list of services, select it.

  4. If you do not yet have a PingOne Worker Service:

    1. Click Add a Service.

    2. In Choose a service type, select PingOne Worker Service, and then click Create.

  5. On the Secondary Configurations tab, click Add a Secondary Configuration.

  6. On the New workers configuration page:

    1. Enter a Name for the configuration.

      For example, SDK PingOne Worker.

      You use this value when you configure an authentication journey that performs risk evaluations.

    2. In Client ID, enter the client ID of the PingOne Worker application you created earlier.

    3. In Client Secret Label Identifier, enter an identifier to create a specific secret label to represent the client secret of the worker application.

      For example, workerAppClientSecret.

      The secret label uses the template am.services.pingone.worker.identifier.clientsecret where identifier is the Client Secret Label Identifier value.

      This field can only contain characters a-z, A-Z, 0-9, and . and can’t start or end with a period.

    4. In Environment ID, enter the environment ID containing the PingOne Worker application you created earlier.

    5. Click Create

  7. On the Workers Configuration page, ensure that the PingOne API Server URL and PingOne Authorization Server URL are correct for the region of your PingOne servers:

    PingOne URLs by region
    Region Authorization URL API URL

    North America

    (Excluding Canada)

    https://auth.pingone.com

    https://api.pingone.com/v1

    Canada

    https://auth.pingone.ca

    https://api.pingone.ca/v1

    Europe

    https://auth.pingone.eu

    https://api.pingone.eu/v1

    Asia-Pacific

    https://auth.pingone.asia

    https://api.pingone.asia/v1

  8. Confirm your configuration resembles the image below, and then click Save changes.

    Example worker service configuration in AM
    Figure 53. Example worker application in PingOne

Map the Client Secret Label Identifier to a secret

To make the client secret available to the PingOne Worker Service, you must map the secret to the ID created.

Map secrets in Advanced Identity Cloud

  1. In the PingOne Advanced Identity Cloud admin UI, click Native Consoles > Access Management.

  2. In the AM admin UI (native console), go to Realm > Secret Stores.

  3. Click the ESV secret store, then click Mappings.

  4. Click + Add Mapping.

    1. In Secret Label, select the label generated when you entered the Client Secret Label Identifier previously.

      For example, am.services.pingone.worker.workerAppClientSecret.clientsecret.

    2. In aliases, enter the name of the ESV secret you created earlier, including the esv- prefix, and then click Add.

      For example, esv-ping-protect-client-secret

    The result resembles the following:

    Example mapping of the Client Secret Label Identifier value.

  5. Click Create.

To learn more about mapping secrets and label identifiers in Advanced Identity Cloud, refer to Secret labels.

Map secrets in self-managed AM

To learn about mapping secrets in self-managed AM, refer to Map and rotate secrets.

You have now configured the PingOne Worker service in your server. You can now Configure a journey to perform PingOne Protect risk evaluations.

Configure a journey to perform PingOne Protect risk evaluations

To make risk evaluations in PingOne, you must configure an authentication journey in your server.

The following table covers the authentication nodes and callbacks for integrating your authentication journeys with PingOne Protect.

Node Callback Description

PingOne Protect Initialization node

PingOneProtectInitiateCallback

Instruct the embedded PingOne Signals SDK to start gathering contextual information.

PingOne Protect Evaluation node

PingOneProtectEvaluationCallback

Returns contextual information that the server can send to your PingOne Protect instance to perform a risk evaluation.

PingOne Protect Result node

Non-interactive

Inform the PingOne Protect instance about the status of the transaction.

These official PingOne Protect nodes are available in PingAM 7.5 and later, as well as PingOne Advanced Identity Cloud.

If you are using PingAM versions 7.2 to 7.4, you should instead use the equivalent PingOne Protect Marketplace nodes.

The PingOne Protect marketplace nodes use a MetadataCallback callback. The SDK recognizes the specific configuration the marketplace nodes place in this callback and can use it for use with PingOne Protect.

In your server, log in as an administrator and create a new authentication journey similar to the following example:

An example PingOne Protect journey
Figure 54. An example PingOne Protect journey
  • The PingOne Protect Initialize node 1 instructs the SDK to initialize the PingOne Protect Signals API with the configured properties.

    Initialize the PingOne Protect Signals API as early in the journey as possible, before any user interaction.

    This enables it to gather sufficient contextual data to make an informed risk evaluation.

    You can initialize the PingOne Protect Signals API whenever you want to start collecting data. This could be at application startup, or when a particular page or view is visited.

  • The user enters their credentials, which are verified against the identity store.

  • The PingOne Protect Evaluation node 2 performs a risk evaluation against a risk policy in PingOne.

    The example journey continues depending on the outcome:

    High

    The journey requests that the user respond to a push notification.

    Medium or Low

    The risk is not significant, so no further authentication factors are required.

    Exceeds Score Threshold

    The score returned is higher than the configured threshold and is considered too risky to complete successfully.

    Failure

    The risk evaluation could not be completed, so the authentication attempt continues to the Failure node.

    BOT_MITIGATION

    The risk evaluation returned a recommended action to check for the presence of a human, so the journey continues to a CAPTCHA node.

    ClientError

    The client returned an error when attempting to capture the data to perform a risk evaluation, so the authentication attempt continues to the Failure node.

  • An instance of the PingOne Protect Result node 3 returns the Success result to PingOne, which can be viewed in the audit console to help with analysis and risk policy tuning.

  • A second instance of the PingOne Protect Result node 4 returns the Failed result to PingOne, which can be viewed in the audit console to help with analysis and risk policy tuning.

You have now configured a suitable authentication journey in your server. You can now proceed to Step 2. Install dependencies.

Step 2. Install dependencies

To capture contextual data and perform risk evaluations, you must add the PingOne Protect module to your Ping SDK project.

Select your platform below for instructions on installing the required modules or dependencies:

Add Android dependencies

To add the PingOne Protect dependencies to your Android project:

  1. In the Project tree view of your Android Studio project, open the Gradle Scripts/build.gradle file for the module.

  2. In the dependencies section, add the required dependencies:

    Example dependencies section after editing:
    dependencies {
        // Ping SDK main module
        implementation 'org.forgerock:forgerock-auth:4.6.0'
    
        // PingOne Protect module
        implementation 'org.forgerock:ping-protect:4.6.0'
    }

After installing the module, you can proceed to Step 3. Develop the client app.

Add iOS dependencies

You can use CocoaPods or the Swift Package Manager to add the PingOne Protect dependencies to your iOS project.

Add dependencies using CocoaPods

  1. If you do not already have CocoaPods, install the latest version.

  2. If you do not already have a Podfile, in a terminal window, run the following command to create a new Podfile:

    pod init
  3. Add the following lines to your Podfile:

    pod 'PingProtect' // Add-on for {p1} Protect
  4. Run the following command to install pods:

    pod install

Add dependencies using Swift Package Manager

  1. With your project open in Xcode, select File > Add Package Dependencies.

  2. In the search bar, enter the Ping SDK for iOS repository URL: https://github.com/ForgeRock/forgerock-ios-sdk.

  3. Select the forgerock-ios-sdk package, and then click Add Package.

  4. In the Choose Package Products dialog, ensure that the PingProtect library is added to your target project.

  5. Click Add Package.

  6. In your project, import the library:

    // Import the {p1} Protect library
    import PingProtect

After installing the module, you can proceed to Step 3. Develop the client app.

Add JavaScript dependencies

Install the PingOne Protect module by using npm:

npm install @forgerock/ping-protect

After installing the module, you can proceed to Step 3. Develop the client app.

Step 3. Develop the client app

Integrating your application with PingOne Protect enables you to perform risk evaluations during your customer’s journey.

Add code for the following tasks to fully integrate with PingOne Protect:

Initialize data collection

You must initialize the PingOne Signals SDK so that it collects the data needed to evaluate risk.

The earlier you can initialize the PingOne Signals SDK, the more data it can collect to make a risk evaluation.

You can initialize the PingOne Signals SDK by using the start() method, which supports the following parameters:

Parameter

Description

Android

iOS

JavaScript

envID

Required. Your PingOne environment identifier.

deviceAttributesToIgnore

Optional. A list of device attributes to ignore when collecting device signals.

For example, AUDIO_OUTPUT_DEVICES or IS_ACCEPT_COOKIES.

isBehavioralDataCollection

behavioralDataCollection

When true, collect behavioral data.

Default is true.

isConsoleLogEnabled

consoleLogEnabled

When true, output SDK log messages in the developer console.

Default is false.

isLazyMetadata

lazyMetadata

When true, calculate metadata on demand rather than automatically after calling start.

Default is false.

N/A

deviceKeyRsyncIntervals

Number of days that device attestation can rely upon the device fallback key.

Default: 14

N/A

disableHub

When true, the client stores device data in the browser’s localStorage only.

When false the client uses an iframe.

Default is false.

N/A

disableTags

When true, the client does not collect tag data.

Tags are used to record the pages the user visited, forming a browsing history.

Default is false.

N/A

enableTrust

When true, tie the device payload to a non-extractable crypto key stored in the browser for content authenticity verification.

Default is false.

N/A

externalIdentifiers

Optional. A list of custom identifiers that are associated with the device entity in PingOne Protect.

N/A

hubUrl

Optional. The iframe URL to use for cross-storage device IDs.

N/A

waitForWindowLoad

When true, initialize the SDK on the load event, instead of the DOMContentLoaded event.

Default is true.

There are two options for initializing the PingOne Signals SDK:

Initialize manually

Call the start() method before users start interacting with your application to gather the most data and make the most informed risk evaluations.

Pass in the configuration parameters as required.

  • Android

  • iOS

  • JavaScript

try {
  val params =
    PIInitParams(
      envId = "3072206d-c6ce-ch15-m0nd-f87e972c7cc3",
    )
  PIProtect.start(context, params)
  Logger.info("Settings Protect", "Initialize succeeded")
} catch (e: Exception) {
  Logger.error("Initialize Error", e.message)
  throw e
}
let initParams = PIInitParams(envId: "3072206d-c6ce-ch15-m0nd-f87e972c7cc3")
  PIProtect.start(initParams: initParams) { error in
    if let error = error as? NSError {
        FRLog.e("Initialize error: \(error.localizedDescription)")
    } else {
        FRLog.i("Initialize succeeded")
    }
  }
import { PIProtect } from '@forgerock/ping-protect';

try {
  // Initialize PingOne Protect with manual configuration
  PIProtect.start({ envId: '3072206d-c6ce-ch15-m0nd-f87e972c7cc3' });
} catch (err) {
  console.error(err);
}

Initialize based on a callback

Not all authentication journeys perform risk evaluations, and therefore do not need to initialize data collection. You can choose to initialize capture of data on receipt of the PingOneProtectInitializeCallback callback rather than during app start up.

The callback also provides the configuration parameters.

  • Android

  • iOS

  • JavaScript

try {
  val callback =
    node.getCallback(PingOneProtectInitializeCallback::class.java)
  callback.start(context)
} catch (e: PingOneProtectInitException) {
  Logger.error("PingOneInitException", e, e.message)
} catch (e: Exception) {
  Logger.error("PingOneInitException", e, e.message)
  callback.setClientError(e.message);
}
node.next()
if callback.type == "PingOneProtectInitializeCallback",
  let pingOneProtectInitCallback = callback as? PingOneProtectInitializeCallback
{
  pingOneProtectInitCallback.start { result in
    DispatchQueue.main.async {
      var initResult = ""
      switch result {
      case .success:
        initResult = "Success"
      case .failure(let error):
        initResult = "Error: \(error.localizedDescription)"
      }
      FRLog.i("{p1} Protect Initialize Result: \n\(initResult)")
      handleNode(node)
    }
  }
  return
}
import { PIProtect } from '@forgerock/ping-protect';

if (step.getCallbacksOfType('PingOneProtectInitializeCallback')) {
  const callback = step.getCallbackOfType('PingOneProtectInitializeCallback');

  // Obtain config properties from the callback
  const config = callback.getConfig();

  console.log(JSON.stringify(config));

  try {
    // Initialize {p1} Protect with configuration from callback
    await PIProtect.start(config);
  } catch (err) {
    // Add any errors to the callback
    callback.setClientError(err.message);
  }
}

FRAuth.next(step);

Pause and resume behavioral data capture

The PingOne Protect Signals SDK can capture behavioral data, such as how the user interacts with the app, to help when performing evaluations.

There are scenarios where you might want to pause the collection of behavioral data. For example, the user might not be interacting with the app, or you only want to use device attribute data to be considered when performing PingOne Protect evaluations. You can then resume behavioral data collection when required.

The SDKs provide the pauseBehavioralData() and resumeBehavioralData() methods for pausing and resuming the capture of behavioral data.

The PingOneProtectEvaluationCallback callback can include a flag to pause or resume behavioral capture that you should respond to as follows:

  • Android

  • iOS

  • JavaScript

val callback =
  node.getCallback(PingOneProtectEvaluationCallback::class.java)

const shouldPause = callback.pauseBehavioralData

Logger.info("PingOneProtectEvaluationCallback", "getPauseBehavioralData: ${shouldPause}")

if (shouldPause) {
  PIProtect.pauseBehavioralData()
}
if callback.type == "PingOneProtectEvaluationCallback",
  let pingOneProtectEvaluationCallback = callback as? PingOneProtectEvaluationCallback
{
  if let shouldPause = pingOneProtectEvaluationCallback.pauseBehavioralData, shouldPause {
    PIProtect.pauseBehavioralData()
  }
}
const callback = step.getCallbackOfType('PingOneProtectEvaluationCallback');
const shouldPause = callback.getPauseBehavioralData();

console.log(`getPauseBehavioralData: ${shouldPause}`);

if (shouldPause) {
  PIProtect.pauseBehavioralData();
}

Return collected data for a risk evaluation

To perform risk evaluations, the PingOne server requires the captured data.

On receipt of a PingOneProtectEvaluationCallback callback, use the getData() method to populate the response with the captured data.

  • Android

  • iOS

  • JavaScript

try {
  val callback =
    node.getCallback(PingOneProtectEvaluationCallback::class.java)
  callback.getData(context)
} catch (e: PingOneProtectEvaluationException) {
  Logger.error("PingOneRiskEvaluationCallback", e, e.message)
} catch (e: Exception) {
  Logger.error("PingOneRiskEvaluationCallback", e, e.message)
}
if callback.type == "PingOneProtectEvaluationCallback",
  let pingOneProtectEvaluationCallback = callback as? PingOneProtectEvaluationCallback
{
  pingOneProtectEvaluationCallback.getData { result in
    DispatchQueue.main.async {
      var evaluationResult = ""
      switch result {
      case .success:
        evaluationResult = "Success"
      case .failure(let error):
        evaluationResult = "Error: \(error.localizedDescription)"
      }
      FRLog.i("{p1} Protect Evaluation Result: \n\(evaluationResult)")
      handleNode(node)
    }
  }
  return
}
let data;

if (step.getCallbacksOfType('PingOneProtectEvaluationCallback')) {
  const callback = step.getCallbackOfType('PingOneProtectEvaluationCallback');
  try {
    // Asynchronous call
    data = await PIProtect.getData();
  } catch (err) {
    // Add any errors to the callback
    callback.setClientError(err.message);
  }
}
callback.setData(data);
FRAuth.next(step);

Integrate PingOne Advanced Identity Cloud hosted pages into your apps

Overview

Who is this article for?

This is part one of a step-by-step guide for anyone who wishes to use hosted pages (more on that later in the article) in their mobile or web app, with the help of the Ping SDKs.

The goal is to give you practical advice on theming, show how the SDKs help the integration, and provide some best practices along the way.

Prior knowledge configuring PingOne Advanced Identity Cloud is helpful, but not assumed.

What are hosted pages?

Hosted pages are an PingOne Advanced Identity Cloud capability that let you centrally host parts of the user interface (UI) of your mobile or web application. Use the pages to enable any user journey, such as registration, self-service, authentication, or authorization.

Leveraging the power of Intelligent Access and Use centralized login for access journeys lets you:

  • Seamlessly and quickly integrate multiple web and mobile applications with PingOne Advanced Identity Cloud—​a consistent omnichannel experience.

  • Maximize your flexibility to change the self-service and authentication behavior of a journey without disrupting the end-user experience.

  • Use a WYSIWYG editor to brand and theme pages to match an organization’s color palette and UI standards.

The result: When your users start the login process within your application, the app seamlessly redirects the users to your PingOne Advanced Identity Cloud tenant for authentication. Based on the access journey you designed, a UI appears that contains all of your theming and branding.

Themes in the PingOne Advanced Identity Cloud admin portal

Prerequisites for using themes in PingOne Advanced Identity Cloud

Before reading further, visit the following links:

Change the realm theme

  1. Log in to the Admin UI of your PingOne Advanced Identity Cloud tenant using an administrator account.

  2. Navigate to Realm Settings > Theme.

  3. Using the WYSIWYG editor, select colors for the elements that display on the screen. You can customize the default look and feel for sign-in, registration, and profile pages.

  4. You can add a company logo using the Logo tab on the editor:

    • Set a small logo for sign-in and registration pages.

    • Set a larger logo for user profile navigation.
      (Base64-encoded string, or self-hosted logo URL).

  5. Use the Theme Logo tab to change the small logo or logo mark.

    You can also adjust the full-size logo or profile page logo that appears when a customer or admin completes authentication.

  6. Use the Theme Styles tab to change the look and feel of the UI on your hosted page.

Test your theme configuration

  1. Log out of the Admin UI.

  2. In a new tab, access the default login journey of your tenant:
    https://[TENANT-ADDRESS]/am/XUI/?realm=alpha&authIndexType=service&authIndexValue=Login

  3. Use a user (non-admin) account to authenticate.

  4. The appearance of the login and self-service profile pages depends on your theming configuration. Notice the logo at the top of the page, as well as the custom color of the buttons and the links.

Themes and realms

PingOne Advanced Identity Cloud tools let you set theming and branding for your authentication, registration, and self-service journeys. Themes apply to the selected realm, and are active for all journeys under that realm. You can use two realms per tenant: alpha and bravo. Therefore, you can apply two different themes to each tenant by setting a custom theme for each of the available realms.

Summary

In this part, you learned how to theme and brand your authentication, registration, and self-service pages in PingOne Advanced Identity Cloud.

In part two, you will learn how to integrate your JavaScript SPAs with PingOne Advanced Identity Cloud, reusing the themed, hosted pages.

Integrate a single-page app

This is part two of a four-part article, Integrate with PingOne Advanced Identity Cloud hosted pages.

Overview

Who is this article for?

This is part two of a step-by-step guide for developers that wish to use Ping SDK with Hosted Pages. The goal is to give you practical advice about themes and provide some best practices along the way.

Prior knowledge configuring PingOne Advanced Identity Cloud is helpful but not assumed.

What is a single-page app?

A single-page app (SPA) is an app that runs inside a browser and does not require page reloading during use. You use this type of app every day. For example: Gmail, Google Maps, Facebook, and GitHub.

An SPA is all about providing an outstanding user experience by imitating a "natural" environment in the browser—​no page reloads, no extra wait time. An SPA lets you visit just one web page which uses JavaScript to load all content that requires JavaScript.

An SPA requests the markup and data independently, rendering pages straight in the browser. SPAs frequently use popular JavaScript frameworks like AngularJS, Vue.js, React, and more.

The Ping SDK for JavaScript

Use the Ping SDK for JavaScript to integrate your SPAs with your authorization server

The Ping SDK for JavaScript allows you to rapidly build JavaScript apps against REST APIs.

The Ping SDK for JavaScript supports:

  • Registration and Authentication Trees

  • Access Token Acquisition

  • Session Handling

Integrate a single-page app with PingOne Advanced Identity Cloud

Build the UI

Using the framework of your choice, create a new app. You will configure the app to authenticate using PingOne Advanced Identity Cloud.

When building your SPA, you have two options for integrating with ForgeRock:

Option 1: Integrate via APIs and build your own UI.

Option 2: Use the Hosted Pages in your app (Centralized UI).

The SDK supports both options, allowing you to tailor your experience to your needs. Choose the Option that best meets your project and use case requirements.

A custom UI vs a centralized UI

Building a custom UI for your SPA allows you to have full control of the user experience, the branding, and the theme of your app. A custom UI allows you full customization control, but requires more development effort. Each app that uses the platform must build its own UI.

This might be problematic for projects where various SPAs point to a central authentication point to gain authentication privileges. In this case, if the UI/UX requirements allow it, we recommend using a Centralized UI.

What is a centralized UI

The Ping SDKs let you configure multiple apps on multiple platforms. You can reuse the user interface from PingAM or your own web app. You do not have to manage authentication yourself.

As the UI is centralized in the cloud, changes you make to an authentication journey in PingAM are available to all apps that use the UI. You do not need to rebuild or redeploy the apps.

Since your app does not manage authentication journeys, it does not need access to user credentials. This reduces complexity and risk.

The Ping SDK manages and stores tokens - providing a seamless single sign-on experience.

Additionally, apps that use a centralized UI are not affected by the new third party cookie restrictions being rolled out in modern web browsers.

To learn more about centralized UI, visit Use centralized login.

Configure PingOne Advanced Identity Cloud

Before you begin, follow the instructions in one of the following sections:

In the following example, you configure:

In Advanced Identity Cloud
  • OAuth2.0 provider

  • OAuth2.0 client

In the client applications
  • JS

Configure the OAuth2.0 provider

  1. In the Platform Admin UI, log in to the PingAM Native console.

  2. In the PingAM Native console, click Services.

  3. If there is no OAuth2 Provider, create one using the default settings.

  4. In the OAuth2 Provider configuration, on the Consent tab, enable the Allow Clients to Skip Consent option.

  5. On the Configure OAuth2 tab, enable the Issue Refresh Tokens option.

  6. On the Advanced OpenID Connect tab, enable the claims_parameter_supported option.

Configure the OAuth2.0 client

  1. In the PingAM Native console, navigate to Applications > OAuth 2.0 > Clients.

  2. Create a new OAuth 2.0 client named “sdkPublicClient”.

  3. Set the following properties:

    • Client ID = sdkPublicClient

    • Client secret = (leave blank)

    • Redirection URIs = https://localhost:8443

    • Scope(s) = openid profile email address

  4. Click Create.

  5. On the Core tab:

    • Set the Client type property to Public.

    • Disable the Allow wildcard ports in redirect URIs property.

  6. Click Save.

  7. On the Advanced tab:

    • In the Grant Types field, ensure that the Authorization Code value is present.

    • Set the Token Endpoint Authentication Method field to None.

    • Enable the Implied consent property.

      To properly enable implied consent, the OAuth2 Provider must be configured to allow clients to skip consent. For more information, see the PingAM reference documentation.

  8. Click Save.

Configure your app to use centralized UI

In this example, you use a centralized UI to authenticate your app.

You configure the app to use the PingOne Advanced Identity Cloud authentication hosted page you used in part one of this tutorial.

To learn more about centralized UI, visit Use centralized login.

Let’s see how you can do that in more detail:

For this example, you are going to import the Ping SDK using the NPM package, assuming you are using a CommonJS Bundler.

A CDN proxy and other approaches are available.

const forgerock = require('@forgerock/javascript-sdk'); |
  1. Create a centralized login instance using the Config class.

  2. There is a close relationship between your app configuration and your OAuth 2.0 client configuration. Make sure they match.

    forgerock.Config.set({
      serverConfig: {
        baseUrl: 'https://openam-forgerock-sdks.forgeblocks.com/am',
        timeout: 90000
      },
      realmPath: 'alpha',
      clientId: 'sdkPublicClient',
      redirectUri: 'https://localhost:8443', // Example: the alias from the prerequisites section
      scope: 'openid profile email address',
    });
  3. Create a login button with the tag ‘#loginBtn’.

  4. Initiate authentication using the centralized UI feature.

    document.querySelector('#loginBtn').addEventListener('click', () => {
    /**
    * The key-value of `login: redirect` is what allows central-login.
    * @note async await could be used in place of promises for greater clarity
    */
     forgerock
     .TokenManager
     .getTokens({ login: 'redirect' })
     .then(() => {forgerock
          .UserManager
          .getCurrentUser()
          .then((user) => showUser(user));
     });
    });
  5. Create a way to logout.

     document.querySelector('#logoutBtn').addEventListener('click', () => {
     try {
        Forgerock
          .FRUser
          .logout()
          .then(() => location.assign(`${document.location.origin}`))
     } catch (error) {
        console.error(error);
      }
     });

Test your application

When running your app in a browser window, the app redirects to your themed PingOne Advanced Identity Cloud tenant for authentication.

After successfully authenticating, the app returns to your SPA.

IntegrateyourSPAwithFIDC7

Summary

In this part, you learned how to use a centralized UI to integrate your SPA with PingOne Advanced Identity Cloud’s hosted pages.

Integrate native mobile apps

This is part three of a four-part article, Integrate with PingOne Advanced Identity Cloud hosted pages.

Overview

Who is this article for?

This is part three of a step-by-step guide for anyone who wishes to use Ping SDK with hosted pages. The goal is to give you practical advice about themes and provide some best practices along the way.

Prior knowledge configuring PingOne Advanced Identity Cloud is helpful but not assumed.

What is a native mobile application?

Native apps install and run on a specific mobile device operating system (OS) such as Apple iOS or Android OS.

Users download the app via app stores such as the Apple App Store, the Google Play store, and so on.

Users use the version of an app that is specific to Apple iOS or Android OS. For an app to be available for multiple mobile OSs, you must create a version of that app for each OS.

Native SDKs for Android and iOS

The Ping SDKs let you quickly integrate authentication into your mobile apps. We provide native SDKs for Android and iOS, written in Java and Swift respectively.

Integration with Xcode

Use the following methods to integrate the Ping SDK for iOS with your Xcode projects:

  • CocoaPods (Recommended)

  • Embed source code

  • Swift Package manager

To learn more about how to use the Ping SDK for iOS in your app, refer to the iOS tutorial.

Integration with Android Studio

The Ping SDK for Android requires Java 8 (v1.8). You must configure the compiler options in your project to use this version of Java.

You use gradle to integrate the Ping SDK for Android into your project.

To learn more about how to use the Ping SDK for Android in your app, visit the Android tutorial.

A custom UI vs a centralized UI

As discussed in previous parts of this guide, the use of a custom UI or a centralized UI mostly depends on your project requirements. The Ping SDKs for iOS and Android support both options for integrating apps with PingOne Advanced Identity Cloud.

When building a custom UI for authentication, you need to build the login and register pages for each platform separately. This guarantees the best user experience for each platform and adheres to platform-specific UI/UX patterns. However, this means the development team must duplicate effort to authenticate app users.

The example in this part of the guide uses a centralized UI, for both iOS and Android, to leverage PingOne Advanced Identity Cloud hosted login pages. You created the pages earlier in the tutorial.

To learn more about the centralized UI, see Use centralized login.

Configure the server

Configure the OAuth 2.0 provider

In part two of the tutorial, you set the OAuth 2.0 provider in PingAM to support authentication with the Ping SDK for JavaScript. The OAuth 2.0 provider does not require any additional configuration to support the iOS and Android SDKs.

Configure OAuth 2.0 clients

In this example, we create an iOS client and an Android client.

Create the iOS client
  1. In the PingAM native console, navigate to Applications > OAuth 2.0 > Clients.

  2. Create a new OAuth 2.0 client called “iOSClient”.

  3. Enter the following details:

    • Client ID = iOSClient

    • Client secret = (leave blank)

    • Redirection URIs = frauth://com.forgerock.ios.frexample

    • Scope(s) = openid profile email address

  4. Click Create.

  5. On the Core tab:

    • Set the Client type property to Public.

    • Disable the Allow wildcard ports in redirect URIs property.

  6. Click Save.

  7. On the Advanced tab:

    • Ensure the Grant Types field contains the Authorization Code value.

    • Set the Token Endpoint Authentication Method field to None.

    • Enable the Implied consent property.

      To properly enable implied consent, the OAuth2 Provider must be configured to allow clients to skip consent. For more information, see the PingAM reference documentation.

    • Click Save.

Create the Android OAuth2.0 client
  1. In the PingAM native console, navigate to Applications > OAuth 2.0 > Clients.

  2. Create a new OAuth 2.0 client called “AndroidClient”.

    • Enter the following details:

    • Client ID = AndroidClient

    • Client secret = (leave blank)

    • Redirection URIs = org.forgerock.demo:/oauth2redirect

    • Scope(s) = openid profile email address

  3. Click Create.

  4. On the Core tab:

    • Set the Client type property to Public.

    • Disable the Allow wildcard ports in redirect URIs property.

  5. Click Save.

  6. On the Advanced tab:

    • Ensure the Grant Types field contains the Authorization Code value.

    • Set the Token Endpoint Authentication Method field to None.

    • Enable the Implied consent property.

  7. Click Save.

Configure your app to use the centralized UI

In this example, we use the centralized UI to authenticate our app. We point the app to use the PingOne Advanced Identity Cloud authentication hosted page from part one of this tutorial.

Configure the Xcode project

  1. Create a new Xcode project and add the Ping SDK for iOS using cocoapods.

  2. In the Podfile, add the following:

    pod 'FRAuth'
    pod 'FRUI'
    pod 'FRProximity'
  3. In a terminal window, navigate to the root of your Xcode project and run the following command:

    pod install
  4. When Pod installation is complete, open the project’s ‘xcworkspace’ file.

  5. In the Main.storyboard file:

  6. Add a Login button in the ViewController view.

  7. Connect the Login button with the ViewController class creating an IBAction.

  8. In Viewcontroller.swift, add the following code:

    func performCentralizedLogin() {
         FRUser.browser()?
             .set(presentingViewController: self)
             .set(browserType: .authSession)
             .build().login { (user, error) in
                 self.displayLog("User: \(String(describing: user)) || Error: \(String(describing: error))")
         }
         return
    
     }
  9. Call the performCentralizedLogin function from the Login button IBAction.

Configure the custom URL scheme in Xcode

We now configure the custom URL scheme of your Ping SDK for iOS app to use the centralized UI.

If your app targets iOS 10 or earlier, configure a custom URL scheme as follows:

  1. In Xcode, in the Project Navigator, double-click your app to open the Project pane.

  2. On the Info tab, in the URL Types panel, configure your custom URL scheme:

  3. Update your application to call the validateBrowserLogin() function:

    1. In your AppDelegate.swift file, call the validateBrowserLogin() function:

      AppDelegate.swift
      class AppDelegate: UIResponder, UIApplicationDelegate {
      
        func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
          // Parse and validate URL, extract authorization code, and continue the flow:
          Browser.validateBrowserLogin(url)
        }
      }
    2. If your application is using SceneDelegate, in your SceneDelegate.swift file call the validateBrowserLogin() function:

      SceneDelegate.swift
      class SceneDelegate: UIResponder, UIWindowSceneDelegate {
      
        func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
          if let url = URLContexts.first?.url {
            Browser.validateBrowserLogin(url)
          }
        }
      }

Test the configuration

At this point, you are ready to test the app using a simulator or an iOS device.

When you click the Login button, you should see something like this:

IntegrateNativewithFIDC3

For more information, see the sample app demo in the SDK Sample Apps repo on GitHub.

Configure the Android Studio project

Android compile options

  1. In the Project tree view of your Android Studio project, navigate to the app folder.

  2. Open the build.gradle file.

  3. Add the following code at the top level:

    kotlin {
      jvmToolchain {
        languageVersion.set(JavaLanguageVersion.of(17))
      }
    }
  4. In the dependencies section, add the following code:

    implementation 'org.forgerock:forgerock-auth:2.2.0'
    implementation 'org.forgerock:forgerock-auth-ui:2.2.0'
    implementation 'net.openid:appauth:0.11.1'
  5. Navigate to app > src > main > res > values.

  6. Open the strings.xml file.

  7. In the <resources> section, add the following code:

    <!-- PingAM OAuth 2.0 client details -->
    <string name="AndroidClient" translatable="false">sdkPublicClient</string>
    <string name="forgerock_oauth_redirect_uri" translatable="false">org.forgerock.demo:/oauth2redirect</string>
    <string name="forgerock_oauth_scope" translatable="false">openid profile email address</string>
    <integer name="forgerock_oauth_threshold" translatable="false">30</integer>
    
    <!-- PingAM instance details -->
    <string name="forgerock_url" translatable="false">[PingOne Advanced Identity Cloud PingAM URL]</string>
    <string name="forgerock_cookie_name" translatable="false">iPlanetDirectoryPro</string>
    <string name="forgerock_realm" translatable="false">alpha</string>
    <integer name="forgerock_timeout" translatable="false">30</integer>
    
    <!-- PingAM tree details -->
    <string name="forgerock_auth_service" translatable="false">[TREE NAME]</string>
  8. To allow AppAuth to capture authorization redirects, add a custom scheme to your build.gradle file:

     android.defaultConfig.manifestPlaceholders = [
        'appAuthRedirectScheme': 'com.example.sdkapp'
    ]

Configure your app to use browser mode

  1. In your MainActivity, add a button with a listener that runs the following code:

    RUser.browser().login(this, new FRListener<FRUser>() {
             @Override
             public void onSuccess(FRUser result) {
                 runOnUiThread(() -> {
                     final AccessToken accessToken;
                     try {
                         accessToken = FRUser.getCurrentUser().getAccessToken();
                         WritableMap map = Arguments.createMap();
                         map.putString("userToken", accessToken.getIdToken());
                         reactNativeCallback.invoke(map);
                     } catch (AuthenticationRequiredException e) {
                         e.printStackTrace();
                     }
                 });
             }
             @Override
             public void onException(Exception e) {
                 System.out.println(e);
             }
         });

Test the configuration

At this point, you are ready to test the app using an emulator or an Android device.

When you click the Login button, you should see something like this:

IntegrateNativewithFIDC4

For more information, see the sample app demo in the SDK Sample Apps repo on GitHub.

Summary

In this part, you learned how to integrate your native iOS and Android apps with PingOne Advanced Identity Cloud’s hosted pages, using the centralized UI.

This is part four of a four-part article, Integrate with PingOne Advanced Identity Cloud hosted pages. This is part four of a four-part article, Integrate with PingOne Advanced Identity Cloud hosted pages.

Overview

Who is this article for?

This is part four of a step-by-step guide for anyone who wishes to use Ping SDK with hosted pages. The goal is to give you practical advice about theming and provide some best practices along the way.

Prior knowledge configuring PingOne Advanced Identity Cloud is helpful but not assumed.

Centralized UI and authentication journeys

In part two and part three of this guide, we used a centralized UI to authenticate our app with PingOne Advanced Identity Cloud.

When using a centralized UI by default, the user is redirected to the default realm journey. In some cases, you may want to point users to different authentication journeys:

Using elevated authentication in certain platforms (2FA, geofencing, and jailbreak detection).

Using centralized UI for authenticating different types of users (admins, clients).

The Ping SDKs for iOS, Android, and JavaScript let you easily configure the journey (tree) for authentication.

In this part of the tutorial, we use ACR values for authentication.

ACR Claims

In the OpenID Connect specification, the acr claim identifies a set of rules that the user must satisfy when authenticating to the OpenID provider. For example, a chain or journey (tree) configured in PingAM.

To avoid exposing the name of authentication trees or chains, PingAM implements a map that consists of a key (the value that is part of the acr claim), and the name of the authentication tree or chain.

The OpenID Connect specification indicates that the acr claim is a list of authentication contexts. PingAM honors the first value in the list for which it has a valid mapping. For example, if the relying party requests a list of acr values, such as acr-1 acr-2 acr-3, and only acr-2 and acr-3 are mapped, PingAM always chooses acr-2 to authenticate the end user.

The Ping SDKs for Android, iOS, and JavaScript leverage the traditional authorization code flow with PKCE from the OAuth 2.0 spec. When using centralized login, to enable the client app to customize the flow, we use the Authentication Context Class Reference (ACR) property to communicate context to the authorization server (PingOne Advanced Identity Cloud).

Using this method, the client app chooses the journey it presents to the user. You can pre-configure this in the OAuth2.0 client, or on the fly from the client application.

Configure PingAM

Verify or change settings in the OAuth 2.0 provider

In part two of this guide, we set the OAuth2.0 provider in PingAM. One of the steps was to enable the claims_parameter_supported option. Perform the following steps to verify or change this option:

  1. In the PingAM admin UI, navigate to Services.

  2. Select the OAuth 2.0 provider.

  3. Navigate to the Advanced OpenID Connect tab.

  4. Confirm that Enable "claims_parameter_supported" is set to ON.

  5. In OpenID Connect acr_values, add a new entry: Auth Chain Mapping.

  6. Save the configuration.

Configure a new journey

  1. In the Admin UI, click Journeys.

  2. Click +New journey and name it SimpleLogin.

  3. Use the editor to create a journey.

Configure ACR values in the Ping SDK for JavaScript

Configure SPA to use the SimpleLogin journey

  1. Returning to the SPA from part two of the guide, open the project in Visual Studio Code.

  2. Create a new constant:

    const acr_values = 'simple';
  3. If using Ping SDK for JavaScript v2.2.0 or earlier, replace the SDK configuration with the following:

    Config.set({
     clientId,
     middleware: [
            (req, action, next) => {
                     if (action.type === 'AUTHORIZE') {
                         req.url.searchParams.append('acr_values', acr_values);
                                     }
                     next();
    
                 },
             ],
     realmPath,
     redirectUri,
     scope,
     serverConfig: {
         baseUrl,
         },
     support,
    });
    req.url.searchParams.append('acr_values', acr_values);' changes the searchParams query string.

    If using the latest version of the Ping SDK for JavaScript 3 or later, you can dynamically pass the acr_values constant in the TokenManager.getTokens() method. See the example code below:

    /**
    * The key-value of `login: redirect` is what allows central-login.
    * Passing no arguments or a key-value of `login: 'embedded'` means
    * the app handles authentication locally.
    */
    const acr_values = 'simple';
    await TokenManager.getTokens({ login: 'redirect', query: { acr_values }});

When testing the application, you expect to use the SimpleLogin journey for authentication.

Configure ACR values in the Ping SDK for iOS

Configure iOS to use the SimpleLogin journey

  1. Returning to the iOS application from Part 3 of this guide, open the .xcworkspace file using Xcode.

  2. Locat