redeem_api_session_code()
Description
Redeem an API session code using its original destination and PKCE verifier.Invalid attempts return false without consuming a valid code. An atomic DELETE elects exactly
one successful redeemer even when transactions are disabled. Reads deliberately bypass caches/replicas.
Parameters
| Column | Type | Default | Description |
|---|---|---|---|
| $system | string | Destination configuration key. | |
| string | #[\SensitiveParameter] | ||
| $redirect_uri : | string | get_api_session_destination$system | |
| $code | |||
| 128}\z/' | |||
| $code_verifier | string | Original RFC 7636 verifier (43-128 unreserved ASCII characters). | |
| $code | string | Opaque code received through the browser redirect. | |
| $redirect_uri | string | Exact callback URL used when issuing the code. |
Return
| array|false | Session signing key and authorized identity fields, or false if validation fails. |
Location
include/user_api_session_functions.php lines 148 to 213
Definition
function redeem_api_session_code(
string $system,
#[\SensitiveParameter] string $code,
#[\SensitiveParameter] string $code_verifier,
string $redirect_uri
): array|false {
global $enable_remote_apis;
$destination = get_api_session_destination($system, $redirect_uri);
if (
!$enable_remote_apis
|| $destination === false
|| $destination['delivery'] !== 'redirect_code'
|| $destination['url'] !== $redirect_uri
|| !preg_match('/\A[a-f0-9]{64}\z/', $code)
|| !preg_match('/\A[A-Za-z0-9._~-]{43,128}\z/', $code_verifier)
) {
return false;
}
$code_hash = hash('sha256', $code);
$rows = ps_query(
'SELECT user, `system`, redirect_uri, code_challenge, key_hash, querydata FROM user_api_session_code
WHERE code_hash = ? AND `system` = ? AND redirect_uri = ? AND expires > UNIX_TIMESTAMP()',
['s', $code_hash, 's', $system, 's', $redirect_uri],
'',
-1,
true,
0
);
if (count($rows) !== 1) {
return false;
}
$record = $rows[0];
// Database collations can compare URLs case-insensitively; redirect binding must be exact.
if ($record['system'] !== $system || $record['redirect_uri'] !== $redirect_uri) {
return false;
}
$challenge = rtrim(strtr(base64_encode(hash('sha256', $code_verifier, true)), '+/', '-_'), '=');
if (!hash_equals($record['code_challenge'], $challenge)) {
return false;
}
$sessionkey = get_session_api_key($record['user']);
$owner = get_api_session_code_user($record['user']);
if ($owner === false) {
return false;
}
if (!hash_equals($record['key_hash'], hash('sha256', $sessionkey))) {
return false;
}
$querydata = json_decode($record['querydata'], true);
$response = ['sessionkey' => $sessionkey];
foreach (array_intersect($querydata, $destination['querydata'], ['username', 'email', 'fullname']) as $key) {
$response[$key] = $owner[$key];
}
ps_query(
'DELETE FROM user_api_session_code WHERE code_hash = ? AND expires > UNIX_TIMESTAMP()',
['s', $code_hash]
);
return sql_affected_rows() === 1 ? $response : false;
}
This article was last updated 29th October 2026 18:35 Europe/London time based on the source file dated 29th October 2026 14:30 Europe/London time.