Step 4: Exchange code for token (NO AUTH + PKCE)
POST {{authPath}}/{{envID}}/as/token
You can use the /{{envID}}/as/token endpoint to obtain an access token by presenting its authorization grant. In this activity, the token request identifies authorization_code as the grant type, and it must also provide the code value returned in the resume endpoint.
With the pkceEnforcment property enabled on the application, the token request must provide the code_verifier parameter value. This parameter is used to verify the code_challenge value submitted in the authorization request.
The token request transforms the code_verifier property value using the code_challenge_method specified in the authorize request. If the transformed code_verifier value is equal to the code_challenge value submitted in the authorize request, then the authorization server issues the token.
Usage summary
This workflow demonstrates authentication for public clients (SPAs, mobile apps) that cannot securely store secrets. Here are the key factors:
Token request
-
No Client Secret: Public clients don’t have secrets
-
Request body:
-
grant_typeisauthorization_code -
code(authorization code) -
redirect_uri -
client_idis{{publicClientAppID}}(onlyclient_id, no secret) -
code_verifieris the{{pkceCodeVerifier}}
-
PKCE flow
-
Authorization: Send
code_challenge(SHA256 hash of verifier) -
Token: Send
code_verifier(original random string) -
Server verifies:
SHA256(code_verifier)equals thecode_challengevalue -
Prevents code interception attacks
Security model
-
No shared secrets to steal.
-
PKCE protects against authorization code interception.
-
Code verifier proves possession of original challenge.
-
Safe for JavaScript apps, mobile apps.
When to use
-
Single Page Applications (React, Angular, Vue)
-
Mobile apps (iOS, Android)
-
Desktop apps without secure storage
-
Any client that cannot keep secrets confidential
|
Important: Public clients are limited to certain grant types and should use short-lived tokens with refresh token rotation. |
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={{publicAuthCode}}' \
--data-urlencode 'redirect_uri=http://localhost:3000/callback' \
--data-urlencode 'client_id={{nativeAppID}}' \
--data-urlencode 'code_verifier={{pkceCodeVerifier}}'
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", "{{publicAuthCode}}");
request.AddParameter("redirect_uri", "http://localhost:3000/callback");
request.AddParameter("client_id", "{{nativeAppID}}");
request.AddParameter("code_verifier", "{{pkceCodeVerifier}}");
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%7BpublicAuthCode%7D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback&client_id=%7B%7BnativeAppID%7D%7D&code_verifier=%7B%7BpkceCodeVerifier%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")
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%7BpublicAuthCode%7D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback&client_id=%7B%7BnativeAppID%7D%7D&code_verifier=%7B%7BpkceCodeVerifier%7D%7D
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={{publicAuthCode}}&redirect_uri=http://localhost:3000/callback&client_id={{nativeAppID}}&code_verifier={{pkceCodeVerifier}}");
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": "{{publicAuthCode}}",
"redirect_uri": "http://localhost:3000/callback",
"client_id": "{{nativeAppID}}",
"code_verifier": "{{pkceCodeVerifier}}"
}
};
$.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': '{{publicAuthCode}}',
'redirect_uri': 'http://localhost:3000/callback',
'client_id': '{{nativeAppID}}',
'code_verifier': '{{pkceCodeVerifier}}'
}
};
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%7BpublicAuthCode%7D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback&client_id=%7B%7BnativeAppID%7D%7D&code_verifier=%7B%7BpkceCodeVerifier%7D%7D'
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' => '{{publicAuthCode}}',
'redirect_uri' => 'http://localhost:3000/callback',
'client_id' => '{{nativeAppID}}',
'code_verifier' => '{{pkceCodeVerifier}}'
));
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%7BpublicAuthCode%7D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback&client_id=%7B%7BnativeAppID%7D%7D&code_verifier=%7B%7BpkceCodeVerifier%7D%7D"
response = http.request(request)
puts response.read_body
let parameters = "grant_type=authorization_code&code=%7B%7BpublicAuthCode%7D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback&client_id=%7B%7BnativeAppID%7D%7D&code_verifier=%7B%7BpkceCodeVerifier%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.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()