friendship ended with social-app. php is my new best friend
1--- 2title: Authentication 3description: Learn how to authenticate HTTP requests with the Fetch HTTP package 4--- 5 6# Authentication 7 8This guide explains the various authentication methods available in the Fetch HTTP package and how to use them. 9 10## Bearer Token Authentication 11 12Bearer token authentication is a common method used for API authentication, particularly with OAuth 2.0 and JWT tokens. 13 14### Using Helper Functions 15 16```php 17// Using the fetch() function 18$response = fetch('https://api.example.com/users', [ 19 'token' => 'your-oauth-token' 20]); 21 22// Using the token option with any HTTP method helper 23$response = get('https://api.example.com/users', null, [ 24 'token' => 'your-oauth-token' 25]); 26``` 27 28### Using the ClientHandler 29 30```php 31use Fetch\Http\ClientHandler; 32 33$response = ClientHandler::create() 34 ->withToken('your-oauth-token') 35 ->get('https://api.example.com/users'); 36``` 37 38## Basic Authentication 39 40Basic authentication sends credentials encoded in the Authorization header. 41 42### Using Helper Functions 43 44```php 45// Using the fetch() function 46$response = fetch('https://api.example.com/users', [ 47 'auth' => ['username', 'password'] 48]); 49 50// Using the auth option with any HTTP method helper 51$response = get('https://api.example.com/protected', null, [ 52 'auth' => ['username', 'password'] 53]); 54``` 55 56### Using the ClientHandler 57 58```php 59use Fetch\Http\ClientHandler; 60 61$response = ClientHandler::create() 62 ->withAuth('username', 'password') 63 ->get('https://api.example.com/protected'); 64``` 65 66## API Key Authentication 67 68Many APIs use API keys for authentication, which can be sent in different ways. 69 70### As a Query Parameter 71 72```php 73// Using the fetch() function 74$response = fetch('https://api.example.com/data', [ 75 'query' => ['api_key' => 'your-api-key'] 76]); 77 78// Using get() with query params 79$response = get('https://api.example.com/data', [ 80 'api_key' => 'your-api-key' 81]); 82 83// Using the ClientHandler 84$response = ClientHandler::create() 85 ->withQueryParameter('api_key', 'your-api-key') 86 ->get('https://api.example.com/data'); 87``` 88 89### As a Header 90 91```php 92// Using the fetch() function 93$response = fetch('https://api.example.com/data', [ 94 'headers' => ['X-API-Key' => 'your-api-key'] 95]); 96 97// Using get() with options 98$response = get('https://api.example.com/data', null, [ 99 'headers' => ['X-API-Key' => 'your-api-key'] 100]); 101 102// Using the ClientHandler 103$response = ClientHandler::create() 104 ->withHeader('X-API-Key', 'your-api-key') 105 ->get('https://api.example.com/data'); 106``` 107 108## Custom Authentication Schemes 109 110For APIs using custom authentication schemes, you can set headers directly. 111 112```php 113// Example for a custom scheme 114$response = fetch('https://api.example.com/data', [ 115 'headers' => ['Authorization' => 'CustomScheme your-credential'] 116]); 117 118// Using the ClientHandler 119$response = ClientHandler::create() 120 ->withHeader('Authorization', 'CustomScheme your-credential') 121 ->get('https://api.example.com/data'); 122``` 123 124## OAuth 2.0 Authorization Flow 125 126For more complex OAuth 2.0 flows, you'll need to implement the authorization code flow first, then use the resulting access token: 127 128```php 129// Step 1: Redirect user to authorization URL (not part of the HTTP client) 130// ... 131 132// Step 2: Exchange authorization code for an access token 133$tokenResponse = post('https://oauth.example.com/token', [ 134 'grant_type' => 'authorization_code', 135 'code' => $authorizationCode, 136 'client_id' => 'your-client-id', 137 'client_secret' => 'your-client-secret', 138 'redirect_uri' => 'your-redirect-uri' 139]); 140 141// Step 3: Extract and store the access token 142$accessToken = $tokenResponse->json()['access_token']; 143 144// Step 4: Use the access token for API requests 145$apiResponse = get('https://api.example.com/resource', null, [ 146 'token' => $accessToken 147]); 148``` 149 150## Global Authentication Configuration 151 152Configure authentication globally to apply it to all requests: 153 154```php 155// Configure client with authentication 156fetch_client([ 157 'base_uri' => 'https://api.example.com', 158 'headers' => [ 159 'Authorization' => 'Bearer your-oauth-token' // For Bearer token auth 160 ] 161 // OR for basic auth 162 // 'auth' => ['username', 'password'] 163]); 164 165// Now all requests will include the authentication 166$users = get('/users')->json(); 167$user = get("/users/{$id}")->json(); 168$newUser = post('/users', ['name' => 'John Doe'])->json(); 169``` 170 171## Authentication for Specific Clients 172 173Create dedicated clients for different authentication scenarios: 174 175```php 176// Client for public endpoints 177$publicClient = ClientHandler::createWithBaseUri('https://api.example.com'); 178 179// Client for authenticated endpoints 180$authClient = ClientHandler::createWithBaseUri('https://api.example.com') 181 ->withToken('your-oauth-token'); 182 183// Use the appropriate client for each request 184$publicData = $publicClient->get('/public-data')->json(); 185$privateData = $authClient->get('/private-data')->json(); 186``` 187 188## Token Refresh 189 190For APIs that use short-lived access tokens, you might need to implement token refreshing: 191 192```php 193function getAuthenticatedClient() { 194 static $token = null; 195 static $expiresAt = 0; 196 197 // Check if token is expired 198 if ($token === null || time() > $expiresAt) { 199 // Refresh the token 200 $response = post('https://oauth.example.com/token', [ 201 'grant_type' => 'refresh_token', 202 'refresh_token' => 'your-refresh-token', 203 'client_id' => 'your-client-id', 204 'client_secret' => 'your-client-secret' 205 ]); 206 207 $tokenData = $response->json(); 208 $token = $tokenData['access_token']; 209 $expiresAt = time() + ($tokenData['expires_in'] - 60); // Buffer of 60 seconds 210 } 211 212 // Return a client with the current token 213 return ClientHandler::createWithBaseUri('https://api.example.com') 214 ->withToken($token); 215} 216 217// Use the authenticated client 218$client = getAuthenticatedClient(); 219$response = $client->get('/protected-resource'); 220``` 221 222## Asynchronous Authentication 223 224For scenarios where you need to perform authentication in an asynchronous context: 225 226```php 227use function async; 228use function await; 229 230await(async(function() { 231 // First, get an auth token 232 $tokenResponse = await(async(fn() => 233 post('https://oauth.example.com/token', [ 234 'grant_type' => 'client_credentials', 235 'client_id' => 'your-client-id', 236 'client_secret' => 'your-client-secret' 237 ]) 238 )); 239 240 $token = $tokenResponse->json()['access_token']; 241 242 // Then use the token for subsequent requests 243 $apiResponse = await(async(fn() => 244 fetch('https://api.example.com/protected', [ 245 'token' => $token 246 ]) 247 )); 248 249 return $apiResponse->json(); 250})); 251``` 252 253## Testing with Authentication 254 255For testing, you can use mock responses: 256 257```php 258use Fetch\Http\ClientHandler; 259 260// Create a mock response 261$mockResponse = ClientHandler::createJsonResponse( 262 ['username' => 'testuser', 'role' => 'admin'], 263 200 264); 265 266// Test code that uses authentication 267function testAuthenticatedRequest() { 268 global $mockResponse; 269 270 // In your test framework, you would mock the actual HTTP client 271 // and return the mock response 272 273 // Then in your application code: 274 $response = get('https://api.example.com/me', null, [ 275 'token' => 'test-token' 276 ]); 277 278 // Assert against the response 279 assert($response->successful()); 280 assert($response->json()['username'] === 'testuser'); 281} 282``` 283 284## Working with Authentication Response Status 285 286Fetch PHP provides convenient methods to check authentication-related status codes: 287 288```php 289$response = get('https://api.example.com/protected'); 290 291// Check specific authentication-related status codes 292if ($response->isUnauthorized()) { 293 echo "Authentication required (401)"; 294} elseif ($response->isForbidden()) { 295 echo "Permission denied (403)"; 296} elseif ($response->isStatus(429)) { 297 echo "Rate limited (429)"; 298} 299 300// Using Status enums 301use Fetch\Enum\Status; 302 303if ($response->statusEnum() === Status::UNAUTHORIZED) { 304 echo "Authentication required (401)"; 305} elseif ($response->statusEnum() === Status::FORBIDDEN) { 306 echo "Permission denied (403)"; 307} 308``` 309 310## Common Authentication Errors 311 312### Unauthorized (401) 313 314This typically means your credentials are missing or invalid: 315 316```php 317$response = get('https://api.example.com/protected'); 318 319if ($response->isUnauthorized()) { 320 echo "Authentication required. Please provide valid credentials."; 321 // Redirect to login or prompt for credentials 322} 323``` 324 325### Forbidden (403) 326 327This means you're authenticated, but don't have permission for the resource: 328 329```php 330$response = get('https://api.example.com/admin-only', null, [ 331 'token' => 'user-token' // Token for a non-admin user 332]); 333 334if ($response->isForbidden()) { 335 echo "You don't have permission to access this resource."; 336 // Show appropriate message or redirect 337} 338``` 339 340## Error Handling for Authentication 341 342Use try/catch to handle authentication errors: 343 344```php 345try { 346 $response = fetch('https://api.example.com/protected', [ 347 'token' => 'possibly-expired-token' 348 ]); 349 350 if ($response->isUnauthorized()) { 351 // Handle invalid or expired token 352 throw new \Exception("Authentication failed: Token is invalid or expired"); 353 } 354 355 $data = $response->json(); 356} catch (\Fetch\Exceptions\NetworkException $e) { 357 echo "Network error during authentication: " . $e->getMessage(); 358} catch (\Fetch\Exceptions\RequestException $e) { 359 echo "Request error during authentication: " . $e->getMessage(); 360} catch (\Exception $e) { 361 echo "Authentication error: " . $e->getMessage(); 362} 363``` 364 365## Security Best Practices 366 3671. **Never Hard-Code Credentials**: Use environment variables or a secure configuration system 368 369 ```php 370 // Bad: Hard-coding credentials 371 $client->withToken('my-secret-token'); 372 373 // Good: Using environment variables 374 $client->withToken($_ENV['API_TOKEN']); 375 ``` 376 3772. **Use HTTPS**: Always use HTTPS for authenticated requests 378 379 ```php 380 // Ensure HTTPS is used 381 if (!str_starts_with($apiUrl, 'https://')) { 382 throw new \Exception('Authentication requires HTTPS'); 383 } 384 ``` 385 3863. **Handle Tokens Securely**: Store tokens securely and never expose them to clients 387 3884. **Use Short-Lived Tokens**: Prefer short-lived access tokens with refresh capability 389 3905. **Implement Rate Limiting**: To protect against brute force attacks 391 3926. **Add Retry Logic for Authentication Failures**: Some authentication failures are transient 393 394 ```php 395 $client = fetch_client() 396 ->retry(3, 1000) // 3 retries with 1s initial delay 397 ->retryStatusCodes([401, 429]) // Retry on 401 (Unauthorized) and 429 (Too Many Requests) 398 ->withToken($token) 399 ->get('https://api.example.com/protected'); 400 ``` 401 4027. **Log Authentication Failures**: But be careful not to log sensitive information 403 404 ```php 405 use Monolog\Logger; 406 use Monolog\Handler\StreamHandler; 407 408 $logger = new Logger('auth'); 409 $logger->pushHandler(new StreamHandler('logs/auth.log', Logger::WARNING)); 410 411 $client = fetch_client(); 412 $client->setLogger($logger); 413 414 $response = $client 415 ->withToken($token) 416 ->get('https://api.example.com/protected'); 417 418 if ($response->isUnauthorized()) { 419 // Log will include request details but credentials will be redacted 420 // thanks to the sanitization in the ClientHandler 421 } 422 ``` 423 424## Next Steps 425 426- Learn about [Error Handling](/guide/error-handling) for authentication errors 427- Explore [Retry Handling](/guide/retry-handling) for handling token expiration 428- See [Asynchronous Requests](/guide/async-requests) for asynchronous authentication flows 429- Check out [Logging](/guide/logging) for logging authenticated requests securely