Installation:
composer require labrodev/dpop
php artisan dpop:install
.env and publish the config file.First Use Case:
POST /oauth/token) with a DPoP proof in the DPoP header.
Example cURL:
curl -X POST "http://your-app.test/oauth/token" \
-H "DPoP: <your-dpop-jwt>" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials"
dpop middleware to a route to enforce DPoP verification:
Route::get('/protected', function () {
return response()->json(['message' => 'Protected route']);
})->middleware('dpop');
Key Setup:
DPOP_CLIENT_PRIVATE_KEY and DPOP_CLIENT_PUBLIC_KEY in .env.openssl ecparam -genkey -name prime256v1 -out client_private.pem
openssl ec -in client_private.pem -pubout -out client_public.pem
Token Issuance:
jti (JWT ID) and htp (HTTP request target) claims.
Example (pseudo-code):
const dpopJwt = createDpopJwt({
jti: 'unique-request-id',
htp: 'http://your-app.test/protected',
privateKey: clientPrivateKey
});
DPoP header along with the OAuth2 token request.Route Protection:
dpop middleware automatically:
DPoP header.jti and htp claims match the request.DpopVerified, DpopFailed) for custom logic.Token Verification:
cnf claim.OAuth2 Integration:
laravel/passport or league/oauth2-server for OAuth2 flows. The dpop middleware can be chained with OAuth2 middleware.Route::get('/api/user', function () {
return response()->json(['user' => auth()->user()]);
})->middleware(['auth:api', 'dpop']);
API Clients:
dpop-js can help.Testing:
DPoP header in tests:
$response = $this->withHeaders(['DPoP' => $validDpopJwt])
->get('/protected');
Key Rotation:
DPOP_CLIENT_PUBLIC_KEY in the config.Key Management:
-----BEGIN EC PRIVATE KEY-----
...
-----END EC PRIVATE KEY-----
DPoP JWT Claims:
jti Uniqueness: The jti (JWT ID) must be unique per request. Reusing jti values can lead to validation failures.htp Mismatch: The htp (HTTP target) claim must exactly match the request's host and path. Trailing slashes or case sensitivity may cause issues.
htp: "https://example.com/api" vs. htp: "https://example.com/api/".Middleware Order:
dpop middleware after OAuth2 authentication middleware to avoid redundant checks:
// Correct order
->middleware(['auth:api', 'dpop'])
dpop before auth:api may cause unnecessary DPoP validation for unauthenticated requests.Token Endpoint:
/oauth/token) expects the DPoP header. Ensure your OAuth2 client libraries support custom headers.DPoP header even for grant_type=client_credentials.CORS:
DPoP header is included in the allowed headers:
// config/cors.php
'paths' => ['api/*', 'oauth/token'],
'allowed_headers' => ['*', 'DPoP'],
Validation Errors:
dpop.log file (if configured) for detailed errors.Invalid DPoP signature: Verify the private/public key pair.JTI already used: Regenerate the jti for the request.HTP mismatch: Ensure the htp claim matches the request URL.Logging:
config/dpop.php:
'debug' => env('DPOP_DEBUG', false),
Testing DPoP:
php artisan dpop:generate-dpop-jwt --jti=test123 --htp="http://your-app.test/protected" --private-key-path=client_private.pem
Custom Middleware:
DpopMiddleware class to add custom logic:
namespace App\Http\Middleware;
use Labrodev\Dpop\Middleware\DpopMiddleware as BaseDpopMiddleware;
class CustomDpopMiddleware extends BaseDpopMiddleware
{
public function handle($request, Closure $next)
{
// Custom logic before DPoP verification
$response = parent->handle($request, $next);
// Custom logic after DPoP verification
return $response;
}
}
app/Http/Kernel.php:
protected $routeMiddleware = [
'dpop' => \App\Http\Middleware\CustomDpopMiddleware::class,
];
Events:
use Labrodev\Dpop\Events\DpopVerified;
use Labrodev\Dpop\Events\DpopFailed;
DpopVerified::listen(function (DpopVerified $event) {
// Log successful DPoP verification
});
DpopFailed::listen(function (DpopFailed $event) {
// Handle failed DPoP verification (e.g., revoke token)
});
Token Binding:
DpopTokenService:
namespace App\Services;
use Labrodev\Dpop\Services\DpopTokenService as BaseDpopTokenService;
class CustomDpopTokenService extends BaseDpopTokenService
{
public function bindTokenToClient($token, $clientId)
{
// Custom binding logic
return parent::bindTokenToClient($token, $clientId);
}
}
config/dpop.php:
'token_service' => \App\Services\CustomDpopTokenService::class,
Key Storage:
How can I help you explore Laravel packages today?