Orchestration SDKs

Configuring iOS apps for the Device Authorization Grant

PingOne PingOne Advanced Identity Cloud PingAM iOS

You can configure iOS apps built with the Orchestration SDK to authenticate users using the OAuth 2.0 Device Authorization Grant (RFC 8628).

This is suitable for devices with limited input capabilities, such as smart TVs, kiosks, or IoT devices. The requesting app starts an authorization request, displays a short code and URL to the user, and polls in the background while the user authenticates on a separate browser-capable device.

Configuring an iOS app as the requesting device

To configure an iOS app as the requesting device in the Device Authorization Grant flow, complete each of the following steps:

Before you begin

You need to prepare your authorization server to support the Device Authorization Grant.

Select your server from the options below and complete the tasks before proceeding to configure your application.

  • PingOne

  • Advanced Identity Cloud

  • PingAM

This tutorial requires you to configure your PingOne server as follows:

Task 1. 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, follow these steps:

  1. Log in to your PingOne administration console.

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

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

    PingOne displays the Add User panel.

  4. Enter the following details:

    • Given Name = Demo

    • Family Name = User

    • Username = demo

    • Email = demo.user@example.com

    • Population = Default

    • Password = Ch4ng3it!

  5. Click Save.

Task 2. Register a public OAuth 2.0 client with device authorization enabled

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

  1. Log in to your PingOne administration console.

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

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

    PingOne displays the Add Application panel.

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

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

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

    1. In Grant Type, select the following values:

      Authorization Code

      Refresh Token

      Device Authorization

    2. In Token Endpoint Authentication Method, select None.

    3. Click Save.

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

    1. In Scopes, select the following values:

      email

      phone

      profile

      The openid scope is selected by default.
  8. Optionally, on the Policies tab, click the pencil icon () to select the authentication policies for the application.

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

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

    To use a PingOne policy:

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

    2. Click Save.

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

      For more information, see Authentication policies for applications.

    To use a DaVinci Flow policy:

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

    2. In the confirmation message, click Continue.

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

    4. Click Save.

      PingOne applies the first policy in the list.

  9. Click Save.

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

    Enable the application using the toggle.
    Figure 1. 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.

This tutorial requires you to configure your PingOne Advanced Identity Cloud tenant as follows:

Task 1. 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:

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

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

  3. Click New Alpha realm - User.

  4. Enter the following details:

    • Username = demo

    • First Name = Demo

    • Last Name = User

    • Email Address = demo.user@example.com

    • Password = Ch4ng3it!

  5. Click Save.

Task 2. Register a public OAuth 2.0 client with device authorization enabled

Public clients do not use a client secret to obtain tokens because they are unable to keep them hidden. The Orchestration 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.

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

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

  2. In the left panel, click Applications.

  3. Click Custom Application.

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

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

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

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

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

  9. Select Configure for SDK Sample Apps.

  10. Click Create Application.

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

  11. On the Sign On tab:

    1. In Grant Types, ensure the following values appear:

      Authorization Code

      Refresh Token

      Device Code

    2. In Scopes, ensure the following values appear:

      openid profile email address

  12. Click Show advanced settings, and on the Authentication tab, confirm the following properties:

    1. In Token Endpoint Authentication Method, select none.

    2. In Client Type, select Public.

    3. Enable the Implied Consent property.

  13. 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.

Task 3. 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 Orchestration SDKs, follow these steps:

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

  2. In the left panel, click Services.

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

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

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

  6. Click Save Changes.

This tutorial requires you to configure your PingAM server as follows:

Task 1. 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:

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

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

  3. Enter the following details:

    • User ID = demo

    • Password = Ch4ng3it!

    • Email Address = demo.user@example.com

  4. Click Create.

Task 2. Register a public OAuth 2.0 client with device authorization enabled

Public clients do not use a client secret to obtain tokens because they are unable to keep them hidden. The Orchestration 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.

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

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

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

  3. In Client ID, enter sdkPublicClient.

  4. Leave Client secret empty.

  5. In Scopes, enter the following values:

    openid profile email address

  6. Click Create.

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

  7. On the Core tab:

    1. In Client type, select Public.

    2. Disable Allow wildcard ports in redirect URIs.

    3. Click Save Changes.

  8. On the Advanced tab:

    1. In Grant Types, enter the following values:

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

    3. Enable the Implied consent property.

  9. Click Save Changes.

Task 3. 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 Orchestration SDKs, follow these steps:

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

  2. In the left panel, click Services.

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

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

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

  6. Click Save Changes.

Step 1. Installing modules

To use the Device Authorization Grant on iOS, add the PingOidc library to your project.

  • SPM (Swift Package Manager)

  • CocoaPods

  1. In Xcode, in the Project Navigator, right-click your project, and then click Add Package Dependencies…​.

  2. In the Search or Enter Package URL field, enter the URL of the repo containing the Orchestration SDK for iOS: https://github.com/ForgeRock/ping-ios-sdk.git.

  3. In Add to Project, select the name of your project, and then click Add Package.

  4. Select the PingOidc library, and in the Add to Target column select the name of your project.

  5. Click Add Package.

  1. If you don’t already have CocoaPods, install the latest version.

  2. Add the following line to your Podfile:

    pod 'PingOidc'
  3. Run the following command to install pods:

    pod install

The Device Authorization Grant does not require any browser-related libraries unless you use the optional authorize(verificationUriComplete:) method described in Opening the authorization URL in SFSafariViewController.

Step 2. Configuring the device client

Use OidcDeviceClient to configure the connection to your authorization server.

The configuration is similar to the standard OidcWebClient, but does not require a redirectUri.

Required parameters to configure OidcDeviceClient
let deviceClient = OidcDeviceClient.createOidcDeviceClient { config in
    config.discoveryEndpoint = "https://auth.pingone.ca/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/as/.well-known/openid-configuration"
    config.clientId = "6c7eb89a-66e9-ab12-cd34-eeaf795650b2"
    config.scopes = ["openid", "email", "address", "profile", "phone"]
}

Update the following properties with values that match your environment:

discoveryEndpoint

The .well-known endpoint from your OAuth 2.0 application.

How do I find my PingOne .well-known URL?

To find the .well-known endpoint for an OAuth 2.0 client in PingOne:

  1. Log in to your PingOne administration console.

  2. Go to Applications > Applications, and then select your OAuth 2.0 client.

    For example, sdkPublicClient.

  3. On the Overview tab, expand the Connection Details section, and then copy the OIDC Discovery Endpoint value.

    Locating the .well-known URL in a PingOne client profile.
How do I find my PingOne Advanced Identity Cloud .well-known URL?

You can view the .well-known endpoint for an OAuth 2.0 client in the PingOne Advanced Identity Cloud admin console:

  1. Log in to your PingOne Advanced Identity Cloud administration console.

  2. Click Applications, and then select the OAuth 2.0 client you created earlier. For example, sdkPublicClient.

  3. On the Sign On tab, in the Client Credentials section, copy the Discovery URI value.

    For example, https://openam-forgerock-sdks.forgeblocks.com/am/oauth2/alpha/.well-known/openid-configuration

If you are using a custom domain, your .well-known is formed as follows:

https://<custom-domain-fqdn>/.well-known/openid-configuration

How do I find my PingAM .well-known URL?

To form the .well-known URL for an PingAM server, concatenate the following information into a single URL:

  1. The base URL of the PingAM component of your deployment, including the port number and deployment path.

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

  2. The string /oauth2

  3. The hierarchy of the realm that contains the OAuth 2.0 client.

    You must specify the entire hierarchy of the realm, starting at the Top Level Realm. Prefix each realm in the hierarchy with the realms/ keyword.

    For example, /realms/root/realms/customers

    If you omit the realm hierarchy, the top level ROOT realm is used by default.

  4. The string /.well-known/openid-configuration

For example, https://auth.pingone.ca/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/as/.well-known/openid-configuration

clientId

The client ID of your OAuth 2.0 application.

For example, 6c7eb89a-66e9-ab12-cd34-eeaf795650b2

scopes

The scopes you added to your OAuth 2.0 application.

For example, "openid", "email", "address", "profile", "phone"

Specifying the device authorization endpoint for Advanced Identity Cloud and PingAM

If you are using the device authorization flow with an Advanced Identity Cloud tenant or PingAM server you must also add the device authorization endpoint in the configuration, as it is not present in the data returned by the .well-known endpoint.

Update the default config with a deviceAuthorizationEndpoint value in the openIdOverride property, as follows:

Adding the device authorization endpoint to OidcDeviceClient config
let deviceClient = OidcDeviceClient.createOidcDeviceClient { config in
    config.discoveryEndpoint = "https://openam-forgerock-sdks.forgeblocks.com/am/oauth2/realms/alpha/.well-known/openid-configuration"
    config.clientId = "sdkPublicClient"
    config.scopes = ["openid", "email", "address", "profile"]
    config.redirectUri = "org.forgerock.demo://oauth2redirect"
    config.openIdOverride = { openId in
        openId.deviceAuthorizationEndpoint = "https://openam-forgerock-sdks.forgeblocks.com/am/oauth2/realms/root/realms/alpha/code"
    }
}

Lear more about the /oauth2/device/code endpoint in the Advanced Identity Cloud documentation.

Step 3. Starting the flow

Call deviceAuthorization() on the device client to start the flow. This returns a stream of DeviceFlowStatus values, one for each stage of the authorization process.

The first stage to handle is .started. This status contains a DeviceAuthorizationResponse object with the values you need to present to the user, so that they can authenticate on a different device.

Handle .started to display the values from the server
let stream = try await deviceClient.deviceAuthorization()
for try await status in stream {
    switch status {
        case .started(let response):
            let userCode = response.userCode
            let verificationUri = response.verificationUri
            let verificationUriComplete = response.verificationUriComplete
            // Display userCode and verificationUri in your UI.
            // Optionally, generate a QR code from verificationUriComplete.
        default:
            break
    }
}

The response object contains the following properties:

userCode

The short human-readable code the user enters on the approving device.

verificationUri

The URL the user navigates to on the approving device to authorize the request.

verificationUriComplete

An optional URL with the userCode already embedded as a query parameter. Use this to generate a QR code: when the user scans it, the authorization server’s page opens with the code pre-filled, so the user does not need to type it manually.

expiresIn

The number of seconds until the deviceCode and userCode expire.

interval

The minimum number of seconds the SDK waits between polling requests.

Rendering the QR code or any specific UI is the responsibility of your app.

The Orchestration SDK only provides the data values.

Opening the authorization URL in SFSafariViewController

If your iOS app is itself capable of completing the authorization, you can open the verificationUriComplete URL directly in an SFSafariViewController, rather than waiting for the user to scan a QR code or type the URL on a separate device.

Call authorize(verificationUriComplete:) on the device client in a separate task, concurrently with the polling loop:

Open the verification URL in-app using SFSafariViewController
case .started(let response):
    Task {
        try? await deviceClient.authorize(
            verificationUriComplete: response.verificationUriComplete ?? response.verificationUri
        )
    }

The method opens the URL in an SFSafariViewController and suspends until the user completes or dismisses the browser session. The polling loop running in the outer for try await loop continues independently and emits .success once the server returns a token.

verificationUriComplete is optional and may be nil if the authorization server does not return it. The ?? response.verificationUri fallback ensures a URL is always provided.

Because authorize(verificationUriComplete:) opens a browser on the requesting device, use it only when the device has browser capability. For true headless devices (smart TVs, kiosks, IoT devices), display the QR code or user code instead.

Step 4. Polling for success

After emitting .started, the Orchestration SDK automatically begins polling the server’s token endpoint. You do not need to manage the polling loop yourself.

The two events to handle when polling are as follows:

.polling

Emitted after each poll while the user has not yet approved the request.

Use this to update a progress indicator if needed. The status includes pollCount, pollInterval, and nextPollAt values.

.success

Emitted once the user approves the request and the server returns a token.

The Orchestration SDK automatically saves the tokens in the configured storage solution.

Add .polling and .success cases
let stream = try await deviceClient.deviceAuthorization()
for try await status in stream {
    switch status {
        case .started(let response):
            // Display codes
        case .polling(let pollCount, let pollInterval, let nextPollAt):
            // Optional: update a progress indicator
            break
        case .success:
            // Authentication complete — tokens saved!
            return
        default:
            break
    }
}

Step 5. Handling errors

The Orchestration SDK emits three terminal error statuses you should check for:

.expired

The device code expired before the user approved the request.

Restart the flow to obtain a new device code.

.accessDenied

The user explicitly denied the authorization request.

.failure

An unrecoverable error occurred.

The error associated value contains the cause.

Complete flow handler with all cases
let stream = try await deviceClient.deviceAuthorization()
for try await status in stream {
    switch status {
        case .started(let response):
            // Display codes
        case .polling(let pollCount, let pollInterval, let nextPollAt):
            // Optional: update a progress indicator
            break
        case .success:
            // Authentication complete — tokens saved!
            return
        case .expired:
            // The device code has expired — restart the flow
            return
        case .accessDenied:
            // The user denied the request
            return
        case .failure(let error):
            // Handle unexpected errors
            return
    }
}

Configuring an iOS app as the approving device

An approving device is often a phone or tablet where the user authenticates to authorize the request that originated on a different device — for example, a smart TV or kiosk.

In this scenario, the approving device:

  1. Obtains the verificationUriComplete URL from the requesting device. Typically the device will scan a QR code, but can use any method for obtaining a URI.

  2. Authenticates the user through a Journey or DaVinci flow - either natively, or by using a browser.

  3. Signals approval to the authorization server on the user’s behalf.

The Orchestration SDK handles step 3 automatically after the user successfully authenticates with the authorization server.

Approving a device authorization flow natively

To signal to the Orchestration SDK that it is acting as the approving device, pass the verificationUriComplete URL in the options block when natively starting a Journey or DaVinci flow:

  • Journey

  • DaVinci flow

Starting a Journey as the approving device
import PingJourney

let node = await journey.start("sdkUsernamePasswordJourney") { options in
    options.verificationUriComplete = URL(string: verificationUriCompleteString)
}

After the user authenticates, the OidcModule extracts the user_code from the URL and sends a POST request to verificationUriComplete containing user_code, decision=allow, and the SSO token.

The requesting device’s polling loop then receives its access token from the server.

Starting a DaVinci flow as the approving device
import PingDavinci

let node = await davinci.start { options in
    options.verificationUriComplete = URL(string: verificationUriCompleteString)
}

After the user authenticates, the OidcModule sends the userCode to the authorization server’s device flow endpoint.

The requesting device’s polling loop then receives its access token from the server.

If verificationUriComplete is not set, the module starts a normal authentication flow with no device-approval step.

In this case the polling device will never complete authentication.

Approving a device authorization flow in-app using SFSafariViewController

The approving device can open the verificationUriComplete URI in SFSafariViewController in-app, rather than running a journey of DaVinci flow natively.

Using this method, the request and approval can be completed on the same device. After user authenticates and approves the request in the browser, the polling loop, which is already running, picks up the token automatically. No second device needed.

This is useful when an app uses the Device Authorization Grant for its UX simplicity. It doesn’t require a redirect URI or app scheme configured, and has a clean polling model, and the device itself is perfectly capable of completing the approval step, so it becomes a "streamlined OIDC-via-device-flow" pattern.

To open the verificationUriComplete in an SFSafariViewController, pass it into the authorize() method:

Approving a device flow using SFSafariViewController
try await deviceClient.authorize(verificationUriComplete: response.verificationUriComplete ?? response.verificationUri)

Getting the verificationUriComplete value

The verificationUriComplete value is returned in the .started status of the requesting device’s deviceAuthorization() stream, as response.verificationUriComplete.

On the approving device it is typically obtained by scanning a QR code generated by the requesting device, or by the user entering the URL manually.

Learn more