binhvd/oauth2-server-php
Laravel/PHP integration for an OAuth2 authorization server, wrapping oauth2-server-php to issue and validate access tokens for APIs. Provides configuration and service setup to add OAuth2 flows, token storage, and request/resource protection.
Install the Package
composer require binhvd/oauth2-server-php
Ensure your project uses PHP 7.4+ (check composer.json constraints).
Basic Server Initialization
Create a minimal OAuth2 server in a route (e.g., routes/api.php):
use OAuth2\Server;
use OAuth2\GrantType\AuthorizationCodeGrant;
use OAuth2\GrantType\RefreshTokenGrant;
$server = new Server([
'grant_types' => [
AuthorizationCodeGrant::class,
RefreshTokenGrant::grantType(),
],
'storage' => new \OAuth2\Storage\Memory(), // Replace with DB in production
'access_lifetime' => 3600, // 1 hour
]);
$request = \OAuth2\Request::createFromGlobals();
$response = new \OAuth2\Response();
if ($server->handleTokenRequest($request, $response)->isValid()) {
$response->send();
} else {
http_response_code(401);
echo $response->getHttpBody();
}
First Use Case: Authorization Code Flow
/oauth/authorize (you’ll need to define this route)./oauth/token with code.curl -X POST http://your-app.test/oauth/token \
-d "grant_type=authorization_code&code=YOUR_CODE&redirect_uri=YOUR_REDIRECT_URI&client_id=CLIENT_ID&client_secret=CLIENT_SECRET"
Define Grant Types
Extend or use built-in grants (e.g., PasswordGrant, ClientCredentialsGrant):
$server = new Server([
'grant_types' => [
AuthorizationCodeGrant::class,
RefreshTokenGrant::grantType(),
PasswordGrant::class, // For resource owner credentials
],
]);
Storage Layer
Replace Memory storage with a database-backed solution (e.g., Eloquent):
use OAuth2\Storage\Pdo;
use OAuth2\Storage\Pdo as PdoStorage;
$pdo = new \PDO('mysql:host=localhost;dbname=oauth', 'user', 'pass');
$storage = new PdoStorage($pdo);
$server = new Server(['storage' => $storage]);
Middleware for Protected Routes Use middleware to validate access tokens in API routes:
// app/Http/Middleware/AuthenticateOAuth.php
public function handle($request, Closure $next) {
$token = $request->bearerToken();
if (!$token || !$this->validateToken($token)) {
return response()->json(['error' => 'invalid_token'], 401);
}
return $next($request);
}
private function validateToken($token) {
$server = new Server(['storage' => app('oauth.storage')]);
$request = \OAuth2\Request::createFromGlobals();
$request->setRequestToken($token);
$response = new \OAuth2\Response();
return $server->verifyResourceRequest($request, $response)->isValid();
}
Client Registration Dynamically register clients via a controller:
public function registerClient(Request $request) {
$client = new \OAuth2\Client(
$request->client_id,
$request->client_secret,
$request->redirect_uri
);
app('oauth.storage')->setClient($client);
return response()->json(['client_id' => $client->getId()]);
}
Scopes and Roles Attach scopes to tokens during grant processing:
// In a custom grant type
$token = new \OAuth2\AccessToken([
'client_id' => $clientId,
'user_id' => $userId,
'scope' => 'read write', // Space-separated scopes
]);
CSRF in Authorization Flow
VerifyCsrfToken middleware for /oauth/authorize routes.@method(['GET', 'POST']) public function authorize() to your controller and validate CSRF tokens.State Parameter Missing
AuthorizationCodeGrant expects a state parameter for security. Always include it in your authorize requests.$state = Str::random(40);
session(['oauth_state' => $state]);
Token Storage Quirks
Memory storage is not persistent. Use Pdo or Redis in production.dd(app('oauth.storage')->getClient($clientId));
Redirect URI Mismatch
redirect_uri strictly. Ensure your client’s registered URI matches the request.invalid_redirect_uri → Check setRedirectUri() in your client setup.Scope Validation
read write vs READ WRITE).error_details in the server config to get granular error messages:
$server = new Server([
'error_details' => true, // Show detailed errors in responses
]);
handleTokenRequest in a log:
\Log::info('Token request', [
'grant' => $request->getGrantType(),
'client_id' => $request->getClientId(),
]);
Authorization: Bearer YOUR_TOKEN
Custom Grant Types
Extend \OAuth2\GrantType\AbstractGrant to add custom flows (e.g., JWT bearer):
class JwtGrant extends AbstractGrant {
public function validateRequest(\OAuth2\RequestInterface $request) {
$token = $request->getHeader('Authorization');
if (!preg_match('/Bearer (.*)/', $token, $matches)) {
return false;
}
// Validate JWT logic here
return true;
}
}
Override Storage Methods
Extend \OAuth2\Storage\StorageInterface to add custom logic (e.g., audit logs):
class CustomStorage implements StorageInterface {
public function getClient($clientId) {
$client = parent::getClient($clientId);
\Log::info("Client $clientId accessed", ['client' => $client]);
return $client;
}
}
Response Customization
Modify the response format by extending \OAuth2\Response:
class ApiResponse extends \OAuth2\Response {
public function getHttpBody() {
return json_encode([
'error' => $this->error,
'error_description' => $this->errorDescription,
'custom_field' => 'value',
]);
}
}
User Authentication Integrate with Laravel’s auth system in grants:
// In a custom grant
$user = \Auth::guard('api')->user();
if (!$user) {
throw new \OAuth2\Exception\OAuthServerException(
\OAuth2\Exception\OAuthServerException::INVALID_GRANT,
'User not authenticated'
);
}
How can I help you explore Laravel packages today?