PingOne Platform APIs

Create Application (OIDC Device Authorization Grant)

   

POST {{apiPath}}/environments/{{envID}}/applications

The POST {{apiPath}}/environments/{{envID}}/applications operation adds a new application resource to the specified environment.

This example specifies the DEVICE_CODE value on the grantTypes property. This grant uses the /{{envID}}/as/device_authorization endpoint to initiate an action that returns an activation code to the end user. For more information about authorization flows for applications configured with a device authorization grant, refer to Device Authorization Grant.

The deviceTimeout and devicePollingInterval optional OIDC properties are required when DEVICE_CODE is specified on the grantTypes property.

Generating a verification URI

Setting the deviceCustomVerificationUri property on the application gives administrators the flexibility to present a short URL to the user to complete the activation flow. Instead of presenting the user with a PingOne URL like this, https://auth.pingone.com/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/device, setting the deviceCustomVerificationUri property can give the user an easier URL to remember, such as https://acme.co/go.

The composition of the verification_uri in the /device_authorization response follows these rules:

  1. If the deviceCustomVerificationUri property is set, the response from the /device_authorization endpoint returns the exact short URL designated in the deviceCustomVerificationUri property as the verification_uri value.

  2. If the deviceCustomVerificationUri property is not set, and if the environment has a custom domain configured:

    • If the application’s devicePathId is set, the verification_uri value uses the format https://{{customDomain}}/device/{{devicePathId}} or https://{{customDomain}}/device/{{devicePathId}}?user_code={{userCode}}.

    • If the application’s devicePathId is not set, the verification_uri value uses the format https://{{customDomain}}/device or https://{{customDomain}}/device?user_code={{userCode}}.

  3. If the deviceCustomVerificationUri property is not set, and if the environment does not have a custom domain configured:

    • If the application’s devicePathId is set, the verification_uri value uses the format https://auth.pingone.<com/ca/eu/com.au/sg/asia>/{{envId}}/device/{{devicePathId}} or https://auth.pingone.<com/ca/eu/com.au/sg/asia>/{{envId}}/device/{{devicePathId}}?user_code={{userCode}}.

    • If the application’s devicePathId is not set, the verification_uri value uses the format https://auth.pingone.<com/ca/eu/com.au/sg/asia>/{{envId}}/device or https://auth.pingone.<com/ca/eu/com.au/sg/asia>/{{envId}}/device?user_code={{userCode}}.

The URLs for the verification_uri and for the redirect (refer to the next section for redirect details) are generated based on the inbound initial authorize call. For example, if the environment’s custom domain is acme.com, but the initial call is from https://auth.pingone.com/{{envID}}/as/device_authorization, then the generated verification_uri will have a format of https://auth.pingone.com/{{envId}}/device (or https://auth.pingone.com/{{envId}}/device/{{devicePathId}} if the device path ID is specified). If the initial call is from https://acme.com/as/device_authorization, the generated verification_uri will have a format of https://acme.com/{{id}}/device (or https://acme.com/{{id}}/device/{{devicePathId}}).

Configuring a redirect

If the application’s configuration sets a short URL in the deviceCustomVerificationUri property, the administrator needs to configure a redirect to one of the following options:

  1. If the environment uses a custom domain:

    • https://{{customDomain}}/device/{{applicationId}} or https://{{customDomain}}/device/{{applicationId}}?user_code={{userCode}}

    • https://{{customDomain}}/device/{{devicePathId}} or https://{{customDomain}}/device/{{devicePathId}}?user_code={{userCode}} (only if devicePathId is set)

    • https://{{customDomain}}/device or https://{{customDomain}}/device?user_code={{userCode}}

  2. If the environment does not use a custom domain:

    • https://auth.pingone.<com/ca/eu/com.au/sg/asia>/{{envId}}/device/{{applicationId}} or https://auth.pingone.<com/ca/eu/com.au/sg/asia>/device/{{applicationId}}?user_code={{userCode}}

    • https://auth.pingone.<com/ca/eu/com.au/sg/asia>/{{envId}}/device/{{devicePathId}} or https://auth.pingone.<com/ca/eu/com.au/sg/asia>/device/{{devicePathId}}?user_code={{userCode}} (only if devicePathId is set)

    • https://auth.pingone.<com/ca/eu/com.au/sg/asia>/{{envId}}/device or https://auth.pingone.<com/ca/eu/com.au/sg/asia>/device?user_code={{userCode}}

The applicationId path is safer because that value does not change (devicePathId can change). In addition, a redirect to /device without an applicationId or devicePathId is not recommended because the flow cannot adhere to an application’s configured sign-on policy. However, if you use the same deviceCustomVerificationUri property value for two separate applications, then a redirect to /device is needed, and the flow uses the environment’s default sign-on policy.

For example, if the environment’s custom domain is set to acme-corporation.com, the application’s deviceCustomVerificationUri property is set to https://acme.com/go, and the application’s ID is 18d4b06c-f8f3-4064-a2c4-0db80250fb5f, the workflow looks like this:

  1. The end user types the short URL in a browser, https://acme.com/go to start the activation flow.

  2. The administrator has configured the following redirect outside of PingOne to redirect the browser and start the device authorization flow: https://acme-corporation.com/device/18d4b06c-f8f3-4064-a2c4-0db80250fb5f.

  3. In PingOne, the flow is redirected to https://acme-corporation.com/signon?flowId=c78dbdd0-cc2c-42fa-b275-486503c30d2b to start the PingOne flow.

It is possible that the deviceCustomVerificationUri can use the same domain as the custom domain. For example, if the custom domain is simply acme.com, and the deviceCustomVerificationUri property is not set, then the verification_uri can be https://acme.com/device/ or https://acme.com/device/{{ID}}, in which the {{ID}} value is either the application ID or the device path ID. Conversely, if a deviceCustomVerificationUri is set to https://acme.com/go, then a configured redirect to https://acme.com/device/{{appId}}) is needed. Also, a redirect to /device without an applicationId or devicePathId is not recommended because the flow cannot adhere to an application’s configured sign-on policy.

Request Model

Base application data model (web application)

Refer to Applications base data model for complete descriptions.

Property Required? Type

enabled

Required

Boolean

name

Required

String

description

Optional

String

type

Required

String

protocol

Required

String

homePageUrl

Optional

URL

loginPageUrl

Optional

URL

icon.id

Optional

UUID

icon.href

Optional

URL

tags

Optional

Array

assignActorRoles

Optional

Boolean

accessControl.role.type

Optional

String

accessControl.group.type

Optional

String

accessControl.group.groups

Optional

Array

accessControl.group.groups.id

Optional

UUID

Additional OIDC properties

If you set the protocol attribute to OPENID_CONNECT, you must provide values for the required OIDC settings. Optional settings can be omitted.

Refer to Applications OIDC settings data model for complete descriptions.

Property Required? Type

devicePathId

Optional

String

deviceCustomVerificationUri

Optional

String

deviceTimeout

Required

Integer

devicePollingInterval

Required

Integer

grantTypes

Required

String

postLogoutRedirectUris

Required

URL

redirectUris

Required

URL

responseTypes

Required

String

tokenEndpointAuthMethod

Required

String

pkceEnforcement

Optional

String

refreshTokenDuration

Optional

Integer

refreshTokenRollingDuration

Optional

Integer

signing

Optional

Object

signing.keyRotationPolicy

Required

Object

signing.keyRotationPolicy.id

Required

String

supportUnsignedRequestObject

Optional

Boolean

Headers

Authorization      Bearer {{accessToken}}

Content-Type      application/json

Body

raw ( application/json )

{
    "enabled": true,
    "name": "Device-App{{$timestamp}}",
    "description": "App for Device Authorization Grant",
    "type": "CUSTOM_APP",
    "protocol": "OPENID_CONNECT",
    "grantTypes": [
        "DEVICE_CODE",
        "REFRESH_TOKEN"
    ],
    "deviceTimeout": 600,
    "devicePollingInterval": 5,
    "tokenEndpointAuthMethod": "NONE",
    "devicePathId": "go"
}

Example Request

  • cURL

  • C#

  • Go

  • HTTP

  • Java

  • jQuery

  • NodeJS

  • Python

  • PHP

  • Ruby

  • Swift

curl --location --globoff '{{apiPath}}/environments/{{envID}}/applications' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {{accessToken}}' \
--data '{
    "enabled": true,
    "name": "Device-App{{$timestamp}}",
    "description": "App for Device Authorization Grant",
    "type": "CUSTOM_APP",
    "protocol": "OPENID_CONNECT",
    "grantTypes": [
        "DEVICE_CODE",
        "REFRESH_TOKEN"
    ],
    "deviceTimeout": 600,
    "devicePollingInterval": 5,
    "tokenEndpointAuthMethod": "NONE",
    "devicePathId": "go"
}'
var options = new RestClientOptions("{{apiPath}}/environments/{{envID}}/applications")
{
  MaxTimeout = -1,
};
var client = new RestClient(options);
var request = new RestRequest("", Method.Post);
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Authorization", "Bearer {{accessToken}}");
var body = @"{" + "\n" +
@"    ""enabled"": true," + "\n" +
@"    ""name"": ""Device-App{{$timestamp}}""," + "\n" +
@"    ""description"": ""App for Device Authorization Grant""," + "\n" +
@"    ""type"": ""CUSTOM_APP""," + "\n" +
@"    ""protocol"": ""OPENID_CONNECT""," + "\n" +
@"    ""grantTypes"": [" + "\n" +
@"        ""DEVICE_CODE""," + "\n" +
@"        ""REFRESH_TOKEN""" + "\n" +
@"    ]," + "\n" +
@"    ""deviceTimeout"": 600," + "\n" +
@"    ""devicePollingInterval"": 5," + "\n" +
@"    ""tokenEndpointAuthMethod"": ""NONE""," + "\n" +
@"    ""devicePathId"": ""go""" + "\n" +
@"}";
request.AddStringBody(body, DataFormat.Json);
RestResponse response = await client.ExecuteAsync(request);
Console.WriteLine(response.Content);
package main

import (
  "fmt"
  "strings"
  "net/http"
  "io"
)

func main() {

  url := "{{apiPath}}/environments/{{envID}}/applications"
  method := "POST"

  payload := strings.NewReader(`{
    "enabled": true,
    "name": "Device-App{{$timestamp}}",
    "description": "App for Device Authorization Grant",
    "type": "CUSTOM_APP",
    "protocol": "OPENID_CONNECT",
    "grantTypes": [
        "DEVICE_CODE",
        "REFRESH_TOKEN"
    ],
    "deviceTimeout": 600,
    "devicePollingInterval": 5,
    "tokenEndpointAuthMethod": "NONE",
    "devicePathId": "go"
}`)

  client := &http.Client {
  }
  req, err := http.NewRequest(method, url, payload)

  if err != nil {
    fmt.Println(err)
    return
  }
  req.Header.Add("Content-Type", "application/json")
  req.Header.Add("Authorization", "Bearer {{accessToken}}")

  res, err := client.Do(req)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer res.Body.Close()

  body, err := io.ReadAll(res.Body)
  if err != nil {
    fmt.Println(err)
    return
  }
  fmt.Println(string(body))
}
POST /environments/{{envID}}/applications HTTP/1.1
Host: {{apiPath}}
Content-Type: application/json
Authorization: Bearer {{accessToken}}

{
    "enabled": true,
    "name": "Device-App{{$timestamp}}",
    "description": "App for Device Authorization Grant",
    "type": "CUSTOM_APP",
    "protocol": "OPENID_CONNECT",
    "grantTypes": [
        "DEVICE_CODE",
        "REFRESH_TOKEN"
    ],
    "deviceTimeout": 600,
    "devicePollingInterval": 5,
    "tokenEndpointAuthMethod": "NONE",
    "devicePathId": "go"
}
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, "{\n    \"enabled\": true,\n    \"name\": \"Device-App{{$timestamp}}\",\n    \"description\": \"App for Device Authorization Grant\",\n    \"type\": \"CUSTOM_APP\",\n    \"protocol\": \"OPENID_CONNECT\",\n    \"grantTypes\": [\n        \"DEVICE_CODE\",\n        \"REFRESH_TOKEN\"\n    ],\n    \"deviceTimeout\": 600,\n    \"devicePollingInterval\": 5,\n    \"tokenEndpointAuthMethod\": \"NONE\",\n    \"devicePathId\": \"go\"\n}");
Request request = new Request.Builder()
  .url("{{apiPath}}/environments/{{envID}}/applications")
  .method("POST", body)
  .addHeader("Content-Type", "application/json")
  .addHeader("Authorization", "Bearer {{accessToken}}")
  .build();
Response response = client.newCall(request).execute();
var settings = {
  "url": "{{apiPath}}/environments/{{envID}}/applications",
  "method": "POST",
  "timeout": 0,
  "headers": {
    "Content-Type": "application/json",
    "Authorization": "Bearer {{accessToken}}"
  },
  "data": JSON.stringify({
    "enabled": true,
    "name": "Device-App{{$timestamp}}",
    "description": "App for Device Authorization Grant",
    "type": "CUSTOM_APP",
    "protocol": "OPENID_CONNECT",
    "grantTypes": [
      "DEVICE_CODE",
      "REFRESH_TOKEN"
    ],
    "deviceTimeout": 600,
    "devicePollingInterval": 5,
    "tokenEndpointAuthMethod": "NONE",
    "devicePathId": "go"
  }),
};

$.ajax(settings).done(function (response) {
  console.log(response);
});
var request = require('request');
var options = {
  'method': 'POST',
  'url': '{{apiPath}}/environments/{{envID}}/applications',
  'headers': {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer {{accessToken}}'
  },
  body: JSON.stringify({
    "enabled": true,
    "name": "Device-App{{$timestamp}}",
    "description": "App for Device Authorization Grant",
    "type": "CUSTOM_APP",
    "protocol": "OPENID_CONNECT",
    "grantTypes": [
      "DEVICE_CODE",
      "REFRESH_TOKEN"
    ],
    "deviceTimeout": 600,
    "devicePollingInterval": 5,
    "tokenEndpointAuthMethod": "NONE",
    "devicePathId": "go"
  })

};
request(options, function (error, response) {
  if (error) throw new Error(error);
  console.log(response.body);
});
import requests
import json

url = "{{apiPath}}/environments/{{envID}}/applications"

payload = json.dumps({
  "enabled": True,
  "name": "Device-App{{$timestamp}}",
  "description": "App for Device Authorization Grant",
  "type": "CUSTOM_APP",
  "protocol": "OPENID_CONNECT",
  "grantTypes": [
    "DEVICE_CODE",
    "REFRESH_TOKEN"
  ],
  "deviceTimeout": 600,
  "devicePollingInterval": 5,
  "tokenEndpointAuthMethod": "NONE",
  "devicePathId": "go"
})
headers = {
  'Content-Type': 'application/json',
  'Authorization': 'Bearer {{accessToken}}'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)
<?php
require_once 'HTTP/Request2.php';
$request = new HTTP_Request2();
$request->setUrl('{{apiPath}}/environments/{{envID}}/applications');
$request->setMethod(HTTP_Request2::METHOD_POST);
$request->setConfig(array(
  'follow_redirects' => TRUE
));
$request->setHeader(array(
  'Content-Type' => 'application/json',
  'Authorization' => 'Bearer {{accessToken}}'
));
$request->setBody('{\n    "enabled": true,\n    "name": "Device-App{{$timestamp}}",\n    "description": "App for Device Authorization Grant",\n    "type": "CUSTOM_APP",\n    "protocol": "OPENID_CONNECT",\n    "grantTypes": [\n        "DEVICE_CODE",\n        "REFRESH_TOKEN"\n    ],\n    "deviceTimeout": 600,\n    "devicePollingInterval": 5,\n    "tokenEndpointAuthMethod": "NONE",\n    "devicePathId": "go"\n}');
try {
  $response = $request->send();
  if ($response->getStatus() == 200) {
    echo $response->getBody();
  }
  else {
    echo 'Unexpected HTTP status: ' . $response->getStatus() . ' ' .
    $response->getReasonPhrase();
  }
}
catch(HTTP_Request2_Exception $e) {
  echo 'Error: ' . $e->getMessage();
}
require "uri"
require "json"
require "net/http"

url = URI("{{apiPath}}/environments/{{envID}}/applications")

http = Net::HTTP.new(url.host, url.port);
request = Net::HTTP::Post.new(url)
request["Content-Type"] = "application/json"
request["Authorization"] = "Bearer {{accessToken}}"
request.body = JSON.dump({
  "enabled": true,
  "name": "Device-App{{\$timestamp}}",
  "description": "App for Device Authorization Grant",
  "type": "CUSTOM_APP",
  "protocol": "OPENID_CONNECT",
  "grantTypes": [
    "DEVICE_CODE",
    "REFRESH_TOKEN"
  ],
  "deviceTimeout": 600,
  "devicePollingInterval": 5,
  "tokenEndpointAuthMethod": "NONE",
  "devicePathId": "go"
})

response = http.request(request)
puts response.read_body
let parameters = "{\n    \"enabled\": true,\n    \"name\": \"Device-App{{$timestamp}}\",\n    \"description\": \"App for Device Authorization Grant\",\n    \"type\": \"CUSTOM_APP\",\n    \"protocol\": \"OPENID_CONNECT\",\n    \"grantTypes\": [\n        \"DEVICE_CODE\",\n        \"REFRESH_TOKEN\"\n    ],\n    \"deviceTimeout\": 600,\n    \"devicePollingInterval\": 5,\n    \"tokenEndpointAuthMethod\": \"NONE\",\n    \"devicePathId\": \"go\"\n}"
let postData = parameters.data(using: .utf8)

var request = URLRequest(url: URL(string: "{{apiPath}}/environments/{{envID}}/applications")!,timeoutInterval: Double.infinity)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("Bearer {{accessToken}}", forHTTPHeaderField: "Authorization")

request.httpMethod = "POST"
request.httpBody = postData

let task = URLSession.shared.dataTask(with: request) { data, response, error in
  guard let data = data else {
    print(String(describing: error))
    return
  }
  print(String(data: data, encoding: .utf8)!)
}

task.resume()

Example Response

201 Created

{
    "_links": {
        "self": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/applications/1a4142e1-8c8d-4814-99bc-b84313c50cf1"
        },
        "environment": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
        },
        "attributes": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/applications/1a4142e1-8c8d-4814-99bc-b84313c50cf1/attributes"
        },
        "pushCredentials": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/applications/1a4142e1-8c8d-4814-99bc-b84313c50cf1/pushCredentials"
        },
        "secret": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/applications/1a4142e1-8c8d-4814-99bc-b84313c50cf1/secret"
        },
        "grants": {
            "href": "https://api.pingone.com/v1/environments/abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6/applications/1a4142e1-8c8d-4814-99bc-b84313c50cf1/grants"
        }
    },
    "environment": {
        "id": "abfba8f6-49eb-49f5-a5d9-80ad5c98f9f6"
    },
    "id": "1a4142e1-8c8d-4814-99bc-b84313c50cf1",
    "name": "Device-App1685470636",
    "description": "App for Device Authorization Grant",
    "enabled": true,
    "hiddenFromAppPortal": false,
    "type": "CUSTOM_APP",
    "protocol": "OPENID_CONNECT",
    "createdAt": "2023-05-30T18:17:16.483Z",
    "updatedAt": "2023-05-30T18:17:16.483Z",
    "assignActorRoles": false,
    "grantTypes": [
        "REFRESH_TOKEN",
        "DEVICE_CODE"
    ],
    "devicePathId": "go",
    "tokenEndpointAuthMethod": "NONE",
    "pkceEnforcement": "OPTIONAL",
    "parRequirement": "OPTIONAL",
    "devicePollingInterval": 5,
    "parTimeout": 60,
    "signing": {
        "keyRotationPolicy": {
            "id": "38c6ccb0-bfd9-4e6b-ace7-4651c52a3c2c"
        }
    },
    "deviceTimeout": 600
}