389 lines
7.4 KiB
Markdown
389 lines
7.4 KiB
Markdown
# API Authentication
|
|
|
|
Core PHP Framework provides multiple authentication methods for API access, including API keys, OAuth tokens, and session-based authentication.
|
|
|
|
## API Keys
|
|
|
|
API keys are the primary authentication method for external API access.
|
|
|
|
### Creating API Keys
|
|
|
|
```php
|
|
use Mod\Api\Models\ApiKey;
|
|
|
|
$apiKey = ApiKey::create([
|
|
'name' => 'Mobile App',
|
|
'workspace_id' => $workspace->id,
|
|
'scopes' => ['posts:read', 'posts:write', 'categories:read'],
|
|
'rate_limit_tier' => 'pro',
|
|
]);
|
|
|
|
// Get plaintext key (only shown once!)
|
|
$plaintext = $apiKey->plaintext_key; // sk_live_...
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"name": "Mobile App",
|
|
"key": "sk_live_abc123...",
|
|
"scopes": ["posts:read", "posts:write"],
|
|
"rate_limit_tier": "pro",
|
|
"created_at": "2026-01-26T12:00:00Z"
|
|
}
|
|
```
|
|
|
|
::: warning
|
|
The plaintext API key is only shown once at creation. Store it securely!
|
|
:::
|
|
|
|
### Using API Keys
|
|
|
|
Include the API key in the `Authorization` header:
|
|
|
|
```bash
|
|
curl -H "Authorization: Bearer sk_live_abc123..." \
|
|
https://api.example.com/v1/posts
|
|
```
|
|
|
|
Or use basic authentication:
|
|
|
|
```bash
|
|
curl -u sk_live_abc123: \
|
|
https://api.example.com/v1/posts
|
|
```
|
|
|
|
### Key Format
|
|
|
|
API keys follow the format: `{prefix}_{environment}_{random}`
|
|
|
|
- **Prefix:** `sk` (secret key)
|
|
- **Environment:** `live` or `test`
|
|
- **Random:** 32 characters
|
|
|
|
**Examples:**
|
|
- `sk_live_EXAMPLE_KEY_REPLACE_ME`
|
|
- `sk_test_EXAMPLE_KEY_REPLACE_ME`
|
|
|
|
### Key Security
|
|
|
|
API keys are hashed with bcrypt before storage:
|
|
|
|
```php
|
|
// Creation
|
|
$hash = bcrypt($plaintext);
|
|
|
|
// Verification
|
|
if (Hash::check($providedKey, $apiKey->key_hash)) {
|
|
// Valid key
|
|
}
|
|
```
|
|
|
|
**Security Features:**
|
|
- Never stored in plaintext
|
|
- Bcrypt hashing (cost factor: 10)
|
|
- Secure comparison with `hash_equals()`
|
|
- Rate limiting per key
|
|
- Automatic expiry support
|
|
|
|
### Key Rotation
|
|
|
|
Rotate keys regularly for security:
|
|
|
|
```php
|
|
$newKey = $apiKey->rotate();
|
|
|
|
// Returns new key object with:
|
|
// - New plaintext key
|
|
// - Same scopes and settings
|
|
// - Old key marked for deletion after grace period
|
|
```
|
|
|
|
**Grace Period:**
|
|
- Default: 24 hours
|
|
- Both old and new keys work during this period
|
|
- Old key auto-deleted after grace period
|
|
|
|
### Key Permissions
|
|
|
|
Control what each key can access:
|
|
|
|
```php
|
|
$apiKey = ApiKey::create([
|
|
'name' => 'Read-Only Key',
|
|
'scopes' => [
|
|
'posts:read',
|
|
'categories:read',
|
|
'analytics:read',
|
|
],
|
|
]);
|
|
```
|
|
|
|
Available scopes documented in [Scopes & Permissions](#scopes--permissions).
|
|
|
|
## Sanctum Tokens
|
|
|
|
Laravel Sanctum provides token-based authentication for SPAs:
|
|
|
|
### Creating Tokens
|
|
|
|
```php
|
|
$user = User::find(1);
|
|
|
|
$token = $user->createToken('mobile-app', [
|
|
'posts:read',
|
|
'posts:write',
|
|
])->plainTextToken;
|
|
```
|
|
|
|
### Using Tokens
|
|
|
|
```bash
|
|
curl -H "Authorization: Bearer 1|abc123..." \
|
|
https://api.example.com/v1/posts
|
|
```
|
|
|
|
### Token Abilities
|
|
|
|
Check token abilities in controllers:
|
|
|
|
```php
|
|
if ($request->user()->tokenCan('posts:write')) {
|
|
// User has permission
|
|
}
|
|
```
|
|
|
|
## Session Authentication
|
|
|
|
For first-party applications, use session-based authentication:
|
|
|
|
```bash
|
|
# Login first
|
|
curl -X POST https://api.example.com/login \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"email":"user@example.com","password":"secret"}' \
|
|
-c cookies.txt
|
|
|
|
# Use session cookie
|
|
curl https://api.example.com/v1/posts \
|
|
-b cookies.txt
|
|
```
|
|
|
|
## OAuth 2.0 (Optional)
|
|
|
|
If Laravel Passport is installed, OAuth 2.0 is available:
|
|
|
|
### Authorization Code Grant
|
|
|
|
```bash
|
|
# 1. Redirect user to authorization endpoint
|
|
https://api.example.com/oauth/authorize?
|
|
client_id=CLIENT_ID&
|
|
redirect_uri=CALLBACK_URL&
|
|
response_type=code&
|
|
scope=posts:read posts:write
|
|
|
|
# 2. Exchange code for token
|
|
curl -X POST https://api.example.com/oauth/token \
|
|
-d "grant_type=authorization_code" \
|
|
-d "client_id=CLIENT_ID" \
|
|
-d "client_secret=CLIENT_SECRET" \
|
|
-d "code=AUTH_CODE" \
|
|
-d "redirect_uri=CALLBACK_URL"
|
|
```
|
|
|
|
### Client Credentials Grant
|
|
|
|
For server-to-server:
|
|
|
|
```bash
|
|
curl -X POST https://api.example.com/oauth/token \
|
|
-d "grant_type=client_credentials" \
|
|
-d "client_id=CLIENT_ID" \
|
|
-d "client_secret=CLIENT_SECRET" \
|
|
-d "scope=posts:read"
|
|
```
|
|
|
|
## Scopes & Permissions
|
|
|
|
### Available Scopes
|
|
|
|
| Scope | Description |
|
|
|-------|-------------|
|
|
| `posts:read` | Read blog posts |
|
|
| `posts:write` | Create and update posts |
|
|
| `posts:delete` | Delete posts |
|
|
| `categories:read` | Read categories |
|
|
| `categories:write` | Create and update categories |
|
|
| `analytics:read` | Access analytics data |
|
|
| `webhooks:manage` | Manage webhook endpoints |
|
|
| `keys:manage` | Manage API keys |
|
|
| `admin:*` | Full admin access |
|
|
|
|
### Scope Enforcement
|
|
|
|
Protect routes with scope middleware:
|
|
|
|
```php
|
|
Route::middleware('scope:posts:write')
|
|
->post('/posts', [PostController::class, 'store']);
|
|
```
|
|
|
|
### Wildcard Scopes
|
|
|
|
Use wildcards for broad permissions:
|
|
|
|
- `posts:*` - All post permissions
|
|
- `*:read` - Read access to all resources
|
|
- `*` - Full access (use sparingly!)
|
|
|
|
## Authentication Errors
|
|
|
|
### 401 Unauthorized
|
|
|
|
Missing or invalid credentials:
|
|
|
|
```json
|
|
{
|
|
"message": "Unauthenticated."
|
|
}
|
|
```
|
|
|
|
**Causes:**
|
|
- No `Authorization` header
|
|
- Invalid API key
|
|
- Expired token
|
|
- Revoked credentials
|
|
|
|
### 403 Forbidden
|
|
|
|
Valid credentials but insufficient permissions:
|
|
|
|
```json
|
|
{
|
|
"message": "This action is unauthorized.",
|
|
"required_scope": "posts:write",
|
|
"provided_scopes": ["posts:read"]
|
|
}
|
|
```
|
|
|
|
**Causes:**
|
|
- Missing required scope
|
|
- Workspace suspended
|
|
- Resource access denied
|
|
|
|
## Best Practices
|
|
|
|
### 1. Use Minimum Required Scopes
|
|
|
|
```php
|
|
// ✅ Good - specific scopes
|
|
$apiKey->scopes = ['posts:read', 'categories:read'];
|
|
|
|
// ❌ Bad - excessive permissions
|
|
$apiKey->scopes = ['*'];
|
|
```
|
|
|
|
### 2. Rotate Keys Regularly
|
|
|
|
```php
|
|
// Rotate every 90 days
|
|
if ($apiKey->created_at->diffInDays() > 90) {
|
|
$apiKey->rotate();
|
|
}
|
|
```
|
|
|
|
### 3. Use Different Keys Per Client
|
|
|
|
```php
|
|
// ✅ Good - separate keys
|
|
ApiKey::create(['name' => 'Mobile App iOS']);
|
|
ApiKey::create(['name' => 'Mobile App Android']);
|
|
|
|
// ❌ Bad - shared key
|
|
ApiKey::create(['name' => 'All Mobile Apps']);
|
|
```
|
|
|
|
### 4. Monitor Key Usage
|
|
|
|
```php
|
|
$usage = ApiKey::find($id)->usage()
|
|
->whereBetween('created_at', [now()->subDays(7), now()])
|
|
->count();
|
|
```
|
|
|
|
### 5. Implement Key Expiry
|
|
|
|
```php
|
|
$apiKey = ApiKey::create([
|
|
'name' => 'Temporary Key',
|
|
'expires_at' => now()->addDays(30),
|
|
]);
|
|
```
|
|
|
|
## Rate Limiting
|
|
|
|
All authenticated requests are rate limited based on tier:
|
|
|
|
| Tier | Requests per Hour |
|
|
|------|------------------|
|
|
| Free | 1,000 |
|
|
| Pro | 10,000 |
|
|
| Enterprise | Unlimited |
|
|
|
|
Rate limit headers included in responses:
|
|
|
|
```
|
|
X-RateLimit-Limit: 10000
|
|
X-RateLimit-Remaining: 9995
|
|
X-RateLimit-Reset: 1640995200
|
|
```
|
|
|
|
## Testing Authentication
|
|
|
|
### Test Mode Keys
|
|
|
|
Use test keys for development:
|
|
|
|
```php
|
|
$testKey = ApiKey::create([
|
|
'name' => 'Test Key',
|
|
'environment' => 'test',
|
|
]);
|
|
|
|
// Key prefix: sk_test_...
|
|
```
|
|
|
|
Test keys:
|
|
- Don't affect production data
|
|
- Higher rate limits
|
|
- Clearly marked in admin panel
|
|
- Can be deleted without confirmation
|
|
|
|
### cURL Examples
|
|
|
|
**API Key:**
|
|
```bash
|
|
curl -H "Authorization: Bearer sk_live_..." \
|
|
https://api.example.com/v1/posts
|
|
```
|
|
|
|
**Sanctum Token:**
|
|
```bash
|
|
curl -H "Authorization: Bearer 1|..." \
|
|
https://api.example.com/v1/posts
|
|
```
|
|
|
|
**Session:**
|
|
```bash
|
|
curl -H "Cookie: laravel_session=..." \
|
|
https://api.example.com/v1/posts
|
|
```
|
|
|
|
## Learn More
|
|
|
|
- [API Reference →](/api/endpoints)
|
|
- [Rate Limiting →](/api/endpoints#rate-limiting)
|
|
- [Error Handling →](/api/errors)
|
|
- [API Package →](/packages/api)
|