Benjamin Schieder

SECURE STATELESS TOKEN AUTHENTICATION

2026 April 29

Recently I once again came into the situation that I needed to authenticate and identify endusers without a local user database. I only rely on the Access Token returned by an OpenID Connect auth server. However, I also couldn’t disclose the token to the enduser, because that would enable them to do more things directly with the downstream API than I want to enable them to.

What I tried first is asynchronously encrypting the token. This would have enabled fun things like forcing a token rotation by simply creating a new keypair. No way to decrypt the encrypted token would force a re-authentication of everyone. This, however, quickly ran into size constraints as you can’t encrypt a 3000 bit token with a 2048 bit keypair.

After trying a few things, I ended up creating a permanent 4096 bit keypair for asynchronous encryption and use that to encrypt a random passphrase for synchronous encryption for every session. I would then use the passphrase to encrypt the access token, encrypt the passphrase with the public key, set the token and encrypted passphrase as cookies, and walk in reverse on every API call. Since this is for a really low-volume tool (expecting less than 1 active session per week!), the overhead is acceptable.

Example PHP code below:

<?php

// Encryption

// Generate random passphrase
$random_passphrase = bin2hex(random_bytes(32)); // 64 character hex string

// Load the Public/Private key
$key = openssl_pkey_get_private("file://private.nopass.pem");
$pub = openssl_pkey_get_public("file://public.pub");

// Encrypt the passphrase with the public key
$random_passphrase_cipher_text = "";
if (!openssl_public_encrypt($random_passphrase, $random_passphrase_cipher_text, $pub, OPENSSL_PKCS1_OAEP_PADDING)) {
    echo "Encryption failed\n";
    exit 1;
}

// Example access token to encrypt
$access_token = base64_encode(json_encode({
  "ver": 1,
  "jti": "AT.uxOyf45ipeiOQ5345euDIhfyitc",
  "iss": "https://oidc.example.com/oauth2/default",
  "aud": "api://example.com",
  "iat": 1777454756,
  "exp": 1777458356,
  "sub": "bob@example.com",
  "name": "Bob Bobbington",
  "admin": true,
  "scp": [
    "openid",
    "profile",
    "email"
  ],
  "auth_time": 1000
}));

$access_token_cipher_text = openssl_encrypt($access_token, 'aes-256-cbc', $random_passphrase, 0, substr($random_passphrase, 0, 16));

// Decryption

$random_passphrase_decrypted = "";
if (!openssl_private_decrypt($random_passphrase_cipher_text, $random_passphrase_decrypted, $key, OPENSSL_PKCS1_OAEP_PADDING)){
    echo "Decryption failed\n";
    exit 1;
}

$access_token_decrypted = openssl_decrypt($access_token_cipher_text, 'aes-256-cbc', $random_passphrase_decrypted, 0, substr($random_passphrase_decrypted, 0, 16));

EOF

Category: blog

Tags: php oidc encryption