Coding standards
Security in ResourceSpace
Developer reference
Database
User api session functions

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

ColumnTypeDefaultDescription
$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.