Content · Web Development · PHP

PHP OAuth 2.0 Authorization Code with PKCE

Bind an authorization code to the client that initiated it using a high-entropy code verifier and its SHA-256.

1 views Published Oct 17, 2026 Updated Oct 17, 2026 admin

Bind an authorization code to the client that initiated it using a high-entropy code verifier and its SHA-256 challenge.

The examples introduce the underlying control before showing a verification and an application-level safeguard.

What You Will Build


Bind an authorization code to the client that initiated it using a high-entropy code verifier and its SHA-256 challenge.

Prerequisites


Use PHP 8.4 or newer, HTTPS for all authentication operations, and an application session or database where required. Example configuration values are placeholders, never production secrets.

Example 1: Basic Operation


Create one-time state, PKCE verifier and S256 code challenge.

<?php
session_start();
$state = bin2hex(random_bytes(32));
$verifier = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');
$challenge = rtrim(strtr(base64_encode(hash('sha256', $verifier, true)), '+/', '-_'), '=');
$_SESSION['oauth_attempt'] = ['state' => $state, 'verifier' => $verifier];
echo 'PKCE S256 challenge: ' . $challenge;

Example 2: A Different Use Case


Validate the callback state and consume the login attempt once.

<?php
session_start();
$attempt = $_SESSION['oauth_attempt'] ?? [];
$state = $_GET['state'] ?? null;
if (!is_string($state) || !isset($attempt['state']) || !hash_equals($attempt['state'], $state)) {
    http_response_code(400); exit('Invalid OAuth state');
}
$code = $_GET['code'] ?? null;
if (!is_string($code) || $code === '') { http_response_code(400); exit; }
$_SESSION['validated_pkce_verifier'] = $attempt['verifier'];
unset($_SESSION['oauth_attempt']);
// Exchange the code at a fixed token endpoint; expire verifier after this attempt.

Practical Example: Checking the Result


Build an authorization request using fixed provider settings, not user-controlled URLs.

<?php
// Run only after the server has validated and consumed the callback state.
// These values must be kept from the same original authorization attempt.
$authorizationCode = $_GET['code'] ?? null;
$verifier = $_SESSION['validated_pkce_verifier'] ?? null;
if (!is_string($authorizationCode) || !is_string($verifier)) { http_response_code(400); exit; }
$tokenEndpoint = 'https://auth.example.test/token'; // Fixed trusted provider setting.
$payload = http_build_query(['grant_type' => 'authorization_code', 'code' => $authorizationCode,
    'client_id' => getenv('OAUTH_CLIENT_ID'), 'redirect_uri' => 'https://app.example.test/callback',
    'code_verifier' => $verifier], '', '&', PHP_QUERY_RFC3986);
$ch = curl_init($tokenEndpoint);
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload,
    CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10,
    CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded']]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
unset($_SESSION['validated_pkce_verifier']);
if (!is_string($response) || $status !== 200) { throw new RuntimeException('Token exchange failed'); }
$tokens = json_decode($response, true, 512, JSON_THROW_ON_ERROR);
// Validate scope, issuer and token type; store tokens server-side only.
// For login via OpenID Connect, also validate the ID token and nonce.

Important Behavior and Edge Cases


Persist verifier and CSRF state server-side only for that login attempt, then consume them exactly once.

Enforce controls on the server, test denial paths and do not assume that a snippet alone implements an entire production workflow.

Comparison with Related Functions


ApproachKey distinction
PHP OAuth 2.0 Authorization Code with PKCEBind an authorization code to the client that initiated it using a high-entropy code verifier and its SHA-256 challenge.
Related techniqueThe S256 method is stronger than plain; PKCE supplements rather than replaces state validation.

Common Beginner Mistakes


MistakeCorrect approach
Confusing this operation with a neighboring cryptographic or security mechanismThe S256 method is stronger than plain; PKCE supplements rather than replaces state validation.
Ignoring a security prerequisite or failure casePersist verifier and CSRF state server-side only for that login attempt, then consume them exactly once.

Key Takeaways


  • Bind an authorization code to the client that initiated it using a high-entropy code verifier and its SHA-256 challenge.
  • Persist verifier and CSRF state server-side only for that login attempt, then consume them exactly once.
  • The S256 method is stronger than plain; PKCE supplements rather than replaces state validation.

Apply this behavior to a real application only after confirming the required extension, configuration and failure handling on the deployment runtime.

Share this resourceSend this page to someone who may find it useful.