Add OAuth2 client credentials authentication to any Eloquent model.
composer require whilesmart/eloquent-client-credentialsPublish the config file:
php artisan vendor:publish --tag=client-credentials-configRun migrations:
php artisan migrate// config/client-credentials.php
return [
'default_model' => \Whilesmart\EloquentClientCredentials\Models\Client::class,
'owner_resolver' => \Whilesmart\EloquentClientCredentials\Resolvers\DefaultOwnerResolver::class,
'middleware_hooks' => [],
'routes' => [
'enabled' => false,
'prefix' => 'api',
'middleware' => [],
'client_routes' => false,
],
'oauth' => [
'enabled' => true,
'token_lifetime' => 3600,
'refresh_token_lifetime' => 86400 * 30,
'refresh_tokens_enabled' => false,
],
];Enable routes in your config:
'routes' => [
'enabled' => true,
'prefix' => 'api',
'middleware' => ['auth:sanctum'],
'client_routes' => true,
],This registers the following routes:
| Method | URI | Description |
|---|---|---|
| POST | /api/oauth/token |
Issue access token |
| POST | /api/oauth/revoke |
Revoke access token |
| GET | /api/clients |
List clients |
| POST | /api/clients |
Create client |
| GET | /api/clients/{slug} |
Get client |
| PUT | /api/clients/{slug} |
Update client |
| DELETE | /api/clients/{slug} |
Delete client |
| POST | /api/clients/{slug}/regenerate-secret |
Regenerate secret |
Use the HasClientCredentials trait:
use Whilesmart\EloquentClientCredentials\Traits\HasClientCredentials;
class ApiApp extends Model
{
use HasClientCredentials;
protected $fillable = ['name', 'secret', /* ... */];
}The trait provides:
setSecret(string $plainSecret)- Hash and store a secretverifySecret(string $secret)- Verify a plain secret against stored hashregenerateSecret()- Generate and store a new random secretplainSecret- Access the plain secret (only available immediately after creation/regeneration)
Configure how the owner is resolved for client operations. Create a custom resolver:
use Whilesmart\EloquentClientCredentials\Contracts\OwnerResolverInterface;
class CustomOwnerResolver implements OwnerResolverInterface
{
public function resolve(Request $request): ?Model
{
return $request->user()->currentTeam;
}
}Register in config:
'owner_resolver' => \App\Resolvers\CustomOwnerResolver::class,You can also pass an owner directly when using the controller programmatically:
$controller = app(ClientController::class);
$controller->store($request, $customOwner);Client Credentials Grant:
POST /api/oauth/token
Content-Type: application/json
{
"grant_type": "client_credentials",
"client_id": "your-client-id",
"client_secret": "your-client-secret",
"scope": "read write"
}Response:
{
"access_token": "...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read write",
"refresh_token": "..."
}Refresh Token Grant (when enabled):
POST /api/oauth/token
Content-Type: application/json
{
"grant_type": "refresh_token",
"refresh_token": "your-refresh-token"
}POST /api/oauth/revoke
Authorization: Bearer your-access-tokenAuthenticate requests using OAuth2 bearer tokens:
Route::middleware('client.bearer')->group(function () {
Route::get('/resource', fn () => 'Protected');
});
// With scope requirement
Route::middleware('client.bearer:admin')->get('/admin', fn () => 'Admin only');Authenticate using HTTP Basic Authentication:
Route::middleware('client.basic')->group(function () {
Route::get('/resource', fn () => 'Protected');
});Credentials: client_id:client_secret base64 encoded.
Authenticate using custom headers:
Route::middleware('client.auth')->group(function () {
Route::get('/resource', fn () => 'Protected');
});Headers required:
X-Client-ID: your-client-idX-Client-Secret: your-client-secret
In your bootstrap/app.php or service provider:
use Whilesmart\EloquentClientCredentials\Http\Middleware\AuthenticateBearerToken;
use Whilesmart\EloquentClientCredentials\Http\Middleware\AuthenticateBasicAuth;
use Whilesmart\EloquentClientCredentials\Http\Middleware\AuthenticateClient;
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'client.bearer' => AuthenticateBearerToken::class,
'client.basic' => AuthenticateBasicAuth::class,
'client.auth' => AuthenticateClient::class,
]);
})Add custom logic before/after controller actions:
// config/client-credentials.php
'middleware_hooks' => [
\App\Hooks\MyClientHook::class,
],Create a hook class:
use Whilesmart\EloquentClientCredentials\Interfaces\MiddlewareHookInterface;
class MyClientHook implements MiddlewareHookInterface
{
public function before(Request $request, string $action): ?Request
{
// Modify request or return null to continue
return $request;
}
public function after(Request $request, JsonResponse $response, string $action): JsonResponse
{
// Modify response
return $response;
}
}Available hook actions (from HookAction enum):
CLIENT_STORECLIENT_UPDATECLIENT_DELETECLIENT_REGENERATE_SECRETTOKEN_ISSUETOKEN_REVOKE
Default client model with:
- UUID primary key
- Sluggable name
- Polymorphic owner relationship
- Revocation support
OAuth2 access tokens with:
- UUID primary key
- Polymorphic client relationship
- Scopes support
- Expiration and revocation
Refresh tokens with:
- UUID primary key
- Linked to access token (cascades on delete)
- Expiration and revocation
# Config
php artisan vendor:publish --tag=client-credentials-config
# Migrations
php artisan vendor:publish --tag=client-credentials-migrations
# Language files
php artisan vendor:publish --tag=client-credentials-lang
# Routes
php artisan vendor:publish --tag=client-credentials-routescomposer testMIT