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.
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:
-
Collect username and password.
-
Request KBA information.
-
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.
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
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.
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.
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
The Ping SDKs are built from the ground up to use best practices for securing token material and data.
Security is a very broad subject, and every environment is different. Readers are expected to do their own research and complement the information found in these topics.
Tokens and keys
Learn how the Ping SDKs secure your session and OAuth 2.0-related tokens, and the encryption used.
Authentication
Discover the protocols the Ping SDKs use when your app authenticates your users.
Data
What data do the Ping SDKs use, and what security measures help to protect it.
OAuth 2.0
See how the Ping SDKs use Proof Key for Code Exchange (PKCE) to mitigate the risks of an OAuth 2.0 attack.
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 theHTTPOnly
andSecure
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 supportssessionStorage
.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 settingsetInvalidatedByBiometricEnrollment
totrue
-
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. IfSecuredKey
generation fails, theKeychainManager
generates theKeychainService
with noSecuredKey
. The values in this case will be added to the iOS keychain askSecClassGenericPassword
types.If
SecuredKey
creation is successful then the value is encrypted before being stored. TheSecuredKey.swift
class provides anisAvailable()
public method that validates whether creation of theSecuredKey
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
andDefaultSingleSignOnManager
for storing tokens, in addition toSecuredSharedPreferences.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 theauth
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:
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
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:
-
Log in to your PingOne Advanced Identity Cloud tenant.
-
At the top right of the screen, click your name, and then select Tenant settings.
-
On the Global Settings tab, click Cross-Origin Resource Sharing (CORS).
-
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.
-
-
Add
https://localhost:8443
and any DNS aliases you use to host your Ping SDK for JavaScript applications to the Accepted Origins property. -
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.
-
Click Save CORS Configuration.
Create a demo user
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:
-
Log in to your PingOne Advanced Identity Cloud tenant.
-
In the left panel, click Identities > Manage.
-
Click New Alpha realm - User.
-
Enter the following details:
-
Username =
demo
-
First Name =
Demo
-
Last Name =
User
-
Email Address =
demo.user@example.com
-
Password =
Ch4ng3it!
-
-
Click Save.
Create an authentication journey
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:
-
In your PingOne Advanced Identity Cloud tenant, navigate to Journeys, and click New Journey.
-
Enter a name, such as
sdkUsernamePasswordJourney
and click Save.The authentication journey designer appears.
-
Drag the following nodes into the designer area:
-
Page Node
-
Platform Username
-
Platform Password
-
Data Store Decision
-
-
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.
-
Connect the nodes as follows:
Figure 3. Example username and password authentication journey -
Click Save.
Register OAuth 2.0 clients in Advanced Identity Cloud
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:
-
Log in to your PingOne Advanced Identity Cloud tenant.
-
In the left panel, click Applications.
-
Click Custom Application.
-
Select OIDC - OpenId Connect as the sign-in method, and then click Next.
-
Select Native / SPA as the application type, and then click Next.
-
In Name, enter a name for the application, such as
Public SDK Client
. -
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. -
In Client ID, enter
sdkPublicClient
, and then click Create Application.PingOne Advanced Identity Cloud creates the application and displays the details screen.
-
On the Sign On tab:
-
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. -
In Grant Types, enter the following values:
Authorization Code
Refresh Token
-
In Scopes, enter the following values:
openid profile email address
-
-
Click Show advanced settings, and on the Authentication tab:
-
In Token Endpoint Authentication Method, select
none
. -
In Client Type, select
Public
. -
Enable the Implied Consent property.
-
-
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:
-
Log in to your PingOne Advanced Identity Cloud tenant.
-
In the left panel, click Applications.
-
Click Custom Application.
-
Select OIDC - OpenId Connect as the sign-in method, and then click Next.
-
Select Web as the application type, and then click Next.
-
In Name, enter a name for the application, such as
Confidential SDK Client
. -
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. -
On the Web Settings page:
-
In Client ID, enter
sdkConfidentialClient
-
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.
-
Click Create Application.
PingOne Advanced Identity Cloud creates the application and displays the details screen.
-
-
On the Sign On tab, click Show advanced settings, and on the Access tab:
-
In Default Scopes, enter
am-introspect-all-tokens
.
-
-
Click Save.
Configure the OAuth 2.0 provider
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:
-
In your PingOne Advanced Identity Cloud tenant, navigate to Native Consoles > Access Management.
-
In the left panel, click Services.
-
In the list of services, click OAuth2 Provider.
-
On the Core tab, ensure Issue Refresh Tokens is enabled.
-
On the Consent tab, ensure Allow Clients to Skip Consent is enabled.
-
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
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
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:
-
Log in to the PingAM admin UI as an administrator.
-
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. -
On the Secondary Configurations tab, click Click Add a Secondary Configuration.
-
In the Name field, enter
ForgeRockSDK
. -
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
-
Click Create.
PingAM displays the configuration of your new CORS filter.
-
On the CORS filter configuration page:
-
Ensure Enable the CORS filter is enabled.
-
Set the Max Age property to
600
-
Ensure Allow Credentials is enabled.
Figure 4. Example of the completed Ping SDK CORS filter -
-
Click Save Changes.
Create a demo user
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:
-
Log in to the PingAM admin UI as an administrator.
-
Navigate to Identities, and then click Add Identity.
-
Enter the following details:
-
User ID =
demo
-
Password =
Ch4ng3it!
-
Email Address =
demo.user@example.com
-
-
Click Create.
Create an authentication tree
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:
-
Under Realm Overview, click Authentication Trees, then click Create Tree.
-
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.
-
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
-
-
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.
-
Connect the nodes as follows:
Figure 5. Example username and password authentication tree -
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.
-
Click Save.
Register OAuth 2.0 clients in AM
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:
-
Log in to the PingAM admin UI as an administrator.
-
Navigate to Applications > OAuth 2.0 > Clients, and then click Add Client.
-
In Client ID, enter
sdkPublicClient
. -
Leave Client secret empty.
-
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
andstate
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. -
In Scopes, enter the following values:
openid profile email address
-
Click Create.
PingAM creates the new OAuth 2.0 client, and displays the properties for further configuration.
-
On the Core tab:
-
In Client type, select
Public
. -
Disable Allow wildcard ports in redirect URIs.
-
Click Save Changes.
-
-
On the Advanced tab:
-
In Grant Types, enter the following values:
Authorization Code Refresh Token
-
In Token Endpoint Authentication Method, select
None
. -
Enable the Implied consent property.
-
-
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:
-
Log in to the PingAM admin UI as an administrator.
-
Navigate to Applications > OAuth 2.0 > Clients, and then click Add Client.
-
In Client ID, enter
sdkConfidentialClient
. -
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.
-
In Default Scopes, enter
am-introspect-all-tokens
.PingAM creates the new OAuth 2.0 client and displays the properties for further configuration.
-
On the Advanced tab:
-
Enable the Implied consent property.
-
-
Click Save Changes.
Configure the OAuth 2.0 provider
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:
-
Log in to the PingAM admin UI as an administrator.
-
In the left panel, click Services.
-
In the list of services, click OAuth2 Provider.
-
On the Core tab, ensure Issue Refresh Tokens is enabled.
-
On the Consent tab, ensure Allow Clients to Skip Consent is enabled.
-
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:
-
Log in to your PingOne administration console.
-
In the left panel, navigate to Directory > Users.
-
Next to the Users label, click the plus icon ().
PingOne displays the Add User panel.
-
Enter the following details:
-
Given Name =
Demo
-
Family Name =
User
-
Username =
demo
-
Email =
demo.user@example.com
-
Population =
Default
-
Password =
Ch4ng3it!
-
-
Click Save.
Create a revoke resource in PingOne
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:
-
Log in to your PingOne administration console.
-
In the left panel, navigate to Applications > Resources.
-
Next to the Resources label, click the plus icon ().
PingOne displays the Add Custom Resource panel.
-
In Resource Name, enter a name for the custom resource, for example
SDK Revoke Resource
, and then click Next. -
On the Attributes page, click Next.
-
On the Scopes page, click Add Scope.
-
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
OAuth 2.0 client application profiles define how applications connect to PingOne and obtain OAuth 2.0 tokens.
-
To allow the Ping SDK for JavaScript to connect to PingOne and obtain OAuth 2.0 tokens, you must register a public OAuth 2.0 client application for web apps.
-
To allow the Ping SDK for Android and iOS to connect to PingOne and obtain OAuth 2.0 tokens, you must register a public OAuth 2.0 client application for native mobile apps.
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:
-
Log in to your PingOne administration console.
-
In the left panel, navigate to Applications > Applications.
-
Next to the Applications label, click the plus icon ().
PingOne displays the Add Application panel.
-
In Application Name, enter a name for the profile, for example
sdkPublicClient
-
Select OIDC Web App as the Application Type, and then click Save.
-
On the Configuration tab, click the pencil icon ().
-
In Grant Type, select the following values:
Authorization Code
Refresh Token
-
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.
-
In Token Endpoint Authentication Method, select
None
. -
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. -
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
-
Click Save.
-
-
On the Resources tab, next to Allowed Scopes, click the pencil icon ().
-
In Scopes, select the following values:
email
phone
profile
SDK Revoke Resource
The openid
scope is selected by default.The result resembles the following:
Figure 6. Adding scopes, including the custom "revoke" scope to an application.
-
-
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:
-
Click Add policies and then select the policies that you want to apply to the application.
-
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:
-
You must clear all PingOne policies. Click Deselect all PingOne Policies.
-
In the confirmation message, click Continue.
-
On the DaVinci Policies tab, select the policies that you want to apply to the application.
-
Click Save.
PingOne applies the first policy in the list.
-
-
Click Save.
-
Enable the OAuth 2.0 client application by using the toggle next to its name:
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:
-
Log in to your PingOne administration console.
-
In the left panel, navigate to Applications > Applications.
-
Next to the Applications label, click the plus icon ().
PingOne displays the Add Application panel.
-
In Application Name, enter a name for the profile, for example
sdkNativeClient
-
Select Native as the Application Type, and then click Save.
-
On the Configuration tab, click the pencil icon ().
-
In Grant Type, select the following values:
Authorization Code
Refresh Token
-
In Redirect URIs, enter the following value:
org.forgerock.demo://oauth2redirect
-
In Token Endpoint Authentication Method, select
None
. -
In Signoff URLs, enter the following value:
org.forgerock.demo://oauth2redirect
-
Click Save.
-
-
On the Resources tab, next to Allowed Scopes, click the pencil icon ().
-
In Scopes, select the following values:
email
phone
profile
SDK Revoke Resource
The openid
scope is selected by default.The result resembles the following:
Figure 8. Adding scopes, including the custom "revoke" scope to an application.
-
-
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:
-
Click Add policies and then select the policies that you want to apply to the application.
-
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:
-
You must clear all PingOne policies. Click Deselect all PingOne Policies.
-
In the confirmation message, click Continue.
-
On the DaVinci Policies tab, select the policies that you want to apply to the application.
-
Click Save.
PingOne applies the first policy in the list.
-
-
Click Save.
-
Enable the OAuth 2.0 client application by using the toggle next to its name:
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:
-
Log in to your PingOne administration console.
-
In the left panel, click Applications.
-
Select the OAuth 2.0 application profile you created earlier. For example,
sdkPublicClient
orsdkNativeClient
. -
On the Configuration tab, expand the URLs section, and make a note of the following values:
OIDC Discovery Endpoint
Client ID
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
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:
-
Log in to the PingFederate administration console as an administrator.
-
Navigate to
. -
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
-
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
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:
-
Log in to the PingFederate administration console as an administrator.
-
Navigate to
. -
Click Add Client.
PingFederate displays the Clients | Client page.
-
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.
-
In Client Authentication, select
None
. -
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.
-
In Allowed Grant Types, select the following values:
Authorization Code
Refresh Token
-
In the OpenID Connect section:
-
In Logout Mode, select Ping Front-Channel
-
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. -
In Post-Logout Redirect URIs, add the following values:
org.forgerock.demo://oauth2redirect
https://localhost:8443/callback.html
-
-
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
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 | ||
---|---|---|---|
|
The base URL of the server to connect to, including port and deployment path. Identity Cloud example:
Self-hosted example:
|
||
|
The realm in which the OAuth 2.0 client profile and authentication journeys are configured. For example, Defaults to the self-hosted top-level realm |
||
|
A timeout, in seconds, for each request that communicates with your server. |
||
|
The name of the cookie that contains the session token. For example, with a self-hosted PingAM server this value might be
|
Journey properties
Property | Description |
---|---|
|
The name of a user authentication tree configured in your server. For example, |
|
The name of a user registration tree configured in your server. For example, |
OAuth 2.0 properties
Property | Description | ||
---|---|---|---|
|
The For example, |
||
|
The
For example, |
||
|
The URI to redirect users to after they sign out and revoke their OAuth 2.0 tokens. For example, |
||
|
A list of scopes to request when performing an OAuth 2.0 authorization flow, separated by spaces. For example, |
||
|
A threshold, in seconds, to refresh an OAuth 2.0 token before the Defaults to |
SSL pinning properties
Property | Description |
---|---|
|
An array of public key certificate hashes (strings) for trusted sites and services. |
|
An array of BuildStep objects to provide additional SSL pinning parameters to |
Custom endpoint properties
Property | Description |
---|---|
|
Override the default path to your server’s |
|
Override the default path to the PingAM’s |
|
Override the default path to your server’s |
|
Override the default path to your server’s |
|
Override the default path to your server’s |
|
Override the default path to your server’s |
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 |
---|---|
|
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 |
---|---|
|
Lets the app access precise 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 | ||
---|---|---|---|
|
The base URL of the server to connect to, including port and deployment path. Identity Cloud example:
Self-hosted example:
|
||
|
The realm in which the OAuth 2.0 client profile and authentication journeys are configured. For example, Defaults to the self-hosted top-level realm |
||
|
A timeout, in seconds, for each request that communicates with your server. |
||
|
When Defaults to |
||
|
The name of the cookie that contains the session token. For example, with a self-hosted PingAM server this value might be
|
Journey properties
Property | Description |
---|---|
|
The name of a user authentication tree configured in your server. For example, |
|
The name of a user registration tree configured in your server. For example, |
OAuth 2.0 properties
Property | Description |
---|---|
|
The For example, |
|
The [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, |
|
A list of scopes to request when performing an OAuth 2.0 authorization flow, separated by spaces. For example, |
|
A threshold, in seconds, to refresh an OAuth 2.0 token before the Defaults to |
SSL pinning properties
Property | Description |
---|---|
|
An array of public key certificate hashes (strings) for trusted sites and services. |
|
Keychain access group for the shared keychain. |
Custom endpoint properties
Property | Description |
---|---|
|
Override the default path to your server’s |
|
Override the default path to the PingAM’s |
|
Override the default path to your server’s |
|
Override the default path to your server’s |
|
Override the default path to your server’s |
|
Override the default path to your server’s |
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.
Property | Description | ||
---|---|---|---|
|
An interface for configuring how the SDK contacts the PingAM instance. Contains |
||
|
The base URL of the server to connect to, including port and deployment path. Identity Cloud example:
Self-hosted example:
|
||
|
A URL to the server’s Use the Example:
Self-hosted example:
|
||
|
A timeout, in milliseconds, for each request that communicates with your server. For example, for 30 seconds specify Defaults to |
||
|
The realm in which the OAuth 2.0 client profile and authentication journeys are configured. For example, Defaults to the self-hosted top-level realm |
||
|
The name of the user authentication tree configured in your server. For example, |
||
|
The |
||
|
The
For example, |
||
|
A list of scopes to request when performing an OAuth 2.0 authorization flow, separated by spaces. For example, |
||
|
A threshold, in seconds, to refresh an OAuth 2.0 token before the Defaults to |
||
|
Specify whether the SDK should output its log messages in the console and the level of messages to display. One of:
|
||
|
Specify a function to override the default logging behavior. |
||
|
The API to use for storing tokens on the client:
|
||
|
Override the default For example, the key used for storing tokens consists of the
|
||
|
Specify whether to include an 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 |
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 Enter the well-known URL in the
|
Property | Description |
---|---|
authenticate |
Override the default |
authorize |
Override the default |
accessToken |
Override the default |
revoke |
Override the default |
userInfo |
Override the default |
sessions |
Override the default |
endSession |
Override the default |
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
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:
Domain ( |
Property name |
Description |
Required |
|
---|---|---|---|---|
Android (Java) |
Android (Kotlin) |
|||
Server ( |
|
|
The base URL of the PingAM instance to connect to, including port and deployment path;
for example, |
|
|
|
The realm where the OAuth 2.0 client profile is configured. Default: |
||
|
|
A timeout, in seconds, for each request that communicates with PingAM. Default: |
||
|
|
The name of the cookie that contains the session token, for example, To locate the cookie name in an PingOne Advanced Identity Cloud tenant, go to Tenant settings > Global Settings > Cookie. Default: |
||
|
|
Time, in seconds, to cache the session token cookie in memory. Default: |
||
Journeys ( |
|
|
The name of the user authentication tree configured in PingAM. |
|
|
|
The name of the user registration tree configured in PingAM. |
||
OAuth 2.0 ( |
|
|
The |
|
|
|
The |
||
|
|
A list of scopes to request when performing an OAuth 2.0 authorization flow. |
||
|
|
A threshold, in seconds, to refresh an OAuth 2.0 token before the |
||
|
|
Time, in seconds, to cache an OAuth 2.0 token in memory (defaults to |
||
Storage ( |
|
|
A custom class for the storage of OpenID Connect-related items, such as access tokens. |
|
|
|
A custom class for the storage of single sign-on-related items, such as SSO tokens. |
||
|
|
A custom class for the storage of cookies. |
||
SSL pinning ( |
|
|
An array of public key certificate hashes (strings) for trusted sites and services. |
|
|
|
An array of |
||
Custom endpoints ( |
|
|
Override the default path to PingAM’s |
|
|
|
Override the default path to the PingAM’s |
||
|
|
Override the default path to PingAM’s |
||
|
|
Override the default path to PingAM’s |
||
|
|
Override the default path to PingAM’s |
||
|
|
Override the default path to PingAM’s |
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
:
Domain | Property | Description | Required |
---|---|---|---|
Server |
|
The base URL of the PingAM instance to connect to, including port and deployment path;
for example, |
|
|
The realm where the OAuth 2.0 client profile is configured. Default: |
||
|
A timeout, in seconds, for each request that communicates with PingAM (defaults to |
||
|
When |
||
|
The name of the cookie that contains the SSO token, for example, To locate the cookie name in an PingOne Advanced Identity Cloud tenant, go to Tenant Settings > Global Settings > Server. Default: |
||
Journeys |
|
The name of the user authentication tree configured in PingAM. |
|
|
The name of the user registration tree configured in PingAM. |
||
OAuth 2.0 |
|
The |
|
|
The |
||
|
A list of scopes to request when performing an OAuth 2.0 authorization flow. |
||
|
A threshold, in seconds, to refresh an OAuth 2.0 token before the |
||
SSL pinning |
|
An array of public key certificate hashes (strings) for trusted sites and services. |
|
|
Keychain access group for the shared keychain. |
||
Custom endpoints |
|
Override the default path to PingAM’s |
|
|
Override the default path to the PingAM’s |
||
|
Override the default path to PingAM’s |
||
|
Override the default path to PingAM’s |
||
|
Override the default path to PingAM’s |
||
|
Override the default path to PingAM’s |
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
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
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 |
---|---|
|
Initial call to an authentication tree |
|
Proceed through an authentication tree flow |
|
Obtain authorization token from PingAM |
|
Exchange authorization code for an access token |
|
Refresh an access token |
|
Revoke a refresh or access token |
|
Log out a session |
|
Obtain information from the |
|
Register a push device with PingAM;
for example, a call to |
|
Authenticate using push;
for example, a call to |
The
|
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:
|
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:
|
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 |
Use and customize loggers
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 |
---|---|
|
Show debug log messages intended only for development, as well as the message levels lower in this list; In addition, all network activities of the SDK are included in the logs. |
|
Show expected log messages for regular usage, as well as the message levels lower in this list, |
|
Show possible issues that are not yet errors, as well as the messages of |
|
Show issues that caused errors. |
|
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 By default, the log level of the Ping SDK for Android is set to |
Customize the Ping SDK for Android logger
The Ping SDK for Android allows developers to customize the default logger behavior:
-
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 } }
-
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);
-
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 |
---|---|
|
Prevent logging |
|
Logs that are not important or can be ignored |
|
Logs that maybe helpful or meaningful for debugging, or understanding the flow |
|
Logs for network traffic, including request and response |
|
Logs that are a minor issue or an error that can be ignored |
|
Logs that are a severe issue or a major error that impacts the SDK’s functionality or flow |
|
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 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 |
Customize the Ping SDK for iOS logger
The Ping SDK for iOS lets developers customize the default logger behavior:
-
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... } }
-
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])
-
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:
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 |
---|---|
|
Show debug log messages intended only for development, as well as the message levels lower in this list;
In addition, all network activities of the SDK are included in the logs. |
|
Show expected log messages for regular usage, as well as the message levels lower in this list, |
|
Show possible issues that are not yet errors, as well as the messages of |
|
Show issues that caused errors. |
|
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 |
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.
-
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}`), };
-
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
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:
-
OAuth 2.0 / OpenID Connect 1.0 tokens
-
SSO data
-
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 You cannot add the parameters to the |
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 |
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)
}
Android tutorials
To complete these tutorials successfully, you should perform the following prerequisite tasks.
Prerequisites
Before starting the tutorials, complete these prerequisite tasks.
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.
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
-
In Android Studio, select File > New > New Project.
-
On the New Project screen, select Empty Views Activity, and then click Next.
-
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:
-
In the Android view of your project, right-click app, and then click Open module settings.
-
In the Project Structure dialog, navigate to Modules > app > Properties.
-
In the Source Compatibility and Target compatibility drop-downs, select the version of Java to use for the project:
Figure 11. Selecting the Java version for a project in Android Studio -
Click OK.
Add build dependencies
To use the Ping SDK for Android, add the relevant dependencies to your project:
-
In the Project tree view of your Android Studio project, open the
Gradle Scripts/build.gradle
file for the module. -
In the
dependencies
section, add the following:implementation 'org.forgerock:forgerock-auth:4.6.0'
Example of thedependencies
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. |
-
Open the project manifest file.
For example, app > manifests > AndroidManifest.xml.
-
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
-
Open the project’s manifest file.
For example, app > manifests > AndroidManifest.xml.
-
Add the relevant properties as a child of the
<manifest>
element:-
Coarse location access
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
-
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!".
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 |
---|---|
|
The |
|
The This value must match a value configured in your OAuth 2.0 client, but is not actually used by the Android application. |
|
A list of scopes to request when performing an OAuth 2.0 authorization flow. |
|
The URL of the PingOne Advanced Identity Cloud or PingAM instance. For example, If you are not using PingOne Advanced Identity Cloud, specify the port and deployment path. For example, |
|
The realm in which the OAuth 2.0 client profile is configured. For example, If you are not using PingOne Advanced Identity Cloud, specify the default PingAM the top-level realm; |
|
The name of the journey to use for authentication. For example, |
|
The name of the cookie that contains the session token. To obtain the name of the cookie in the PingOne Advanced Identity Cloud:
The value is a random string of characters, such as If you are not using PingOne Advanced Identity Cloud, the cookie name is usually |
Show additional configuration properties
Property | Description |
---|---|
|
A threshold, in seconds, to refresh an OAuth 2.0 token before the |
|
A timeout, in seconds, for each request that communicates with PingAM. |
Add required connection settings to your app
-
In the Project tree view of your Android Studio project, navigate to , and then open the
strings.xml
file. -
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>
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
-
Open the project’s
MainActivity
class file.For example, app > java > com.example.quickstart > MainActivity.
-
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); }
-
Add the required import statements for
org.forgerock.android.auth.FRAuth
andorg.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.
-
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.
-
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
-
Navigate to app > res > layout and open
activity_main.xml
. -
Select and delete the existing
TextView
element that contains the textHello World!
. -
From the Palette pane, drag a new
TextView
element to the canvas:-
id:
textViewUserStatus
-
text:
User status
-
-
From the Palette pane, drag a new
Button
element to the canvas:-
id:
buttonLogin
-
text:
Log in
-
-
From the Palette pane, drag a second new
Button
element to the canvas:-
id:
buttonLogout
-
text:
Log out
-
-
Layout the elements on the canvas to your liking.
The following screenshot shows one possibility:
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
-
Open the project’s
MainActivity
class file.For example, app > java > com.example.quickstart > MainActivity.
-
Add the following statements before the definition of the
onCreate()
function:private TextView status; private Button loginButton; private Button logoutButton;
-
Add import statements for the
FRUser
module, and forandroid.widget.Button
andandroid.widget.TextView
.import org.forgerock.android.auth.FRUser; import android.widget.Button; import android.widget.TextView;
-
In the
onCreate()
function, after the call toFRAuth.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();
-
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
:
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
andPasswordCallback
callbacks. In the next step, we create the UI to request these credentials from the user. onException()
-
Handle any errors.
Implement NodeListener
and methods
-
Edit the
MainActivity
class so that it implementsNodeListener<FRUser>
:public class MainActivity extends AppCompatActivity implements NodeListener<FRUser> {
-
Add import statements for
org.forgerock.android.auth.NodeListener
andorg.forgerock.android.auth.Node
:import org.forgerock.android.auth.NodeListener; import org.forgerock.android.auth.Node;
-
At the bottom of the
MainActivity
class, add the handler methods from theNodeListener
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); } }
-
Attach
FRUser.login()
andFRUser.logout()
calls to the appropriate buttons, after theupdateStatus()
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
-
In Android Studio, select Run > Run 'app'.
If everything is configured correctly, the app builds, and the emulator runs the application.
-
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
andPasswordCallback
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
-
Navigate to app > res.
-
Right-click layout and select New > Fragment > Fragment (Blank).
-
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
-
-
Navigate to app > res > layout and open
fragment_username_password.xml
. -
Select and delete the existing
TextView
element that contains the textHello blank fragment
. -
In the Component Tree pane, right-click the FrameLayout component, select Convert FrameLayout to ConstraintLayout, and then click OK.
-
In the Palette pane, from the Text category drag a
Plain Text
input element to the canvas:-
id:
inputUsername
-
text:
Username
-
-
Drag a
Password
element to the canvas:-
id:
inputPassword
-
hint:
Password
-
-
In the Palette pane, from the Button category, drag a
Button
element to the canvas:-
id:
buttonCancel
-
text:
Cancel
-
-
Drag a second
Button
element to the canvas:-
id:
buttonContinue
-
text:
Continue
-
-
Layout the elements on the canvas to your liking.
The following screenshot shows one possibility:
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
-
Open
usernamePasswordFragment.java
For example,
. -
Update the class to extend
DialogFragment
rather thanFragment
, which makes opening and closing the fragment easier:public class usernamePasswordFragment extends DialogFragment {
-
Add import statements for
androidx.fragment.app.DialogFragment
:import androidx.fragment.app.DialogFragment;
-
Within the
usernamePasswordFragment
class, initialize required variables:private MainActivity listener; private Node node;
-
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; }
-
Insert an
onResume()
method below thenewInstance()
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); }
-
Delete the
onCreate()
function. -
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; }
-
Add an
onAttach()
method after theonCreateView()
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; } }
-
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
-
Open the project’s
MainActivity
class file.For example, app > java > com.example.quickstart > MainActivity.
-
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
-
In Android Studio, select Run > Run 'app'.
-
Click Log in.
The fragment dialog appears, with fields for both name and password, as well as continue and cancel buttons:
-
Enter the credentials of the demo user:
-
Name:
demo
-
Password:
Ch4ng3it!
-
-
Click Continue.
If authentication is successful, the application returns to the main screen, and displays
User is authenticated
. -
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
-
-
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
-
Update your app to handle additional supported callbacks.
-
Improve the security of your application by adding SSL pinning.
-
Add the ability to update your configuration without reinstalling the app, with dynamic configuration.
-
Offer "magic links" to your users by adding support for suspending and resuming authentication.
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.
-
In a web browser, navigate to the SDK Sample Apps repository.
-
Download the source code using one of the following methods:
- Download a ZIP file
-
-
Click Code, and then click Download ZIP.
-
Extract the contents of the downloaded ZIP file to a suitable location.
-
- Use a Git-compatible tool to clone the repo locally
-
-
Click Code, and then copy the HTTPS URL.
-
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.
-
In Android Studio, open the
sdk-sample-apps/android/kotlin-ui-prototype
project you cloned in the previous step. -
In the Project pane, switch to the Android view.
-
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. -
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" } }
-
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) }
-
Optionally, specify which of the configured policies PingOne uses to authenticate users.
In
/app/kotlin+java/com.example.app/centralize/CentralizeLoginViewModel
, in thelogin(fragmentActivity: FragmentActivity)
function, add anacr_values
parameter to the authorization request by using thesetAdditionalParameters()
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
-
In Android Studio, select
. -
On the Environment screen, ensure the PingOne environment you added in the previous step is selected.
Figure 12. Select the PingOne environment -
Tap the menu icon (), and then tap Centralize Login:
Figure 13. From the menu, select Centralize Login.The app launches a web browser and redirects to your PingOne environment:
Figure 14. Browser launched and redirected to PingOne -
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.
-
-
Tap Show Userinfo to display the details of the token issues to the demo user:
Figure 15. User info of the demo user -
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:
-
In the PingOne administration console, navigate to Directory > Users.
-
Select the user you signed in as.
-
From the Sevices dropdown, select Authentication:
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.
-
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.
-
In a web browser, navigate to the SDK Sample Apps repository.
-
Download the source code using one of the following methods:
- Download a ZIP file
-
-
Click Code, and then click Download ZIP.
-
Extract the contents of the downloaded ZIP file to a suitable location.
-
- Use a Git-compatible tool to clone the repo locally
-
-
Click Code, and then copy the HTTPS URL.
-
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.
-
In Android Studio, open the
sdk-sample-apps/android/kotlin-ui-prototype
folder you cloned in the previous step. -
In the Project pane, switch to the Android view.
-
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. -
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:
-
Log in to your PingFederate administration console.
-
Navigate to
. -
Make a note of the Base URL value.
For example,
https://pingfed.example.com
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" } }
-
In the
init
object, check thatPingFederate
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
-
In Android Studio, select
. -
On the Environment screen, select the PingFederate
.well-known
endpoint you added in the earlier step.Figure 17. Select the PingFederate environment -
Tap the menu icon (), and then tap Centralize Login:
Figure 18. From the menu, select Centralize Login.The app launches a web browser and redirects to your PingFederate environment:
Figure 19. Browser launched and redirected to PingFederate -
Sign on using your PingFederate credentials
If authentication is successful, the application returns to the user info screen.
-
Tap Show Userinfo to display the details of the token issues to the demo user:
Figure 20. User info of the demo user -
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.
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.
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.
-
In a web browser, navigate to the SDK Sample Apps repository.
-
Download the source code using one of the following methods:
- Download a ZIP file
-
-
Click Code, and then click Download ZIP.
-
Extract the contents of the downloaded ZIP file to a suitable location.
-
- Use a Git-compatible tool to clone the repo locally
-
-
Click Code, and then copy the HTTPS URL.
-
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.
-
In Xcode, on the File menu, click Open.
-
Navigate to the
sdk-sample-apps
folder you cloned in the previous step, navigate toiOS
>uikit-quickstart
>Quickstart.xcodeproj
, and then click Open. -
In the navigator pane in Xcode, right-click
FRAuthConfig
and select Open As > Source Code. -
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>
-
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 andalpha
orbeta
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>
-
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.
-
In Xcode, select Product > Run.
Xcode launches the sample app in the iPhone simulator.
Figure 21. Starting the quickstart app in an iOS simulator -
In the sample app on the iPhone simulator, tap the Login button.
The app displays fields to input the user’s credentials:
Figure 22. Login as your demo user -
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:
Figure 23. Login as your demo userThe 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
-
-
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: {}
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.
-
In a web browser, navigate to the SDK Sample Apps repository.
-
Download the source code using one of the following methods:
- Download a ZIP file
-
-
Click Code, and then click Download ZIP.
-
Extract the contents of the downloaded ZIP file to a suitable location.
-
- Use a Git-compatible tool to clone the repo locally
-
-
Click Code, and then copy the HTTPS URL.
-
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.
-
In Xcode, on the File menu, click Open.
-
Navigate to the
sdk-sample-apps
folder you cloned in the previous step, navigate toiOS
>uikit-frexamples
>FrExample
>FrExample
>FRExample.xcodeproj
, and then click Open. -
In the Project Navigator pane, navigate to FRExample > FRExample, and open the
ViewController
file. -
In the
ViewController
file:-
Change the
useDiscoveryURL
variable totrue
: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. -
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"]]
-
Remove or comment out the
forgerock_ssl_pinning_public_key_hashes
line.For information on SSL pinning, refer to Enable SSL pinning.
-
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"
-
Optionally, specify which of the configured policies PingOne uses to authenticate users.
In the
performCentralizedLogin
function, add anacr_values
parameter to the authorization request by using thesetCustomParam()
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
-
In Xcode, select
.Xcode launches the sample app in the iPhone simulator.
-
In the sample app on the iPhone simulator, in the Select an action menu, select Login with Browser, and then click Perform Action.
Figure 24. Select the PingOne environmentYou 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:
Figure 25. Browser launched and redirected to PingOne -
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.
-
-
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:
-
In the PingOne administration console, navigate to Directory > Users.
-
Select the user you signed in as.
-
From the Sevices dropdown, select Authentication:
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.
-
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.
-
In a web browser, navigate to the SDK Sample Apps repository.
-
Download the source code using one of the following methods:
- Download a ZIP file
-
-
Click Code, and then click Download ZIP.
-
Extract the contents of the downloaded ZIP file to a suitable location.
-
- Use a Git-compatible tool to clone the repo locally
-
-
Click Code, and then copy the HTTPS URL.
-
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.
-
In Xcode, on the File menu, click Open.
-
Navigate to the
sdk-sample-apps
folder you cloned in the previous step, navigate toiOS
>uikit-frexamples
>FrExample
>FrExample
>FRExample.xcodeproj
, and then click Open. -
In the Project Navigator pane, navigate to FRExample > FRExample, and open the
ViewController
file. -
In the
ViewController
file:-
Change the
useDiscoveryURL
variable totrue
: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. -
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"]]
-
Remove or comment out the
forgerock_ssl_pinning_public_key_hashes
line.For information on SSL pinning, refer to Enable SSL pinning.
-
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:
-
Log in to your PingFederate administration console.
-
Navigate to
. -
Make a note of the Base URL value.
For example,
https://pingfed.example.com
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
-
In Xcode, select
.Xcode launches the sample app in the iPhone simulator.
-
In the sample app on the iPhone simulator, in the Select an action menu, select Login with Browser, and then click Perform Action.
Figure 27. Select the PingFederate environmentYou 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:
Figure 28. Browser launched and redirected to PingFederate -
Sign on using your PingFederate credentials
If authentication is successful, the application displays the token issued by PingFederate:
Figure 29. Sample app showing the token returned from PingFederate -
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
Prerequisites
Before starting the tutorials, complete these prerequisite tasks.
Install Node and NPM
The SDK requires a minimum Node.js version of 18
, and is tested on versions 18
and 20
. To get a supported version of Node.js, refer to the Node.js download page.
You will also need npm
to build the code and run the samples.
Tutorial steps
There are three tutorials available for JavaScript, depending on your server environment:
Advanced Identity Cloud/PingAM
Configure the embedded login sample JavaScript app to connect to your Advanced Identity Cloud instance or PingAM server.
Then, navigate through a simple authentication journey, and obtain OAuth 2.0 tokens for the user.
PingOne
Configure the OIDC-based centralized login sample JavaScript app to redirect users to PingOne for authentication.
Then, redirect back to your client application so that it can get OAuth 2.0 tokens and user info for the user.
PingFederate
Configure the OIDC-based centralized login sample JavaScript app to redirect users to PingFederate for authentication.
Then, redirect back to your client application so that it can get OAuth 2.0 tokens and user info for the user.
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.
-
In a web browser, navigate to the Ping SDK sample apps repository.
-
Download the source code using one of the following methods:
- Download a ZIP file
-
-
Click Code, and then click Download ZIP.
-
Extract the contents of the downloaded ZIP file to a suitable location.
-
- Use a Git-compatible tool to clone the repo locally
-
-
Click Code, and then copy the HTTPS URL.
-
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.
-
In a terminal window, navigate to the
sdk-sample-apps/javascript
folder. -
To install the required packages, enter the following:
npm install
The
npm
tool downloads the required packages, and places them inside anode_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 andalpha
orbeta
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
-
In a terminal window, navigate to the root of your
sdk-sample-apps
project. -
To run the embedded login sample, enter the following:
npm run start:embedded-login
-
In a web browser:
-
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. -
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: -
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:
To see the application calling the authorize
andauthenticate
endpoints, open the Network tab of your browser’s developer tools.
-
-
-
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.
-
In a web browser, navigate to the Ping SDK sample apps repository.
-
Download the source code using one of the following methods:
- Download a ZIP file
-
-
Click Code, and then click Download ZIP.
-
Extract the contents of the downloaded ZIP file to a suitable location.
-
- Use a Git-compatible tool to clone the repo locally
-
-
Click Code, and then copy the HTTPS URL.
-
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.
-
In a terminal window, navigate to the
sdk-sample-apps
folder. -
To install the required packages, enter the following:
npm install
The
npm
tool downloads the required packages, and places them inside anode_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.
-
In the IDE of your choice, open the
sdk-sample-apps
folder you cloned in the previous step. -
Make a copy of the
/javascript/central-login-oidc/.env.example
file, and name it.env
.The
.env
file provides the values used by theforgerock.Config.setAsync()
method injavascript/central-login-oidc/src/main.js
. -
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"
-
(Optional) Specify which of the configured policies PingOne uses to authenticate users.
In the
/javascript/central-login-oidc/src/main.js
file add anacr_values
query parameter to thegetTokens()
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
-
In a terminal window, navigate to the root of your
sdk-sample-apps
project. -
To run the embedded login sample, enter the following:
npm run start:central-login-oidc
-
In a web browser, navigate to the following URL:
The sample displays a page with two buttons:
-
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 thecode
andstate
OAuth 2.0 parameters, open the Network tab of your browser’s developer tools. -
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:
-
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 thelogout
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.
-
In a web browser, navigate to the Ping SDK sample apps repository.
-
Download the source code using one of the following methods:
- Download a ZIP file
-
-
Click Code, and then click Download ZIP.
-
Extract the contents of the downloaded ZIP file to a suitable location.
-
- Use a Git-compatible tool to clone the repo locally
-
-
Click Code, and then copy the HTTPS URL.
-
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.
-
In a terminal window, navigate to the
sdk-sample-apps
folder. -
To install the required packages, enter the following:
npm install
The
npm
tool downloads the required packages, and places them inside anode_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.
-
In the IDE of your choice, open the
sdk-sample-apps
folder you cloned in the previous step. -
Make a copy of the
/javascript/central-login-oidc/.env.example
file, and name it.env
.The
.env
file provides the values used by theforgerock.Config.setAsync()
method injavascript/central-login-oidc/src/main.js
. -
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:
-
Log in to your PingFederate administration console.
-
Navigate to
. -
Make a note of the Base URL value.
For example,
https://pingfed.example.com
Do not use the admin console URL. -
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
-
In a terminal window, navigate to the root of your
sdk-sample-apps
project. -
To run the embedded login sample, enter the following:
npm run start:central-login-oidc
-
In a web browser, navigate to the following URL:
The sample displays a page with two buttons:
-
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 thecode
andstate
OAuth 2.0 parameters, open the Network tab of your browser’s developer tools. -
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:
-
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:
Use centralized login
The following diagram shows a sample OAuth 2.0 flow that the Ping SDK uses for your native app:
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
This section describes how to configure your Ping SDK for Android application to use centralized login by leveraging the AppAuth
library:
-
Add the build dependency to the
build.gradle
file:implementation 'net.openid:appauth:0.11.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 Android App Links.
If you do not want to implement Android App Links, you can instead use a custom scheme for your redirect URIs.
-
Android App Links
-
Custom Scheme
Complete the following steps to configure App Links:
-
In your application, configure the AppAuth library to use the HTTP scheme for capturing redirect URIs, by adding an
<intent-filter>
forAppAuth.RedirectUriReceiverActivity
to yourAndroidManifest.xml
:AndroidManifest.xml<activity android:name="net.openid.appauth.RedirectUriReceiverActivity" android:exported="true" tools:node="replace"> <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="https" /> <data android:host="android.example.com" /> <data android:path="/oauth2redirect" /> </intent-filter> </activity>
-
You must set
android:autoVerify
totrue
. This instructs Android to verify the against theassetlinks.json
file you update in the next step. -
Specify the
scheme
,hosts
, andpath
parameters that will be used in your redirect URIs. The host value must match the domain where you upload theassetlinks.json
file.
To learn more about intents, refer to Add intent filters in the Android Developer documentation.
To learn more about redirects and the AppAuth library, refer to Capturing the authorization redirect.
-
-
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="https" /> </intent> </queries>
-
Create or update a Digital Asset Links (
assetlinks.json
) file that associates your app with the domain.You must host the file in a
.well-known
folder on the same host that you entered in the intent filter earlier.The file will resemble the following:
https://android.example.com/.well-known/assetlinks.json[ { "relation": [ "delegate_permission/common.handle_all_urls", ], "target": { "namespace": "android_app", "package_name": "com.example.app", "sha256_cert_fingerprints": [ "c4:15:c8:f1:...:fe:ce:d7:37" ] } } ]
-
To learn more, refer to Associate your app with your website in the Android Developer documentation.
-
-
Upload the completed file to the domain that matches the host value you configured in the earlier step.
For information on uploading an
assetLinks.json
file to an Advanced PingOne Advanced Identity Cloud instance, refer to Upload an Android assetlinks.json file. -
Add the following to the
strings.xml
file:<string name="forgerock_oauth_redirect_uri" translatable="false">https://android.example.com/oauth2redirect</string>
-
Add the App Link to the Redirection URIs property of your OAuth 2.0 client. For example,
https://android.example.com/oauth2redirect
Complete the following steps to configure a custom scheme:
-
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>
forAppAuth.RedirectUriReceiverActivity
to yourAndroidManifest.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.
-
-
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>
-
Configure your application to use the redirect URI, either in the
strings.xml
file, or by usingFROptions
:- strings.xml:
-
<string name="forgerock_oauth_redirect_uri" translatable="false">com.forgerock.android:/oauth2redirect</string>
- FROptions:
-
let options = FROptions( ..., oauthRedirectUri: "com.forgerock.android:/oauth2redirect", ..., )
-
Add the custom scheme to the Redirection URIs property of your OAuth 2.0 client. For example,
com.forgerock.android:/oauth2redirect
-
-
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
This section describes how to configure your Ping SDK for iOS application to use centralized login:
-
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.
-
Apple Universal Links
-
Custom scheme
Complete the following steps to configure Universal Links:
-
In Xcode, in the Project Navigator, double-click your application to open the Project pane.
-
On the Signing & Capabilities tab, click Capability, type
Associated Domains
, and then double click the result to add the capability. -
In Domains, click the Add () button, and enter
applinks:
, followed by the hostname that will be used in your redirect URIs.The host value must match the domain where you upload the
apple-app-site-association
file. -
Create or update an
apple-app-site-association
file that associates your app with the domain.You must host the file in a
.well-known
folder on the same host that you entered in the intent filter earlier.The file will resemble the following:
https://ios.example.com/.well-known/apple-app-site-association{ "applinks": { "details": [ { "appIDs": [ "XXXXXXXXXX.com.example.AppName" ], "components": [ { "/": "/oauth2redirect", "comment": "Associate my app with the OAuth 2.0 redirect URI." } ] } ] } }
-
Upload the completed file to the domain that matches the host value you configured in the earlier step.
For information on uploading an
apple-app-site-association
file to an Advanced PingOne Advanced Identity Cloud instance, refer to Upload an iOS apple-app-site-association file.For learn more information about Universal Links and associating domains, refer to the following in the Apple Developer documentation:
-
Add the Universal Link to the Redirection URIs property of your OAuth 2.0 client. For example,
https://ios.example.com/oauth2redirect
Configure a custom URL type, for example
frauth
, so that users are redirected to your application:-
In Xcode, in the Project Navigator, double-click your application to open the Project pane.
-
On the Info tab, in the URL Types panel, configure your custom URL scheme:
-
Add the custom URL scheme to the Redirection URIs property of your OAuth 2.0 client:
-
-
Update your application to call the
validateBrowserLogin()
function:-
In your
AppDelegate.swift
file, call thevalidateBrowserLogin()
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) } }
-
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) } )
-
If your application is using
SceneDelegate
, in yourSceneDelegate.swift
file call thevalidateBrowserLogin()
function:SceneDelegate.swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) { if let url = URLContexts.first?.url { Browser.validateBrowserLogin(url) } } }
-
-
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 anephemeralAuthSession
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
This section describes how to configure your Ping SDK for JavaScript application with centralized login:
-
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. -
When the user is returned to your app, complete the OAuth 2.0 flow by passing in the
code
andstate
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?
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.
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:
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:
|
Format of signed data |
|||
Integration |
❌ |
✅ |
With device binding, after verification, the signed JWT is available in:
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:
|
Determined by the authentication node. Full configuration options:
|
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. |
|
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 |
|
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 |
|
|
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 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:
-
A server that supports the WebAuthn nodes (PingOne Advanced Identity Cloud or PingAM 7.1 or later).
-
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.
-
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 When this option is disabled, the WebAuthn nodes return a |
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.
-
In the editor drag the following nodes into the journey:
-
WebAuthn Registration node
-
WebAuthn Authentication node
-
Inner Tree Evaluator node
-
Two Choice Collector nodes
-
-
Connect the nodes similar to the following example:
-
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 eitherUNSPECIFIED
orPLATFORM
. -
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 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 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
).
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:
-
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.
-
In the Google Play Console:
-
Select the app that will be supporting mobile biometrics.
-
Navigate to Setup > App integrity > App signing.
Figure 30. App signing keys in the Google Play Console -
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.
-
-
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:
-
In the
build.gradle
file for your application, check the settings defined in thesigningConfigs
property:Example signingConfigs when using the default debug.jkssigningConfigs { debug { storeFile file('../debug.jks') storePassword 'android' keyAlias 'androiddebugkey' keyPassword 'android' } }
-
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
-
When requested, enter the keystore password, as specified in the
keyPassword
property in thebuild.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
-
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.
Host the digital asset links JSON file
-
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.
You can now proceed to Configure biometric authentication journeys.
Configure biometric authentication journeys
To use mobile biometrics with the Ping SDK for Android configure the authentication nodes in your journeys as follows:
-
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
fileFor example,
openam-docs.forgeblocks.com
You do not need the protocol or the path.
-
-
In each WebAuthn Registration node
-
Set the Authentication attachment option to either
UNSPECIFIED
orPLATFORM
-
Ensure the Accepted signing algorithms option includes either
ES256
orRS256
-
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:
-
In the Google Play Console:
-
Select the app that will be supporting mobile biometrics.
-
Navigate to Setup > App integrity > App signing.
-
In the App signing key certificate section, click Download certificate.
This downloads a local copy of the signing certificate, named
deployment_cert.der
.
-
-
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
-
Add a prefix of
android:apk-key-hash:
to the base64-encode SHA-256 fingerprint. For example:android:apk-key-hash:jEFEYh80K55iHYkxsBRLGtAP6wvjOS5Pj-ZKHHjwi0k
-
In each WebAuthn Registration node and WebAuthn Authentication node, set the Origin domains option to the value created in the previous step:
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:
-
In the
build.gradle
file for your application, check the settings defined in thesigningConfigs
property:Example signingConfigs when using the default debug.jkssigningConfigs { debug { storeFile file('../debug.jks') storePassword 'android' keyAlias 'androiddebugkey' keyPassword 'android' } }
-
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 '='
-
When requested, enter the keystore password, as specified in the
keyPassword
property in thebuild.gradle
file.The command prints the base64-encoded SHA-256 fingerprint of the signing key:
Enter keystore password: android jEFEYh80K55iHYkxsBRLGtAP6wvjOS5Pj-ZKHHjwi0k
-
Add a prefix of
android:apk-key-hash:
to the base64-encode SHA-256 fingerprint. For example:android:apk-key-hash:jEFEYh80K55iHYkxsBRLGtAP6wvjOS5Pj-ZKHHjwi0k
-
In each WebAuthn Registration node and WebAuthn Authentication node, set the Origin domains option to the value created in the previous step:
Figure 32. Example WebAuthn Registration node configuration
Summary
You have now configured your WebAuthn journey for use with the Ping SDK for Android.
You can now proceed to Configure the Ping SDK for Android for WebAuthn.
Configure the Ping SDK for Android for WebAuthn
-
Add the following dependency to the
build.gradle
file:implementation 'com.google.android.gms:play-services-fido:20.0.1'
-
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" />
-
Add an
asset_statements
string resource to thestring.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.
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.
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:
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 If the current node has both |
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 |
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.
}
}
});
Any other |
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.
-
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.
-
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
.
-
-
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:
-
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.
-
-
In each WebAuthn Registration node:
-
Set the Authentication attachment option to either
UNSPECIFIED
orPLATFORM
. -
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:
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.
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
.
Request consent
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)
}
Request consent when credentials already exist for the device
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
.
Request consent to create new credentials
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:
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.
To prevent this behavior and only accept passkeys stored on the initial client device, set the preferImmediatelyAvailableCredentials
parameter to true
.
The If the current node contains both 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 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.
Any other |
// 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
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.
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:
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:
|
Format of signed data |
|||
Integration |
❌ |
✅ |
With device binding, after verification, the signed JWT is available in:
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:
|
Determined by the authentication node. Full configuration options:
|
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. |
|
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 |
|
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 |
|
|
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 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:
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:
|
Format of signed data |
|||
Integration |
❌ |
✅ |
With device binding, after verification, the signed JWT is available in:
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:
|
Determined by the authentication node. Full configuration options:
|
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. |
|
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 |
|
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 |
|
|
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 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
BlogWebAuthnRegistration
journeyWe 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.
userUUIDtoDisplayName
scriptvar user = nodeState.get('username').asString();
nodeState.putShared('displayName', user.toString());
outcome = 'true';
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.
HasSession
scriptif (typeof existingSession !== 'undefined') {
outcome = "hasSession";
} else {
outcome = "noSession";
}
SharedStateHasUsername
scriptvar 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:
BlogWebAuthnAuthentication
journeyConfigure 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:
In subsequent authentication attempts, you are able to authenticate using your newly created passkey:
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:
WebAuthnAuthentication
callbackelse 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:
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:
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.
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 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:
FRSession.authenticate(authIndexValue: "BlogWebAuthnRegistration") { result, node, error in
self.handleNode(token: result, node: node, error: error)
}
In order to call 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:
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
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.
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:
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:
|
Format of signed data |
|||
Integration |
❌ |
✅ |
With device binding, after verification, the signed JWT is available in:
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:
|
Determined by the authentication node. Full configuration options:
|
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. |
|
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 |
|
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 |
|
|
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 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 |
---|---|---|
Registers a device to the user and optionally stores the public key and key ID in the user’s profile |
||
Non-interactive |
Stores the public key and key ID in the user’s profile if they were stored in node state |
|
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:
-
In the Project tree view of your Android Studio project, open the
Gradle Scripts/build.gradle
file for the module. -
In the
dependencies
section, add the required dependencies:Exampledependencies
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
-
-
If you do not already have CocoaPods, install the latest version.
-
If you do not already have a Podfile, in a terminal window run the following command to create a new Podfile:
pod init
-
Add the following lines to your Podfile:
pod 'FRDeviceBinding' // Add-on for Device Binding feature
-
Run the following command to install pods:
pod install
-
- Add dependencies using Swift Package Manager
-
-
With your project open in Xcode, select File > Add Package Dependencies.
-
In the search bar, enter the Ping SDK for iOS repository URL:
https://github.com/ForgeRock/forgerock-ios-sdk
. -
Select the
forgerock-ios-sdk
package, and then click Add Package. -
In the Choose Package Products dialog, ensure that the
FRDeviceBinding
library is added to your target project. -
Click Add Package.
-
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.
Customize authentication prompts
Customize or localize the text prompts that appear when accessing the private keys.
Customize authentication UI
Learn how to implement your own user interface for accessing the private keys when requesting an application PIN.
Customize key selection UI
Discover how to implement a user interface for choosing between multiple available keys.
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: TouchID-registered devices show a system-provided title and the 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: |
When using an application PIN, the device shows both the custom title and custom 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:
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.
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 |
Binding or signing did not complete before the timeout expired. |
|
Timeout |
The user cancelled binding or signing before completion. |
|
Abort |
The SDK could not locate an existing private key. Either the device has not yet been bound, or the private key was removed. |
|
Unsupported |
The user failed the authentication required to access the private key. For example, they used an unrecognized fingerprint, or the wrong application PIN. |
|
Unsupported |
An unknown, unexpected error occurred. |
|
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 Figure 46. Custom client error outcome in the device binding node.
|
Device profile client configuration
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 |
---|---|---|
Gather location data and other metadata from the client device. |
||
Non-interactive |
Compare collected device data with that stored in the user’s profile. |
|
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:
-
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
orUnknown 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.
The code is available on GitHub: https://github.com/ForgeRock/forgerock-device-match-script
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:
|
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:
-
Configure the
maxUnmatchedAttrs
parameter to specify the maximum allowed number of allowed mismatches. -
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:
-
Download the device match script project from the GitHub repository:
git clone https://github.com/ForgeRock/forgerock-device-match-script.git
-
In a terminal window, navigate to the root of the device match script project:
cd forgerock-device-match-script
-
Run
npm
to download and install the required packages and modules:npm install
-
Build the device match script with
npm
:npm run build:widget
-
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.
-
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.
-
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:
-
Generate a public/private key pair, and store the
KeyPair
in theAndroidKeyStore
(Shared Storage). -
Hash the public key with SHA1.
-
Encode with Base64.
-
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:
-
Generate an RSA key pair with key size of 2048.
-
Persist RSA keys in the Shared Keychain Service.
-
Hash the public key with SHA1.
-
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
-
Extend the callback that you want to override, providing two constructors that match the parent constructors.
-
Annotate the constructor with the
@Keep
annotation. -
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); } }
-
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 |
---|---|
|
Main collector that includes other collectors and provides a collector version. |
|
Collect BLE support information of the device. |
|
Collect browser information of the device; specifically, the |
|
Collect camera information of the device. |
|
Collect display information of the device. |
|
Collect hardware-related information, such as the number of CPUs, number of active CPUs, and so on. |
|
Collect location Information of the device. |
|
Collect network information of the device. |
|
Collect platform-related information, such as device jailbreak status, time zone/locale, OS version, device name, and device model. |
|
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
-
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); } }
-
Use
FRDeviceCollectorBuilder
to add your customCollector
: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 |
---|---|
|
A unique ID for the device. To learn more about the device identifier, refer to Uniquely identifying devices. |
|
The location of a device (longitude and latitude values). This is configured in the node and requires user permissions. |
|
Metadata for the device, including:
|
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);
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
-
The
DeviceCollector
protocol is a baseline class implementation protocol for theFRDeviceCollector
.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) } }
-
Add the custom collector in the
DeviceProfileCallback
array ofProfileCollectors
:deviceProfileCallback.profileCollector.collectors.append(CustomCollector())
-
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
|
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:
-
In your app, add the following code after the SDK initialization:
FRDevice.currentDevice?.getProfile(completion: { (deviceProfile) in print(deviceProfile) })
-
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 |
---|---|
|
Main collector that includes other collectors and provides a collector version. |
|
Collect BLE support information of the device. |
|
Collect browser information from the device; specifically, the |
|
Collect camera information of the device. |
|
Collect display information of the device. |
|
Collect hardware-related information such as the number of CPUs, number of active CPUs, and so on. |
|
Collect location information of the device. |
|
Collect network information of the device. |
|
Collect platform-related information, such as device jailbreak status, time zone/locale, OS version, device name, and device model. |
|
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 |
---|---|
|
A unique ID for the device. To learn more about the device identifier, refer to Uniquely identifying devices. |
|
The location of a device (longitude and latitude values). This is configured in the node and requires user permissions. |
|
Metadata for the device, including:
|
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 |
---|---|
|
|
|
|
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.
|
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
-
Get the callback and any messages to display to the user:
const deviceCollectorCb = step.getCallbackOfType('DeviceProfileCallback'); const message = deviceCollectorCb.getMessage();
-
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();
-
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.
-
Call the
getProfile()
method of theFRDevice
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. -
Set the device profile information for the step:
step.getCallbackOfType('DeviceProfileCallback').setProfile(profile);
Default collectors
Collector name | Description |
---|---|
|
Collect font information. |
|
Collect display information of the device. |
|
Collect browser information of the device. |
|
Collect hardware related information. |
|
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
-
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' ] });
-
Replace the methods for further customization:
device.getHardwareMeta = function () { let obj; // Custom logic to collect hardware profile obj return obj; }
-
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 |
---|---|
|
A unique ID for the device. To learn more about the device identifier, refer to Uniquely identifying devices. |
|
The location of a device (longitude and latitude values). This is configured in the node and requires user permissions. |
|
Metadata for the device, including:
|
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.
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:
-
Log in to the PingAM console as an administrator, for example
amAdmin
. -
Navigate to Configure > Global Services > Audit Logging.
-
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
-
Save your changes.
Device profile data will no longer appear in the
authentication
audit logs.
Set up social login
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:
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 |
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
-
Log in to your Apple developer account.
-
In the Program resources category, under Certificates, Identifiers & Profiles, click Identifiers.
-
Click the plus button () next to the Identifiers header.
-
Select App IDs, and click Continue.
-
Select App type, and click Continue.
-
Type a description of your app, and provide a
Bundle ID
usingreverse-domain name style
.For example
com.forgerock.ios.sdk.example
. -
Select
Sign in with Apple
, and click Continue. -
Review your entry, and click Register.
Create a service ID
-
On the Identifiers page, click the plus button () next to the Identifier header.
-
Select Service IDs, and click Continue.
-
Enter a description of your service.
-
Enter an
Identifier
that is similar to your app ID.For example,
<app-id>.service
. -
Click Continue.
-
Review your entry, and click Register.
Configure the Apple sign in service
-
On the Identifiers page, click the dropdown next to the magnifying glass icon, and then select Services IDs.
-
Select the service ID you created.
-
Next to
Sign in with Apple
, click Configure. -
Click the plus button next to the Website URLs header.
-
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
-
-
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>
-
Click Next.
-
Review, and click Done.
Create a key
Store your key in a safe location. You cannot download keys more than once.
-
On the developer account page, in the left navigation panel, click Keys.
-
Click the plus button next to the Keys header.
-
Enter your key name, and select Sign in with Apple.
-
Click Configure, select your primary app ID, and click Save.
-
Click Continue.
-
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 theAppID
(bundle identifier) from the Apple Development portal. -
For Web or Android: The
client_id
should be theServiceID
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:
-
Click the Create App button.
-
Select Consumer for app type, and click Next.
-
Enter your app’s display name and contact email.
-
Click the Create app button.
-
On the Add products to your app page, under Facebook Login, click Set up.
-
In the left navigation panel, click Settings > Basic.
-
Take note of the App ID and App secret values.
Generate a key hash
The default password for Android Studio is android
.
-
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
-
Note the key hash value for later use.
Configure an Android app
-
On the developer apps page, double-click an Android app.
-
In the left navigation panel, select Settings > Basic.
-
At the bottom of the page, click Add platform, select Android, and click Next.
-
In the Select Android Store dialog, select a store.
For example, Google Play.
-
Click Next.
-
Scroll down to the Android section.
-
In the Key hashes field, enter the key hash value you generated earlier.
-
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
. -
In the Class Name field, enter your app’s class name.
-
Click Save changes.
Configure an iOS app
-
On the developer apps page, select iOS.
-
Click Next.
-
Enter your Bundle ID.
The name is often a reverse domain name, such as
com.example.app
. -
Click Save changes.
-
In the left navigation panel, under Facebook Login, select Quickstart.
-
Click iOS.
-
Read the information, and select your package manager.
-
Click Next.
-
Enter your Bundle ID.
-
Click Save.
-
Click Continue.
-
Select your single sign-on settings.
-
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:
-
In the left navigation, click Credentials.
-
Click CREATE CREDENTIALS > OAuth client ID.
- For an Android app
-
-
Select
Android
as the value for Application Type. -
In the Name field, type a name for this application.
-
Enter the package name from the
AndroidManifest.xml
file. -
Enter the SHA-1 certificate fingerprint.
Use the following command to get the fingerprint:
keytool -keystore path-to-debug-or-production-keystore -list -v
-
Click Create.
-
- For an iOS app
-
-
Select
iOS
as the value for Application Type. -
In the Name field, type a name for this application.
-
Enter the bundle id as listed in the app’s
Info.plist
file. -
If the app is listed in the Apple App Store, enter the Apple ID of the app.
-
Enter the Team ID that Apple assigned to your team.
-
Click Create.
-
- For a JavaScript app
-
-
Select
Web application
as the value for Application Type. -
In the Name field, type a name for this application.
-
Under Authorized JavaScript Origins, add the origins of the apps that use Google as an IdP.
Origins include scheme, domain, and port.
-
Under Authorized redirect URIs, add the full redirect URLs of your apps that handle the redirection from Google after user login.
-
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:
-
Create an OAuth 2.0 client for the Android application.
See the step for an Android app above.
-
Create an OAuth 2.0 client for PingAM to communicate with the Google APIs.
See the step for a JavaScript app above.
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
-
To access the configuration, log in to the PingOne Advanced Identity Cloud tenant as an administrator.
-
Click Native Consoles.
-
Click Access Management.
-
Click Services.
-
Click Social Identity Providers Service, or create it by clicking Add a Service.
Once in the service, ensure it is enabled.
-
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
-
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 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)
-
For native iOS and Android apps, set Enable Native Nonce to
OFF
.The option is
ON
by default. -
Click Create.
-
Ensure that the configuration is enabled, and the
Transform Script
is set toGoogle Profile Normalization
, orFacebook Profile Normalization
.
Configure Apple
-
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 isapple_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.
-
Sign in with your Apple ID used for authentication at the Apple ID page.
-
Under Security > Sign in with Apple, click Manage apps & websites > Apps & websites using Apple ID.
-
Select the application you are developing using Sign in with Apple.
-
Click Stop using Apple ID.
-
Attempt to re-authenticate in your app.
For more information, read Sign in with Apple - Updated Scope Not Reflected in JWT Claims.
-
- Well Known Endpoint
-
https://appleid.apple.com/.well-known/openid-configuration
- Issuer
-
https://appleid.apple.com
-
Click Create to save your configuration.
-
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
calledlocalAuthentication
. 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
, andLocal 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, thenNATIVE
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.
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 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:
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
)
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
|
SDK configuration
For Google Sign-In, add the following dependency to the build.gradle
file:
implementation 'com.google.android.gms:play-services-auth:20.5.0'
For Facebook Login:
-
Add the Facebook dependency to the
build.gradle
file:implementation 'com.facebook.android:facebook-login:16.0.0'
-
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>
-
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
andfb_login_protocol_scheme
, go to the Facebook Developer Console, and copy the App ID value:Prefix your App ID value with
fb
to get thefb_login_protocol_scheme
value.For example, if your App ID is
123456781234567
, yourfb_login_protocol_scheme
isfb123456781234567
.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: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:
-
Add the
AppAuth
dependency to thebuild.gradle
file:implementation 'net.openid:appauth:0.7.1'
-
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>
-
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: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
-
Create a Facebook client for iOS.
For details, see Configure social login identity providers.
Facebook provides you with the
.plist
configuration. -
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 generatedCustom URL Scheme
, and theLSApplicationQueriesSchemes
, should look something like this: -
Include the
FRFacebookSignIn
module in your project.The
FRFacebookSignIn
is a new module that is distributed separately ofFRAuth
.Assuming you are using CocoaPods, add the following lines in your projects
Podfile
:pod 'FRAuth' pod 'FRFacebookSignIn' ... ... Other Pods ...
-
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. -
Initialize the Facebook sign-in handler in your app’s
AppDelegate
file:-
Locate the following method:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?)
-
Add a call to the
FacebookSignInHandler.application(_:didFinishLaunchingWithOptions:)
method, before thereturn 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
-
Create a Google client for iOS.
For details, refer to Create a Google client.
-
Access the client in the Google Console, and make a note of the generated custom iOS URL scheme:
-
Configure your Xcode project with the Google generated custom iOS URL scheme:
-
Select your project file, select the app target, and in the Info pane, expand the URL Types option.
-
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:
-
-
Include the
FRGoogleSignIn
module in your project.The
FRGoogleSignIn
is a new module that is distributed separately ofFRAuth
.Assuming you are using CocoaPods, add the following lines in your projects
Podfile
:pod 'FRAuth' pod 'FRGoogleSignIn' ... ... Other Pods ...
-
Run the following command to install pods:
pod install
The FRGoogleSignIn module is not available through the Swift Package Manager.
|
Configure Apple
-
Create an Apple Client for iOS.
For details, refer to Create an Apple client.
-
Configure your Xcode project with the Google generated custom iOS URL scheme.
-
Select your project file, select the app target and go to the Signing & Capabilities tab in Xcode.
-
Click the + Capability button, and search for
Sign In with Apple
.After enabling the capability the Xcode page should look something like this.
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
|
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.
Suspend and resume authentication with magic links
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
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:
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.
-
Create an
AuthorizationPolicy
with the URL to evaluate policies against, and a delegate ofAuthorizationPolicyDelegate
:let authPolicy = AuthorizationPolicy( validatingURL: ["https://protectedendpoint"], delegate: self )
-
Add
AuthorizationPolicy
toFRURLProtocol
FRURLProtocol.authorizationPolicy = authPolicy
-
Register the
FRURLProtocol
class:URLProtocol.registerClass(FRURLProtocol.self)
-
Create a
URLSessionConfiguration
withFRURLProtocol
, and create aURLSession
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 namedWww-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()
-
(Optional) If the SDK is unable to parse the response into
policyAdvice
, construct it with the given response by implementing theAuthorizationPolicyDelegate.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 } } }
-
Initiate authentication tree flow, including
policyAdvice
, by implementing theAuthorizationPolicyDelegate.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 } } } }
-
(Optional) Decorate the original
URLRequest
object with updated information, by implementing theAuthorizationPolicyDelegate.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
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
The Ping SDKs can integrate with PingOne Protect to evaluate the risk involved in a transaction.
PingOne Protect is supported in the following servers:
|
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:
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:
-
In the PingOne administration console, navigate to Add ().
, and then click -
In the Add Application panel:
-
In Application name, enter a unique identifier for the worker application.
For example,
Ping SDK Worker
. -
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.
-
In Application Type, select Worker.
-
Click Save.
-
-
In the application properties panel for the worker application you created:
-
On the Roles tab, click Grant Roles.
-
On the Available responsibilities tab, select the Identity Data Admin row, and ensure the environment is correct.
-
Click Save.
-
On the Overview tab, ensure your worker application resembles the following image, and then enable it by using the toggle (1):
Figure 50. Example worker application in PingOne -
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.
|
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:
-
In the PingOne Advanced Identity Cloud admin UI, go to Tenant Settings > Global Settings > Environment Secrets & Variables.
-
Click the Secrets tab.
-
Click + Add Secret.
-
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.
-
Click Save to create the variable.
-
Click View Update, check the details of the new secret, and then click Apply Update.
Advanced Identity Cloud displays a final confirmation page.
Figure 51. Apply updated secrets in Advanced Identity Cloud -
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:
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:
-
If you are using PingOne Advanced Identity Cloud, in the administration console navigate to Native Consoles > Access Management.
-
In the AM admin UI, click Services.
-
If the PingOne Worker Service is in the list of services, select it.
-
If you do not yet have a PingOne Worker Service:
-
Click Add a Service.
-
In Choose a service type, select
PingOne Worker Service
, and then click Create.
-
-
On the Secondary Configurations tab, click Add a Secondary Configuration.
-
On the New workers configuration page:
-
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.
-
In Client ID, enter the client ID of the PingOne Worker application you created earlier.
-
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. -
In Environment ID, enter the environment ID containing the PingOne Worker application you created earlier.
-
Click Create
-
-
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
-
Confirm your configuration resembles the image below, and then click Save changes.
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
-
In the PingOne Advanced Identity Cloud admin UI, click Native Consoles > Access Management.
-
In the AM admin UI (native console), go to Realm > Secret Stores.
-
Click the ESV secret store, then click Mappings.
-
Click + Add Mapping.
-
In Secret Label, select the label generated when you entered the Client Secret Label Identifier previously.
For example,
am.services.pingone.worker.workerAppClientSecret.clientsecret
. -
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:
-
-
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 |
---|---|---|
Instruct the embedded PingOne Signals SDK to start gathering contextual information. |
||
Returns contextual information that the server can send to your PingOne Protect instance to perform a risk evaluation. |
||
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 |
In your server, log in as an administrator and create a new authentication journey similar to the following example:
-
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.
Learn more at initializing data collection.
-
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
orLow
-
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:
Ping SDK for Android
Add the PingOne Protect dependencies to your Android project.
Ping SDK for iOS
Add the PingOne Protect dependencies to your iOS project by using Cocoapods or Swift Package Manager.
Ping SDK for JavaScript
Add the PingOne Protect dependencies to your JavaScript project by using npm.
Add Android dependencies
To add the PingOne Protect dependencies to your Android project:
-
In the Project tree view of your Android Studio project, open the
Gradle Scripts/build.gradle
file for the module. -
In the
dependencies
section, add the required dependencies:Exampledependencies
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
-
If you do not already have CocoaPods, install the latest version.
-
If you do not already have a Podfile, in a terminal window, run the following command to create a new Podfile:
pod init
-
Add the following lines to your Podfile:
pod 'PingProtect' // Add-on for {p1} Protect
-
Run the following command to install pods:
pod install
Add dependencies using Swift Package Manager
-
With your project open in Xcode, select File > Add Package Dependencies.
-
In the search bar, enter the Ping SDK for iOS repository URL:
https://github.com/ForgeRock/forgerock-ios-sdk
. -
Select the
forgerock-ios-sdk
package, and then click Add Package. -
In the Choose Package Products dialog, ensure that the
PingProtect
library is added to your target project. -
Click Add Package.
-
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 |
|
|
Required. Your PingOne environment identifier. |
||
|
Optional. A list of device attributes to ignore when collecting device signals. For example, |
||
|
|
When Default is |
|
|
|
When Default is |
|
|
|
When Default is |
|
N/A |
|
Number of days that device attestation can rely upon the device fallback key. Default: |
|
N/A |
|
When When Default is |
|
N/A |
|
When Tags are used to record the pages the user visited, forming a browsing history. Default is |
|
N/A |
|
When Default is |
|
N/A |
|
Optional. A list of custom identifiers that are associated with the device entity in PingOne Protect. |
|
N/A |
|
Optional. The iframe URL to use for cross-storage device IDs. |
|
N/A |
|
When Default is |
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
-
Log in to the Admin UI of your PingOne Advanced Identity Cloud tenant using an administrator account.
-
Navigate to Realm Settings > Theme.
-
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.
-
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).
-
-
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.
-
Use the Theme Styles tab to change the look and feel of the UI on your hosted page.
Test your theme configuration
-
Log out of the Admin UI.
-
In a new tab, access the default login journey of your tenant:
https://[TENANT-ADDRESS]/am/XUI/?realm=alpha&authIndexType=service&authIndexValue=Login
-
Use a user (non-admin) account to authenticate.
-
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.
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
-
In the Platform Admin UI, log in to the PingAM Native console.
-
In the PingAM Native console, click Services.
-
If there is no OAuth2 Provider, create one using the default settings.
-
In the OAuth2 Provider configuration, on the Consent tab, enable the Allow Clients to Skip Consent option.
-
On the Configure OAuth2 tab, enable the Issue Refresh Tokens option.
-
On the Advanced OpenID Connect tab, enable the claims_parameter_supported option.
Configure the OAuth2.0 client
-
In the PingAM Native console, navigate to Applications > OAuth 2.0 > Clients.
-
Create a new OAuth 2.0 client named “sdkPublicClient”.
-
Set the following properties:
-
Client ID = sdkPublicClient
-
Client secret = (leave blank)
-
Redirection URIs = https://localhost:8443
-
Scope(s) = openid profile email address
-
-
Click Create.
-
On the Core tab:
-
Set the Client type property to Public.
-
Disable the Allow wildcard ports in redirect URIs property.
-
-
Click Save.
-
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.
-
-
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'); |
-
Create a centralized login instance using the
Config
class. -
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', });
-
Create a login button with the tag ‘#loginBtn’.
-
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)); }); });
-
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); } });
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
-
In the PingAM native console, navigate to Applications > OAuth 2.0 > Clients.
-
Create a new OAuth 2.0 client called “iOSClient”.
-
Enter the following details:
-
Client ID = iOSClient
-
Client secret = (leave blank)
-
Redirection URIs = frauth://com.forgerock.ios.frexample
-
Scope(s) = openid profile email address
-
-
Click Create.
-
On the Core tab:
-
Set the Client type property to Public.
-
Disable the Allow wildcard ports in redirect URIs property.
-
-
Click Save.
-
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
-
In the PingAM native console, navigate to Applications > OAuth 2.0 > Clients.
-
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
-
-
Click Create.
-
On the Core tab:
-
Set the Client type property to Public.
-
Disable the Allow wildcard ports in redirect URIs property.
-
-
Click Save.
-
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.
-
-
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
-
Create a new Xcode project and add the Ping SDK for iOS using cocoapods.
-
In the Podfile, add the following:
pod 'FRAuth' pod 'FRUI' pod 'FRProximity'
-
In a terminal window, navigate to the root of your Xcode project and run the following command:
pod install
-
When Pod installation is complete, open the project’s ‘xcworkspace’ file.
-
In the
Main.storyboard
file: -
Add a Login button in the
ViewController
view. -
Connect the Login button with the
ViewController
class creating anIBAction
. -
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 }
-
Call the
performCentralizedLogin
function from the Login buttonIBAction
.
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:
-
In Xcode, in the Project Navigator, double-click your app to open the Project pane.
-
On the Info tab, in the URL Types panel, configure your custom URL scheme:
-
Update your application to call the
validateBrowserLogin()
function:-
In your
AppDelegate.swift
file, call thevalidateBrowserLogin()
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) } }
-
If your application is using
SceneDelegate
, in yourSceneDelegate.swift
file call thevalidateBrowserLogin()
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:
For more information, see the sample app demo in the SDK Sample Apps repo on GitHub.
Configure the Android Studio project
Android compile options
-
In the Project tree view of your Android Studio project, navigate to the
app
folder. -
Open the
build.gradle
file. -
Add the following code at the top level:
kotlin { jvmToolchain { languageVersion.set(JavaLanguageVersion.of(17)) } }
-
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'
-
Navigate to app > src > main > res > values.
-
Open the
strings.xml
file. -
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>
-
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
-
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:
For more information, see the sample app demo in the SDK Sample Apps repo on GitHub.
Navigate to a specific journey using centralized login
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:
-
In the PingAM admin UI, navigate to Services.
-
Select the OAuth 2.0 provider.
-
Navigate to the Advanced OpenID Connect tab.
-
Confirm that Enable "claims_parameter_supported" is set to ON.
-
In OpenID Connect acr_values, add a new entry:
Auth Chain Mapping
. -
Save the configuration.
Configure ACR values in the Ping SDK for JavaScript
Configure SPA to use the SimpleLogin journey
-
Returning to the SPA from part two of the guide, open the project in Visual Studio Code.
-
Create a new constant:
const acr_values = 'simple';
-
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 thesearchParams
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 theTokenManager.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.