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