Skip to content

PHP Tutorial Working with JSON and APIs

API Authentication Basics

Lesson 314 of 369 3 min read By Deepak Paswan Updated Oct 5, 2026

Most APIs need to know who is calling. There are a few common ways to prove it. This lesson shows how to send credentials safely when you call an API, and how to check them when you build one.

1. API keys

A long random string that identifies the caller. Sent in a header (best) or, if the service requires it, in the query string.

index.php
PHP
<?php
$ch = curl_init("https://api.example.com/data");

curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["X-API-Key: " . getenv("EXAMPLE_API_KEY")],
]);

A key in a URL can end up in logs, browser history and the Referer header. Prefer headers.

2. Bearer tokens

The standard header for tokens (including OAuth access tokens and JWTs):

index.php
PHP
<?php
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("EXAMPLE_API_TOKEN")]

3. Basic authentication

A username and password, sent as Authorization: Basic <base64(user:pass)>. It is not encrypted, only encoded, so it is safe only over HTTPS.

index.php
PHP
<?php
echo "Authorization: Basic " . base64_encode("user:secret");
Output
OUTPUT
Authorization: Basic dXNlcjpzZWNyZXQ=

4. OAuth 2.0

When your app acts for a user on another service (“log in with Google”, “post to a user’s account”), the standard is OAuth 2.0. The user approves access on the other service, which gives your app a token. You never see the user’s password. Use a maintained library (such as league/oauth2-client) rather than writing the flow yourself.

Keep keys secret

  • Store keys in environment variables or a config file outside the web root (Environment Variables).
  • Never put a secret API key in JavaScript, in HTML, or in a mobile app. Anyone can read it. Call the API from your server and expose only your own endpoint.
  • Do not commit keys to Git. If one leaks, rotate it (create a new key and delete the old).
  • Use keys with the least permissions the service allows.
  • Use separate keys for development and production.

Checking credentials on your own API

When you build an API, check the key or token on every request, in constant time.

index.php
PHP
<?php
function validApiKey(string $given, array $validKeyHashes): bool
{
    $hash = hash("sha256", $given);

    foreach ($validKeyHashes as $stored) {
        if (hash_equals($stored, $hash)) {
            return true;
        }
    }

    return false;
}

$stored = [hash("sha256", "key_live_abc123")];   // in practice: hashes saved in the database

var_dump(validApiKey("key_live_abc123", $stored));
var_dump(validApiKey("guess", $stored));
Output
OUTPUT
bool(true)
bool(false)

Store only the hash of each key, so a database leak does not expose working keys. Show the key to the user once, when it is created.

Read the header on the server

index.php
PHP
<?php
function bearerToken(array $server): ?string
{
    $header = $server["HTTP_AUTHORIZATION"] ?? "";

    if (preg_match('/^Bearer\s+(\S+)$/i', $header, $m) === 1) {
        return $m[1];
    }

    return null;
}

var_dump(bearerToken(["HTTP_AUTHORIZATION" => "Bearer abc.def"]));
var_dump(bearerToken([]));
Output
OUTPUT
string(7) "abc.def"
NULL

(On some Apache setups, the Authorization header must be passed to PHP with a rewrite rule.)

Respond correctly

  • Missing or wrong credentials: 401 Unauthorized, with a WWW-Authenticate header.
  • Valid credentials, but not allowed: 403 Forbidden.
  • Too many requests: 429.

Webhooks: verify the signature

When a service calls your URL (a payment confirmation, for example), anyone could send a fake call. Services sign their messages with a secret. You recompute the signature and compare.

index.php
PHP
<?php
function signatureIsValid(string $payload, string $signatureHeader, string $secret): bool
{
    $expected = hash_hmac("sha256", $payload, $secret);

    return hash_equals($expected, $signatureHeader);
}

$secret = "whsec_example_secret";
$payload = '{"event":"payment.paid","amount":500}';
$signature = hash_hmac("sha256", $payload, $secret);   // the service computes this and sends it

var_dump(signatureIsValid($payload, $signature, $secret));
var_dump(signatureIsValid($payload . " ", $signature, $secret));   // the body was changed
Output
OUTPUT
bool(true)
bool(false)

Read the raw body (file_get_contents("php://input")) for the check, not a re-encoded version. Use hash_equals(), check any timestamp the service includes to avoid replays, and handle the same event more than once (webhooks are often delivered twice).

Rate limiting

Limit how many requests each key may make per minute, and answer 429 when exceeded. See Authentication Security.

dangerous
Never share secret keys
A secret key in a public repository, a screenshot or a front-end bundle is a leaked key. Public (publishable) keys exist for browser use. Secret keys belong on the server only.