PHP API Authentication
Introduction
When building APIs with PHP, one of the most critical aspects to consider is authentication. Authentication is the process of verifying the identity of clients requesting access to your API resources. Without proper authentication, your API would be vulnerable to unauthorized access, potentially exposing sensitive data or allowing malicious users to manipulate your system.
In this guide, we'll explore various authentication mechanisms for PHP APIs, from simple methods like API keys to more advanced techniques like JSON Web Tokens (JWT) and OAuth 2.0. By the end, you'll understand how to implement secure authentication in your PHP API projects.
Why Authentication Matters
Before diving into implementation details, let's understand why API authentication is crucial:
- Security: Prevents unauthorized access to your API endpoints
- Resource Protection: Controls which clients can access specific resources
- User-specific Data: Ensures users can only access their own data
- Rate Limiting: Helps monitor and control API usage per user
- Accountability: Tracks who is using your API and how
Basic Authentication
Let's start with the simplest form of API authentication: HTTP Basic Authentication.
How Basic Authentication Works
- The client sends a request with an
Authorization
header containing the wordBasic
followed by a base64-encoded string ofusername:password
- The server decodes this string and validates the credentials
- If valid, the server processes the request; otherwise, it returns a 401 Unauthorized response
Implementing Basic Authentication in PHP
<?php
// Basic Authentication implementation
function authenticate() {
// Check if Authorization header exists
$headers = apache_request_headers();
if (!isset($headers['Authorization'])) {
header('WWW-Authenticate: Basic realm="API Authentication"');
header('HTTP/1.0 401 Unauthorized');
echo json_encode(['error' => 'Authentication required']);
exit;
}
// Get credentials from header
$auth = $headers['Authorization'];
if (strpos($auth, 'Basic') !== 0) {
header('HTTP/1.0 401 Unauthorized');
echo json_encode(['error' => 'Invalid authentication method']);
exit;
}
// Decode credentials
$credentials = base64_decode(substr($auth, 6));
list($username, $password) = explode(':', $credentials);
// Validate credentials (in a real app, check against database)
if ($username !== 'api_user' || $password !== 'secure_password') {
header('HTTP/1.0 401 Unauthorized');
echo json_encode(['error' => 'Invalid credentials']);
exit;
}
// Authentication successful
return true;
}
// Use in your API endpoint
if (authenticate()) {
// Process the API request
$data = ['message' => 'Authentication successful', 'data' => [...]];
header('Content-Type: application/json');
echo json_encode($data);
}
Example Request
curl -X GET https://your-api.com/endpoint \
-H "Authorization: Basic YXBpX3VzZXI6c2VjdXJlX3Bhc3N3b3Jk"
Pros and Cons
✅ Pros:
- Simple to implement
- Supported by most HTTP clients
- Built into PHP/Apache
❌ Cons:
- Credentials sent with every request
- Base64 encoding is not encryption (requires HTTPS)
- No built-in expiration mechanism
API Key Authentication
API keys are simple string tokens that clients include with their requests.
How API Key Authentication Works
- The server generates a unique API key for each client
- Clients include this key in requests (typically as a header or query parameter)
- The server validates the key before processing the request
Implementing API Key Authentication
<?php
// API Key Authentication implementation
function validateApiKey() {
// Check for API key in header
$headers = apache_request_headers();
if (!isset($headers['X-API-Key'])) {
// Alternative: check query parameter
if (!isset($_GET['api_key'])) {
header('HTTP/1.0 401 Unauthorized');
echo json_encode(['error' => 'API key required']);
exit;
}
$apiKey = $_GET['api_key'];
} else {
$apiKey = $headers['X-API-Key'];
}
// In a real application, validate against database
$validApiKeys = [
'a1b2c3d4e5f6g7h8i9j0' => ['user_id' => 1, 'role' => 'admin'],
'j9i8h7g6f5e4d3c2b1a0' => ['user_id' => 2, 'role' => 'user']
];
if (!array_key_exists($apiKey, $validApiKeys)) {
header('HTTP/1.0 401 Unauthorized');
echo json_encode(['error' => 'Invalid API key']);
exit;
}
// Authentication successful
return $validApiKeys[$apiKey];
}
// API endpoint
$user = validateApiKey();
$data = ['message' => 'API key valid', 'user_id' => $user['user_id']];
header('Content-Type: application/json');
echo json_encode($data);
Example Request
# Using header
curl -X GET https://your-api.com/endpoint \
-H "X-API-Key: a1b2c3d4e5f6g7h8i9j0"
# Using query parameter
curl -X GET https://your-api.com/endpoint?api_key=a1b2c3d4e5f6g7h8i9j0
Generating Secure API Keys
<?php
// Generate a random API key
function generateApiKey($length = 32) {
return bin2hex(random_bytes($length / 2));
}
$newApiKey = generateApiKey();
echo "Generated API Key: " . $newApiKey;
// In a real application, you would store this in a database
// along with the user ID and other relevant information
API Key Management System
For a complete API key solution, you'll need to build a management system that allows:
- Creating new API keys
- Assigning keys to users
- Revoking compromised keys
- Setting permissions/scopes for each key
JWT (JSON Web Token) Authentication
JWT is a popular token-based authentication method that allows you to securely transmit information between parties as a JSON object.
How JWT Authentication Works
Implementing JWT Authentication in PHP
First, install the necessary package using Composer:
composer require firebase/php-jwt
Now, let's implement a JWT authentication system:
<?php
// JWT Authentication implementation
require 'vendor/autoload.php';
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class JwtAuth {
private $secretKey;
private $algorithm;
private $issuer;
public function __construct() {
// In a real application, store this securely and load from environment variables
$this->secretKey = 'your-secret-key-should-be-long-and-secure';
$this->algorithm = 'HS256';
$this->issuer = 'your-api.com';
}
// Generate a JWT when user logs in
public function generateToken($userId, $username, $role) {
$issuedAt = time();
$expirationTime = $issuedAt + 3600; // Token valid for 1 hour
$payload = [
'iss' => $this->issuer, // Issuer
'iat' => $issuedAt, // Issued at time
'exp' => $expirationTime, // Expiration time
'sub' => $userId, // Subject (user ID)
'username' => $username, // User-specific data
'role' => $role // User role
];
$jwt = JWT::encode($payload, $this->secretKey, $this->algorithm);
return $jwt;
}
// Validate the JWT from incoming request
public function validateToken() {
// Get Authorization header
$headers = apache_request_headers();
if (!isset($headers['Authorization'])) {
return $this->respondUnauthorized('Authorization header not found');
}
$auth = $headers['Authorization'];
// Check if it's a Bearer token
if (strpos($auth, 'Bearer') !== 0) {
return $this->respondUnauthorized('Invalid authorization format');
}
// Extract the token
$jwt = trim(substr($auth, 7));
try {
// Decode and validate the token
$decoded = JWT::decode($jwt, new Key($this->secretKey, $this->algorithm));
// Check if token is expired
if ($decoded->exp < time()) {
return $this->respondUnauthorized('Token expired');
}
// Token is valid, return the decoded data
return [
'success' => true,
'data' => $decoded
];
} catch (\Exception $e) {
return $this->respondUnauthorized('Invalid token: ' . $e->getMessage());
}
}
private function respondUnauthorized($message) {
header('HTTP/1.0 401 Unauthorized');
echo json_encode(['error' => $message]);
exit;
}
}
// Usage examples
// 1. Login endpoint to generate JWT
function loginEndpoint() {
// Validate username and password (simplified for example)
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
// In a real application, validate against database
if ($username === '[email protected]' && $password === 'correct-password') {
$jwtAuth = new JwtAuth();
$token = $jwtAuth->generateToken(123, $username, 'user');
header('Content-Type: application/json');
echo json_encode([
'success' => true,
'token' => $token,
'expires_in' => 3600 // 1 hour
]);
} else {
header('HTTP/1.0 401 Unauthorized');
echo json_encode(['error' => 'Invalid credentials']);
}
}
// 2. Protected API endpoint
function protectedEndpoint() {
$jwtAuth = new JwtAuth();
$result = $jwtAuth->validateToken();
if ($result['success']) {
$userData = $result['data'];
// Process the API request
header('Content-Type: application/json');
echo json_encode([
'success' => true,
'message' => 'Protected data accessed',
'user_id' => $userData->sub,
'username' => $userData->username,
'data' => ['sample' => 'This is protected data']
]);
}
// If validation fails, respondUnauthorized will handle the response
}
Example JWT Requests
# 1. Login to get JWT
curl -X POST https://your-api.com/login \
-H "Content-Type: application/json" \
-d '{"username": "[email protected]", "password": "correct-password"}'
# Response:
# {
# "success": true,
# "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
# "expires_in": 3600
# }
# 2. Access protected endpoint with JWT
curl -X GET https://your-api.com/protected \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
JWT Best Practices
- Secret Key Management: Store your JWT secret key securely, preferably using environment variables
- Short Expiration Times: Set reasonably short expiration times for tokens (1-24 hours)
- Include Only Necessary Data: Don't include sensitive data in JWTs (they can be decoded easily)
- Use HTTPS: Always transmit JWTs over secure connections
- Implement Refresh Tokens: For improved security, use short-lived access tokens with longer-lived refresh tokens
OAuth 2.0 Authentication
OAuth 2.0 is a standard protocol for authorization that allows third-party applications to access resources on behalf of users without sharing credentials.
OAuth 2.0 Flow Types
- Authorization Code Grant: For server-side applications
- Implicit Grant: For browser-based or mobile apps
- Client Credentials Grant: For service-to-service communication
- Resource Owner Password Credentials Grant: For trusted applications
Implementing OAuth 2.0 with PHP
For OAuth 2.0 implementation, it's recommended to use a library like league/oauth2-server.
composer require league/oauth2-server
Here's a simplified example of implementing the Client Credentials Grant flow:
<?php
// OAuth 2.0 Client Credentials implementation (simplified)
require 'vendor/autoload.php';
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Grant\ClientCredentialsGrant;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
// Note: This is a highly simplified example.
// In a real implementation, you would need to create
// repository classes implementing the interfaces
// 1. Setup repositories
$clientRepository = new MyClientRepository();
$scopeRepository = new MyScopeRepository();
$accessTokenRepository = new MyAccessTokenRepository();
// 2. Setup the authorization server
$privateKeyPath = 'path/to/private.key';
$encryptionKey = 'encryption-key-should-be-at-least-32-characters';
$server = new AuthorizationServer(
$clientRepository,
$accessTokenRepository,
$scopeRepository,
$privateKeyPath,
$encryptionKey
);
// 3. Enable the client credentials grant
$server->enableGrantType(
new ClientCredentialsGrant(),
new \DateInterval('PT1H') // Token TTL: 1 hour
);
// 4. Process a request for an access token
$request = Zend\Diactoros\ServerRequestFactory::fromGlobals();
try {
$response = $server->respondToAccessTokenRequest($request, new Zend\Diactoros\Response());
// Return the response to the client
(new Zend\Diactoros\Response\SapiEmitter())->emit($response);
} catch (\Exception $exception) {
// Handle error response
}
OAuth 2.0 With PHP-OAuth2-Server Example
For a more comprehensive implementation, you'd need to create repository classes that implement the required interfaces:
<?php
// Example Client Repository implementation
class MyClientRepository implements ClientRepositoryInterface
{
public function getClientEntity($clientIdentifier)
{
// Fetch client from database and return as ClientEntityInterface
}
public function validateClient($clientIdentifier, $clientSecret, $grantType)
{
// Validate the client credentials against the database
}
}
// Similar implementations needed for:
// - AccessTokenRepository
// - ScopeRepository
// - RefreshTokenRepository (for refresh token grant)
// - UserRepository (for password grant)
Multi-Factor Authentication (MFA)
For APIs requiring higher security, you can implement multi-factor authentication.
Implementing MFA with TOTP (Time-based One-Time Password)
First, install the required package:
composer require spomky-labs/otphp
Now, implement TOTP authentication:
<?php
// MFA implementation with TOTP
require 'vendor/autoload.php';
use OTPHP\TOTP;
class MfaAuthentication {
// Generate a new TOTP secret for a user
public function generateTotpSecret($userId, $username) {
$totp = TOTP::create();
$secret = $totp->getSecret();
// Store the secret in your database, associated with the user
// saveSecretToDatabase($userId, $secret);
// Generate the provisioning URI for QR code
$totp->setLabel($username);
$totp->setIssuer('Your API Name');
$provisioningUri = $totp->getProvisioningUri();
return [
'secret' => $secret,
'provisioning_uri' => $provisioningUri
];
}
// Verify a TOTP code
public function verifyTotpCode($userId, $code) {
// Retrieve the user's secret from the database
// $secret = getSecretFromDatabase($userId);
$secret = 'JBSWY3DPEHPK3PXP'; // Example secret
$totp = TOTP::create($secret);
return $totp->verify($code);
}
// Full authentication with username/password + TOTP
public function authenticate($username, $password, $totpCode) {
// 1. Validate username and password
// $userId = validateCredentials($username, $password);
$userId = 123; // Simplified example
if (!$userId) {
return ['success' => false, 'message' => 'Invalid credentials'];
}
// 2. Verify TOTP code
if (!$this->verifyTotpCode($userId, $totpCode)) {
return ['success' => false, 'message' => 'Invalid TOTP code'];
}
// 3. Authentication successful, generate JWT or session
$jwtAuth = new JwtAuth();
$token = $jwtAuth->generateToken($userId, $username, 'user');
return [
'success' => true,
'token' => $token,
'expires_in' => 3600
];
}
}
// Example usage
$mfa = new MfaAuthentication();
// 1. During account setup, generate and display the secret
$totpSetup = $mfa->generateTotpSecret(123, '[email protected]');
echo "Secret: " . $totpSetup['secret'] . "
";
echo "Provisioning URI for QR code: " . $totpSetup['provisioning_uri'] . "
";
// 2. During login, verify credentials and TOTP code
$result = $mfa->authenticate('[email protected]', 'password123', '123456');
if ($result['success']) {
echo "Login successful! Token: " . $result['token'];
} else {
echo "Login failed: " . $result['message'];
}
Security Best Practices
To ensure your API authentication is as secure as possible, follow these best practices:
- Always Use HTTPS: Encrypt all API traffic to prevent man-in-the-middle attacks
- Rate Limiting: Implement rate limiting to prevent brute force attacks
- Token Expiration: Use short-lived tokens with refresh mechanisms
- Secure Storage: Store authentication secrets and keys securely
- Input Validation: Validate all input to prevent injection attacks
- Logging and Monitoring: Track authentication attempts and failures
- CORS Configuration: Configure proper Cross-Origin Resource Sharing policies
Example Rate Limiting Implementation
<?php
// Simple rate limiting implementation
function checkRateLimit($apiKey, $maxRequests = 100, $timeWindow = 3600) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$key = "rate_limit:$apiKey";
$current = $redis->get($key);
if ($current === false) {
// First request in the time window
$redis->setex($key, $timeWindow, 1);
return true;
}
if ($current >= $maxRequests) {
// Rate limit exceeded
header('HTTP/1.0 429 Too Many Requests');
header('Retry-After: ' . $redis->ttl($key));
echo json_encode([
'error' => 'Rate limit exceeded',
'max_requests' => $maxRequests,
'time_window' => $timeWindow,
'retry_after' => $redis->ttl($key)
]);
exit;
}
// Increment request count
$redis->incr($key);
return true;
}
// Usage in your API endpoint
$apiKey = getApiKeyFromRequest();
checkRateLimit($apiKey, 100, 3600); // 100 requests per hour
Choosing the Right Authentication Method
Here's a comparison to help you choose the right authentication method for your API:
Method | Complexity | Security | Use Cases |
---|---|---|---|
Basic Auth | Low | Low | Development, internal APIs |
API Keys | Low | Medium | Public APIs with low security requirements |
JWT | Medium | High | Most web and mobile applications |
OAuth 2.0 | High | Very High | Third-party integrations, enterprise applications |
MFA | High | Very High | Highly sensitive data, financial applications |
Consider these factors when choosing:
- Target Audience: Developer experience vs. security requirements
- API Exposure: Public vs. private APIs
- Sensitivity of Data: Regular data vs. personal or financial information
- Client Type: Server, web browser, mobile app, IoT device
Summary
In this guide, we've covered various authentication methods for PHP APIs:
- Basic Authentication: Simple username/password authentication
- API Key Authentication: String tokens for identity verification
- JWT Authentication: Self-contained tokens with claims and expiration
- OAuth 2.0: Standard protocol for third-party authorization
- Multi-Factor Authentication: Additional security layer using TOTP
Remember that authentication is just one part of API security. Always complement it with proper authorization (permissions checking), input validation, and other security measures.
Additional Resources
- PHP Manual: HTTP Authentication
- Firebase PHP-JWT Library
- The League OAuth 2.0 Server
- OWASP API Security Top 10
Exercises
- Implement basic API key authentication with rate limiting
- Create a login system using JWT authentication
- Extend the JWT implementation to include refresh tokens
- Implement the OAuth 2.0 Authorization Code flow
- Add TOTP-based multi-factor authentication to your API
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)