PingOne Platform APIs

Step 4: Exchange code for token (JWT AUTH)

 

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

The token endpoint is used by the client to obtain an access token by presenting the client’s authorization grant. For authorization_code grants, the application calls the POST /{{envID}}/as/token endpoint to acquire the access token. To use a JWT to authenticate the request, you must include the client_assertion and client_assertion_type properties.

The request body includes these properties:

  • grant_type

    A string that specifies the grant type of the token request. In this example, the value is authorization_code.

  • code

    A string that specifies the authorization code value returned by the authorization request.

  • redirect_uri

    A string that specifies the URL that specifies the return entry point of the application.

  • scope

    A string that specifies the permissions specified by the access token.

  • client_assertion

    A string that specifies the JWT signed by the client secret that authenticates the token request.

  • client_assertion_type

    A string that specifies the client assertion type. The value of this property must be set to urn:ietf:params:oauth:client-assertion-type:jwt-bearer.

The client_assertion property for this request requires a JWT signed by the client secret to authenticate. If you use the Postman collection to run this workflow, there is a Postman prerequest script that creates the JWT for you and signs it using your application’s secret.

The response data contains the access token and the ID token.

Usage summary

This workflow demonstrates JWT-based authentication using a shared secret. Here are the key factors:

Token request

  • Authentication: No Basic Auth, no plaintext credentials

  • Request body: Uses JWT assertion:

    • grant_type is authorization_code

    • code (authorization code)

    • redirect_uri

    • client_assertion is the {{jwtClientAssertion}} which is thw JWT signed with the application’s client secret

    • client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer

JWT Structure

  • JSON

    Header: {"typ": "JWT", "alg": "HS256"}
    Payload: {
    "iss": {{<client_id}}",
    "sub": "{{client_id}}",
    "aud": "{{token_endpoint_url}}",
    "exp": {{expiration_timestamp}}
    }
    Signature: HMAC-SHA256(header.payload, client_secret)

Benefits

  • Non-repudiation (signed, tamperproof)

  • Expiration built-in

  • Better audit trails

  • No credentials in headers/body

When to use

  • Applications requiring enhanced security, compliance, or audit requirements (finance, healthcare).

Headers

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

Body

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

Key Value

grant_type

authorization_code

code

{{jwtAuthCode}}

redirect_uri

http://localhost:3000/callback

client_assertion

{{jwtClientAssertion}}

client_assertion_type

urn:ietf:params:oauth:client-assertion-type:jwt-bearer

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' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'code={{jwtAuthCode}}' \
--data-urlencode 'redirect_uri=http://localhost:3000/callback' \
--data-urlencode 'client_assertion={{jwtClientAssertion}}' \
--data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
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.AddParameter("grant_type", "authorization_code");
request.AddParameter("code", "{{jwtAuthCode}}");
request.AddParameter("redirect_uri", "http://localhost:3000/callback");
request.AddParameter("client_assertion", "{{jwtClientAssertion}}");
request.AddParameter("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
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=authorization_code&code=%7B%7BjwtAuthCode%7D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback&client_assertion=%7B%7BjwtClientAssertion%7D%7D&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer")

  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")

  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

grant_type=authorization_code&code=%7B%7BjwtAuthCode%7D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback&client_assertion=%7B%7BjwtClientAssertion%7D%7D&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();
MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
RequestBody body = RequestBody.create(mediaType, "grant_type=authorization_code&code={{jwtAuthCode}}&redirect_uri=http://localhost:3000/callback&client_assertion={{jwtClientAssertion}}&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
Request request = new Request.Builder()
  .url("{{authPath}}/{{envID}}/as/token")
  .method("POST", body)
  .addHeader("Content-Type", "application/x-www-form-urlencoded")
  .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"
  },
  "data": {
    "grant_type": "authorization_code",
    "code": "{{jwtAuthCode}}",
    "redirect_uri": "http://localhost:3000/callback",
    "client_assertion": "{{jwtClientAssertion}}",
    "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
  }
};

$.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'
  },
  form: {
    'grant_type': 'authorization_code',
    'code': '{{jwtAuthCode}}',
    'redirect_uri': 'http://localhost:3000/callback',
    'client_assertion': '{{jwtClientAssertion}}',
    'client_assertion_type': 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
  }
};
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=authorization_code&code=%7B%7BjwtAuthCode%7D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback&client_assertion=%7B%7BjwtClientAssertion%7D%7D&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer'
headers = {
  'Content-Type': 'application/x-www-form-urlencoded'
}

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'
));
$request->addPostParameter(array(
  'grant_type' => 'authorization_code',
  'code' => '{{jwtAuthCode}}',
  'redirect_uri' => 'http://localhost:3000/callback',
  'client_assertion' => '{{jwtClientAssertion}}',
  'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
));
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.body = "grant_type=authorization_code&code=%7B%7BjwtAuthCode%7D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback&client_assertion=%7B%7BjwtClientAssertion%7D%7D&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer"

response = http.request(request)
puts response.read_body
let parameters = "grant_type=authorization_code&code=%7B%7BjwtAuthCode%7D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback&client_assertion=%7B%7BjwtClientAssertion%7D%7D&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer"
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.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": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImRlZmF1bHQifQ",
    "token_type": "Bearer",
    "expires_in": 3600,
    "scope": "openid",
    "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImRlZmF1bHQifQ"
}