OAuth2 Server
A complete OAuth2 server implementation for the Hyperf framework based on league/oauth2-server.
Features
- Full OAuth2 server implementation supporting:
- Client Credentials Grant
- Password Grant
- Refresh Token Grant
- Authorization Code Grant (with PKCE support)
- Built-in client management commands
- Multiple storage backends (Eloquent ORM)
- Customizable token lifetimes
- Scope management
- Event-driven architecture
Installation
1. Install via Composer
composer require friendsofhyperf/oauth2-server
2. Publish Configuration
php bin/hyperf.php vendor:publish friendsofhyperf/oauth2-server
3. Generate Encryption Keys
# Generate private/public key pair
php bin/hyperf.php oauth2:generate-keypair
This will generate:
storage/oauth2/private.key
- Private key for signing tokensstorage/oauth2/public.key
- Public key for verifying tokens
4. Run Database Migrations
php bin/hyperf.php migrate
Configuration
Configure the OAuth2 server in config/autoload/oauth2-server.php
:
<?php
return [
'authorization_server' => [
'private_key' => env('OAUTH2_PRIVATE_KEY', 'storage/oauth2/private.key'),
'private_key_passphrase' => env('OAUTH2_PRIVATE_KEY_PASSPHRASE'),
'encryption_key' => env('OAUTH2_ENCRYPTION_KEY'),
'encryption_key_type' => EncryptionKeyType::from(env('OAUTH2_ENCRYPTION_KEY_TYPE', 'plain')),
'response_type' => BearerTokenResponse::class,
'revoke_refresh_tokens' => true,
'access_token_ttl' => new DateInterval('PT1H'),
'auth_code_ttl' => new DateInterval('PT10M'),
'refresh_token_ttl' => new DateInterval('P1M'),
'enable_client_credentials_grant' => true,
'enable_password_grant' => true,
'enable_refresh_token_grant' => true,
'enable_auth_code_grant' => true,
'enable_implicit_grant' => false,
'require_code_challenge_for_public_clients' => true,
'persist_access_tokens' => true,
],
'resource_server' => [
'public_key' => env('OAUTH2_PUBLIC_KEY', 'storage/oauth2/public.key'),
'jwt_leeway' => null,
],
'scopes' => [
'available' => ['read', 'write', 'admin'],
'default' => ['read'],
],
];
Environment Variables
Set these environment variables in your .env
file:
# OAuth2 Keys
OAUTH2_PRIVATE_KEY=storage/oauth2/private.key
OAUTH2_PUBLIC_KEY=storage/oauth2/public.key
OAUTH2_PRIVATE_KEY_PASSPHRASE=
OAUTH2_ENCRYPTION_KEY=your-encryption-key-here
# Optional
OAUTH2_ENCRYPTION_KEY_TYPE=plain
Available Commands
Command | Description |
---|---|
oauth2:clear-expired-tokens | Clear expired access/refresh tokens |
oauth2:create-client | Create new OAuth2 client |
oauth2:delete-client | Delete OAuth2 client |
oauth2:generate-keypair | Generate private/public key pair |
oauth2:list-clients | List all OAuth2 clients |
oauth2:update-client | Update OAuth2 client |
Create Client
Create client for authorization code grant:
php bin/hyperf.php oauth2:create-client \
--name="My Web App" \
--redirect-uri="https://myapp.com/callback" \
--grant-type="authorization_code" \
--grant-type="refresh_token"
Create client for password grant:
php bin/hyperf.php oauth2:create-client \
--name="My Mobile App" \
--grant-type="password" \
--grant-type="refresh_token"
Create client for client credentials grant:
php bin/hyperf.php oauth2:create-client \
--name="My API Service" \
--grant-type="client_credentials"
API Endpoints
Authorization Endpoint
GET /oauth/authorize
For authorization code flow. Parameters:
response_type
: Must becode
client_id
: Client IDredirect_uri
: Must match registered callback URIscope
: Space-separated scope liststate
: CSRF protection tokencode_challenge
: PKCE code challengecode_challenge_method
: PKCE method (usuallyS256
)
Token Endpoint
POST /oauth/token
For exchanging authorization codes for access tokens or using other grant types.
Protected Resources
Protect routes with ResourceServerMiddleware
:
use FriendsOfHyperf\Oauth2\Server\Middleware\ResourceServerMiddleware;
Router::addGroup('/api', function () {
Router::get('user', [UserController::class, 'index']);
Router::post('posts', [PostController::class, 'store']);
})->add(ResourceServerMiddleware::class);
Grant Types
1. Client Credentials Grant
For server-to-server authentication:
curl -X POST http://your-server/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "your-client-id",
"client_secret": "your-client-secret",
"scope": "read write"
}'
2. Password Grant
For trusted applications (mobile apps, SPAs):
curl -X POST http://your-server/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "password",
"client_id": "your-client-id",
"client_secret": "your-client-secret",
"username": "user@example.com",
"password": "password",
"scope": "read write"
}'
3. Authorization Code Grant
For web applications requiring user interaction:
Step 1: Redirect user to authorization endpoint
https://your-server/oauth/authorize?response_type=code&client_id=your-client-id&redirect_uri=https://myapp.com/callback&scope=read&state=random-state&code_challenge=challenge&code_challenge_method=S256
Step 2: Exchange authorization code for token
curl -X POST http://your-server/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"client_id": "your-client-id",
"client_secret": "your-client-secret",
"redirect_uri": "https://myapp.com/callback",
"code_verifier": "verifier",
"code": "authorization-code-from-redirect"
}'
4. Refresh Token Grant
Obtain new access token:
curl -X POST http://your-server/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "refresh_token",
"client_id": "your-client-id",
"client_secret": "your-client-secret",
"refresh_token": "your-refresh-token",
"scope": "read write"
}'
Making Authenticated Requests
Include access token in Authorization header:
curl -X GET http://your-server/api/user \
-H "Authorization: Bearer your-access-token"
Event System
The component dispatches these events you can listen for:
AuthorizationRequestResolveEvent
: When authorization request needs user approvalUserResolveEvent
: When resolving user for password grantScopeResolveEvent
: When resolving scopesTokenRequestResolveEvent
: When processing token requests
Example Event Listener
<?php
namespace App\Listener;
use FriendsOfHyperf\Oauth2\Server\Event\UserResolveEvent;
use Hyperf\Event\Annotation\Listener;
#[Listener]
class UserResolveListener
{
public function listen(): array
{
return [
UserResolveEvent::class,
];
}
public function process(object $event): void
{
// Validate user credentials and return user ID
if ($event->getUsername() === 'admin' && $event->getPassword() === 'secret') {
$event->setUserId('1');
}
}
}
Database Tables
The package creates these tables:
oauth_clients
: OAuth2 clientsoauth_access_tokens
: Access tokensoauth_refresh_tokens
: Refresh tokensoauth_auth_codes
: Authorization codesoauth_personal_access_clients
: Personal access clients
Customization
Custom User Provider
Implement your own user resolution logic by listening to UserResolveEvent
.
Custom Scope Management
Listen to ScopeResolveEvent
to implement custom scope logic.
Custom Token Storage
Extend repository classes to implement custom storage backends.
Security Best Practices
- Always use HTTPS in production
- Store private keys securely with proper file permissions
- Use strong encryption keys
- Implement proper CSRF protection for authorization flow
- Strictly validate callback URIs
- Use short-lived access tokens with refresh tokens
- Implement rate limiting on token endpoint
- Log and monitor token usage
Testing
During development, you can test OAuth2 flows using built-in commands:
# Create test client
php bin/hyperf.php oauth2:create-client \
--name="Test Client" \
--redirect-uri="http://localhost:3000/callback" \
--grant-type="authorization_code" \
--grant-type="password" \
--grant-type="refresh_token"
# List all clients
php bin/hyperf.php oauth2:list-clients
# Clear expired tokens
php bin/hyperf.php oauth2:clear-expired-tokens
Error Handling
Common error responses:
invalid_client
: Client authentication failedinvalid_grant
: Invalid grantinvalid_request
: Missing required parametersinvalid_scope
: Requested scope is invalidunsupported_grant_type
: Unsupported grant typeserver_error
: Internal server error
License
MIT