PingOne Platform APIs

Token (token_exchange)

POST {{authPath}}/{{envID}}/as/token

When using the POST /{{envID}}/as/token endpoint with the token_exchange grant type, an application presents a subject token and optionally an actor token and receives an access token for a custom resource. Learn more in Token exchange grant type.

The application must be configured with a grantTypes value of token_exchange, and an tokenEndpointAuthMethod value of CLIENT_SECRET_BASIC, CLIENT_SECRET_POST, PRIVATE_KEY_JWT, or CLIENT_SECRET_JWT. Learn more in Applications OIDC settings data model.

In the request to the POST /{{envID}}/as/token endpoint, you must authenticate based on the application’s tokenEndpointAuthMethod value.

In the sample request shown here, the application’s tokenEndpointAuthMethod value is CLIENT_SECRET_BASIC, which requires the Authorization: Basic HTTP header and a Base64-encoded representation of "username:password" in the request, in which the username is the client_id and the password is the client_secret.

If the application’s tokenEndpointAuthMethod value is CLIENT_SECRET_JWT, the endpoint accepts a JWT signed by the application’s client secret to authenticate the request. For information about creating the JWT and the claims in the JWT, refer to Create a client secret JWT. This request requires the client_assertion and client_assertion_type properties to specify the JWT:

curl --location --request POST '{{authPath}}/{{envID}}/as/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_assertion={{clientSecretJWT}}' \
--data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode 'subject_token={{subjectToken}}' \
--data-urlencode 'subject_token_type=urn:ietf:params:oauth:token-type:access_token' \
--data-urlencode 'requested_token_type=urn:ietf:params:oauth:token-type:access_token' \
--data-urlencode 'scope={{requestedScopes}}'

If the application’s tokenEndpointAuthMethod value is PRIVATE_KEY_JWT, the endpoint accepts a JWT signed by an external private key file to authenticate the request. Learn more about creating the JWT and the claims in the JWT in Create a private key JWT. This request requires the client_assertion and client_assertion_type properties to specify the JWT:

curl --location --request POST '{{authPath}}/{{envID}}/as/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_assertion={{privateKeyJWT}}' \
--data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode 'subject_token={{subjectToken}}' \
--data-urlencode 'subject_token_type=urn:ietf:params:oauth:token-type:access_token' \
--data-urlencode 'requested_token_type=urn:ietf:params:oauth:token-type:access_token' \
--data-urlencode 'scope={{requestedScopes}}'

If the application’s tokenEndpointAuthMethod value is CLIENT_SECRET_POST, the request does not require an Authorization header, and the client_id and client_secret properties are sent in the request body:

curl --location --request POST '{{authPath}}/{{envID}}/as/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id={{appID}}' \
--data-urlencode 'client_secret={{appSecret}}'\
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode 'subject_token={{subjectToken}}' \
--data-urlencode 'subject_token_type=urn:ietf:params:oauth:token-type:access_token' \
--data-urlencode 'requested_token_type=urn:ietf:params:oauth:token-type:access_token' \
--data-urlencode 'scope={{requestedScopes}}'

PingOne validates the subject_token and the actor_token, if provided. Based on the scope parameter value found in the token request and the scopes configured in the application, PingOne returns an access token in the token response.

Related topics

Prerequisites

Refer to OpenID Connect/OAuth 2, Token, and Token exchange grant type for overview information.

Request Model
Property Type Required?

actor_token

String

Optional

actor_token_type

String

Optional

grant_type

String

Required

requested_token_type

String

Optional

scopes

String

Required

subject_token

String

Required

subject_token_type

String

Required

Refer to the OpenID Connect/OAuth2 data model for full property descriptions.

Headers

Authorization

Content-Type      application/x-www-form-urlencoded

Body

urlencoded ( application/x-www-form-urlencoded )

Key Value

grant_type

urn:ietf:params:oauth:grant-type:token-exchange

subject_token

{{subToken}}

subject_token_type

urn:ietf:params:oauth:token-type:access_token

actor_token

{{actToken}}

actor_token_type

urn:ietf:params:oauth:token-type:id_token

requested_token_type

urn:ietf:params:oauth:token-type:access_token

scope

{{scopeID}}

Example Request

  • cURL

  • C#

  • Go

  • HTTP

  • Java

  • jQuery

  • NodeJS

  • Python

  • PHP

  • Ruby

  • Swift

curl --location --globoff '{{authPath}}/{{envID}}/as/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic e3thcHBJRH19Ont7YXBwU2VjcmV0fX0=' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode 'subject_token={{subToken}}' \
--data-urlencode 'subject_token_type=urn:ietf:params:oauth:token-type:access_token' \
--data-urlencode 'actor_token={{actToken}}' \
--data-urlencode 'actor_token_type=urn:ietf:params:oauth:token-type:id_token' \
--data-urlencode 'requested_token_type=urn:ietf:params:oauth:token-type:access_token' \
--data-urlencode 'scope={{scopeID}}'
var options = new RestClientOptions("{{authPath}}/{{envID}}/as/token")
{
  MaxTimeout = -1,
};
var client = new RestClient(options);
var request = new RestRequest("", Method.Post);
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddHeader("Authorization", "Basic e3thcHBJRH19Ont7YXBwU2VjcmV0fX0=");
request.AddParameter("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange");
request.AddParameter("subject_token", "{{subToken}}");
request.AddParameter("subject_token_type", "urn:ietf:params:oauth:token-type:access_token");
request.AddParameter("actor_token", "{{actToken}}");
request.AddParameter("actor_token_type", "urn:ietf:params:oauth:token-type:id_token");
request.AddParameter("requested_token_type", "urn:ietf:params:oauth:token-type:access_token");
request.AddParameter("scope", "{{scopeID}}");
RestResponse response = await client.ExecuteAsync(request);
Console.WriteLine(response.Content);
package main

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

func main() {

  url := "{{authPath}}/{{envID}}/as/token"
  method := "POST"

  payload := strings.NewReader("grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&subject_token=%7B%7BsubToken%7D%7D&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&actor_token=%7B%7BactToken%7D%7D&actor_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aid_token&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&scope=%7B%7BscopeID%7D%7D")

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

  if err != nil {
    fmt.Println(err)
    return
  }
  req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
  req.Header.Add("Authorization", "Basic e3thcHBJRH19Ont7YXBwU2VjcmV0fX0=")

  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 /{{envID}}/as/token HTTP/1.1
Host: {{authPath}}
Content-Type: application/x-www-form-urlencoded
Authorization: Basic e3thcHBJRH19Ont7YXBwU2VjcmV0fX0=

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&subject_token=%7B%7BsubToken%7D%7D&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&actor_token=%7B%7BactToken%7D%7D&actor_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aid_token&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&scope=%7B%7BscopeID%7D%7D
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();
MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
RequestBody body = RequestBody.create(mediaType, "grant_type=urn:ietf:params:oauth:grant-type:token-exchange&subject_token={{subToken}}&subject_token_type=urn:ietf:params:oauth:token-type:access_token&actor_token={{actToken}}&actor_token_type=urn:ietf:params:oauth:token-type:id_token&requested_token_type=urn:ietf:params:oauth:token-type:access_token&scope={{scopeID}}");
Request request = new Request.Builder()
  .url("{{authPath}}/{{envID}}/as/token")
  .method("POST", body)
  .addHeader("Content-Type", "application/x-www-form-urlencoded")
  .addHeader("Authorization", "Basic e3thcHBJRH19Ont7YXBwU2VjcmV0fX0=")
  .build();
Response response = client.newCall(request).execute();
var settings = {
  "url": "{{authPath}}/{{envID}}/as/token",
  "method": "POST",
  "timeout": 0,
  "headers": {
    "Content-Type": "application/x-www-form-urlencoded",
    "Authorization": "Basic e3thcHBJRH19Ont7YXBwU2VjcmV0fX0="
  },
  "data": {
    "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
    "subject_token": "{{subToken}}",
    "subject_token_type": "urn:ietf:params:oauth:token-type:access_token",
    "actor_token": "{{actToken}}",
    "actor_token_type": "urn:ietf:params:oauth:token-type:id_token",
    "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
    "scope": "{{scopeID}}"
  }
};

$.ajax(settings).done(function (response) {
  console.log(response);
});
var request = require('request');
var options = {
  'method': 'POST',
  'url': '{{authPath}}/{{envID}}/as/token',
  'headers': {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Authorization': 'Basic e3thcHBJRH19Ont7YXBwU2VjcmV0fX0='
  },
  form: {
    'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange',
    'subject_token': '{{subToken}}',
    'subject_token_type': 'urn:ietf:params:oauth:token-type:access_token',
    'actor_token': '{{actToken}}',
    'actor_token_type': 'urn:ietf:params:oauth:token-type:id_token',
    'requested_token_type': 'urn:ietf:params:oauth:token-type:access_token',
    'scope': '{{scopeID}}'
  }
};
request(options, function (error, response) {
  if (error) throw new Error(error);
  console.log(response.body);
});
import requests

url = "{{authPath}}/{{envID}}/as/token"

payload = 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&subject_token=%7B%7BsubToken%7D%7D&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&actor_token=%7B%7BactToken%7D%7D&actor_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aid_token&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&scope=%7B%7BscopeID%7D%7D'
headers = {
  'Content-Type': 'application/x-www-form-urlencoded',
  'Authorization': 'Basic e3thcHBJRH19Ont7YXBwU2VjcmV0fX0='
}

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

print(response.text)
<?php
require_once 'HTTP/Request2.php';
$request = new HTTP_Request2();
$request->setUrl('{{authPath}}/{{envID}}/as/token');
$request->setMethod(HTTP_Request2::METHOD_POST);
$request->setConfig(array(
  'follow_redirects' => TRUE
));
$request->setHeader(array(
  'Content-Type' => 'application/x-www-form-urlencoded',
  'Authorization' => 'Basic e3thcHBJRH19Ont7YXBwU2VjcmV0fX0='
));
$request->addPostParameter(array(
  'grant_type' => 'urn:ietf:params:oauth:grant-type:token-exchange',
  'subject_token' => '{{subToken}}',
  'subject_token_type' => 'urn:ietf:params:oauth:token-type:access_token',
  'actor_token' => '{{actToken}}',
  'actor_token_type' => 'urn:ietf:params:oauth:token-type:id_token',
  'requested_token_type' => 'urn:ietf:params:oauth:token-type:access_token',
  'scope' => '{{scopeID}}'
));
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 "net/http"

url = URI("{{authPath}}/{{envID}}/as/token")

http = Net::HTTP.new(url.host, url.port);
request = Net::HTTP::Post.new(url)
request["Content-Type"] = "application/x-www-form-urlencoded"
request["Authorization"] = "Basic e3thcHBJRH19Ont7YXBwU2VjcmV0fX0="
request.body = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&subject_token=%7B%7BsubToken%7D%7D&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&actor_token=%7B%7BactToken%7D%7D&actor_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aid_token&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&scope=%7B%7BscopeID%7D%7D"

response = http.request(request)
puts response.read_body
let parameters = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&subject_token=%7B%7BsubToken%7D%7D&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&actor_token=%7B%7BactToken%7D%7D&actor_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aid_token&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&scope=%7B%7BscopeID%7D%7D"
let postData =  parameters.data(using: .utf8)

var request = URLRequest(url: URL(string: "{{authPath}}/{{envID}}/as/token")!,timeoutInterval: Double.infinity)
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.addValue("Basic e3thcHBJRH19Ont7YXBwU2VjcmV0fX0=", 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

200 OK

{
    "access_token": "eyJhbGciOiJSUz...",
    "token_type": "Bearer",
    "expires_in": 3600,
    "scope": "exampleScope",
    "issued_token_type": "urn:ietf:params:oauth:token-type:access_token"
 }