jundayw / passport
A lightweight and scalable PHP API request verification and response signature extension package.
Requires
- php: ^8.0
Requires (Dev)
- code-lts/doctum: ^5.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Laravel Passport
A lightweight and extensible Laravel package for API request verification and response signing.
Table of Contents
- Introduction
- Features
- Requirements
- Installation
- How Passport Works
- Usage
- Request Data
- Request Verification
- Accessing Public Parameters
- Signature Generation
- Response Signing
- Custom Public Parameters
- Configuration
- Custom Signer
- Complete Example
- Design Overview
- Security Considerations
- Contributing
- Contributors
- License
Introduction
jundayw/passport provides a simple and extensible way to sign and verify API requests and responses in Laravel applications.
It supports public parameters delivered through:
- HTTP request headers
- Query parameters
- Request body parameters
Passport automatically collects the configured public parameters from the request and uses them as the signing payload.
The package provides:
- Request signature verification
- Response signature generation
- Header, query, and request body support
- Built-in common parameters
- Custom signature parameter names
- Custom public parameters
- Custom HTTP header prefixes
- Pluggable signing drivers
- Laravel service container integration
- Fluent API
Features
- API request signature verification
- API response signature generation
- Configurable signing algorithms
- HMAC and hash-based signing support
- Extensible custom signer implementation
- Fluent API for building and verifying signatures
- Native Laravel service provider and facade integration
Requirements
- PHP 8.0 or higher
- Laravel 10.x or higher
Check the package dependencies for the exact Laravel versions supported by the current release.
Installation
Install the package via Composer:
composer require jundayw/passport
Publish Resources
Publish the package resources using the service provider:
php artisan vendor:publish --provider="Jundayw\Passport\PassportServiceProvider"
Or publish specific resources using their corresponding tags.
Publish Configuration
php artisan vendor:publish --tag=passport-config
Publish Migrations
php artisan vendor:publish --tag=passport-migrations
Run Migrations
After publishing the migrations, run:
php artisan migrate --path=database/migrations/2026_03_01_000000_create_passport_table.php
How Passport Works
A typical signed API request contains a set of public parameters and a signature.
For example:
app_id
action
type
charset
format
method
version
timestamp
nonce
signature
The signature field is generated from the public parameters and a shared application secret.
During verification, Passport:
- Resolves the configured public parameters.
- Reads them from the request.
- Locates the signature value.
- Removes the signature from the signing payload.
- Resolves the application secret.
- Generates the expected signature.
- Compares it with the provided signature.
The signature itself is never included when generating the signature payload.
Usage
Application Credentials
Passport uses an application identifier and a shared secret to sign and verify requests.
For example:
$appId = '2022082413545267'; $appSecret = 'your-app-secret';
The app_id identifies the application, while the application secret is used internally by the configured passport model to generate and verify signatures.
The application secret is not included in the request.
Built-in Public Parameters
Laravel Passport 6.0.0 provides the following public parameters by default:
[
'app_id',
'action',
'type',
'charset',
'format',
'method',
'version',
'timestamp',
'nonce',
]
These parameters are used to identify the application and describe the request when generating or verifying a signature.
| Parameter | Description |
|---|---|
app_id |
Application identifier used to resolve the signing secret. |
action |
API action or business operation. |
type |
Signature algorithm, such as MD5 or SHA512. |
charset |
Character encoding used by the request. |
format |
Request or response data format, such as JSON. |
method |
HTTP request method, such as GET or POST. |
version |
API version. |
timestamp |
Request timestamp. |
nonce |
Unique request value used to distinguish requests. |
The signature field is automatically appended to the parameter list. Its default name is signature.
The public parameters are configurable. The actual signing algorithm, canonicalization rules, and secret resolution are determined by the configured signing driver and application implementation.
Request Data
Passport supports the following request data sources:
- HTTP headers
- Query parameters
- Request body parameters
You can explicitly provide the data to be used during signature verification.
HTTP Headers
When the public parameters are transmitted through HTTP headers, the default x prefix converts parameter names into HTTP header names.
For example:
| Parameter | HTTP Header |
|---|---|
app_id |
X-App-Id |
action |
X-Action |
type |
X-Type |
charset |
X-Charset |
format |
X-Format |
method |
X-Method |
version |
X-Version |
timestamp |
X-Timestamp |
nonce |
X-Nonce |
signature |
X-Signature |
An example request header set is:
X-Action: api X-App-Id: 2022082413545267 X-Charset: UTF-8 X-Format: JSON X-Method: POST X-Nonce: DB17CF80-F0E2-4B7D-B075-6BDE33EA03DD X-Signature: 2BEEC5BA673C4BBF04649D36EC460998D542BF68527E6DA903A622069FE4F4A7AB7F342143454B3EF13615C59CC494454A27E32C06D5C7B389CC74AD845A32D0 X-Timestamp: 2026-09-24T11:39:48.165Z X-Type: SHA512 X-Version: 1.0.0
You can read the header values and verify the request as follows:
$passport = Passport::query($request->query()) ->request($request->post()); $verified = $passport->verify( $passport->getParameter('app_id'), $passport->getParameter('type'), );
The Passport::header() method accepts the application's header data. The parameter names used by the signing process remain consistent with the configured public parameters.
Query String
Public parameters can also be sent through the URL query string.
Example:
/api?app_id=2022082413545267&action=api&type=SHA512&charset=UTF-8&format=JSON&method=POST&version=1.0.0×tamp=2026-09-24T11:40:56.350Z&nonce=1C93F1BA-5330-4365-97DF-4D44108994E7&signature=04B7973BB74B0250A391825F8D261635BC68564A9C90C9D896D2DAC11A99B2B4A68512139343103E7A5199A33BE96A3C655505320C3F754FD6D6D4E648EED945
Verification example:
$passport = Passport::query($request->query()); $verified = $passport->verify( $passport->getParameter('app_id'), $passport->getParameter('type'), );
POST Body
When the public parameters and signature are sent together in a POST request body, the payload may look like this:
{
"app_id": "2022082413545267",
"action": "api",
"type": "SHA512",
"charset": "UTF-8",
"format": "JSON",
"method": "POST",
"version": "1.0.0",
"timestamp": "2026-09-24T11:38:17.327Z",
"nonce": "D06AF494-95B8-48EB-B6C5-128980F42C40",
"signature": "2FF555B4DBB65AD9F96A3F11B311FBE1EA7D6B424768EAF06E71C37179704A352781C3ADAD7F45C158DC5CEBCC68E68C8234AFB38F93FC9E9780ED3A1AD4189D"
}
The request can be verified using:
$passport = Passport::request($request->post()); $verified = $passport->verify( $passport->getParameter('app_id'), $passport->getParameter('type'), );
Combining Request Sources
Passport allows request data to be assembled from multiple locations.
For example:
$passport = Passport::query($request->query()) ->request($request->post());
This is useful when an API protocol defines different parts of the request in different locations.
For example:
HTTP Header
↓
X-App-Id
X-Type
X-Timestamp
X-Nonce
Query String
↓
page
limit
Request Body
↓
order_id
amount
Passport keeps these sections separately:
[
'header' => [...],
'params' => [...],
'data' => [...],
]
This allows the signing implementation to preserve the original request structure while generating a normalized signing payload.
Request Verification
The recommended way to verify a request is to build a Passport instance from the request data:
use Jundayw\Passport\Facades\Passport; $passport = Passport::query($request->query()) ->request($request->post()); if ($passport->verify( $passport->getParameter('app_id'), $passport->getParameter('type'), )) { return $next($request); }
The second argument determines the hashing algorithm.
For example:
$passport->verify( '2022082413545267', 'SHA512', );
The third argument can be used to select a signing driver:
$passport->verify( '2022082413545267', 'SHA512', 'hash_hmac', );
The default driver is:
hash_hmac
Middleware Example
Passport can collect request parameters and verify the signature through the Passport facade.
The following example uses a Laravel middleware to verify incoming API requests.
<?php namespace App\Http\Middleware; use App\Exceptions\InvalidSignatureException; use Closure; use Illuminate\Http\Request; use Jundayw\Passport\Facades\Passport; class ValidateSignature { public function handle(Request $request, Closure $next) { $passport = Passport::query($request->query()) ->request($request->post()); if ($passport->verify( $passport->getParameter('app_id', '2022082413545267'), $passport->getParameter('type', 'md5'), )) { return $next($request); } $this->processInvalidSignature($request); } protected function processInvalidSignature(Request $request) { throw new InvalidSignatureException; } }
The middleware performs the following operations:
- Creates a passport instance through the facade.
- Adds query parameters using
query(). - Adds POST parameters using
request(). - Retrieves the application identifier and signature algorithm.
- Verifies the request signature.
- Continues the request pipeline when verification succeeds.
- Throws an application-specific exception when verification fails.
Register the middleware in the appropriate middleware group or route definition.
The fallback application identifier and algorithm in this example are for demonstration only. In production, use values appropriate to your application's authentication and error-handling requirements.
Accessing Public Parameters
You can retrieve a resolved parameter using:
$passport->getParameter('app_id');
A default value can also be provided:
$passport->getParameter('app_id', '2022082413545267');
Retrieve all resolved parameters:
$passport->getParameters();
Retrieve the signature:
$passport->getSignatureValue();
For example:
$appId = $passport->getParameter('app_id'); $type = $passport->getParameter('type'); $signature = $passport->getSignatureValue();
Signature Generation
Passport can generate a signature from the current passport data:
$signature = $passport->signature( $passport->getParameter('app_id'), $passport->getParameter('type'), );
For example:
$passport = Passport::query($request->query()) ->request($request->post()); $signature = $passport->signature( $passport->getParameter('app_id'), $passport->getParameter('type'), );
The signature parameter is automatically excluded from the signing payload.
Response Signing
Passport can also sign response data.
Create a response passport:
$passport = Passport::response([ 'foo' => 'bar', ]);
Generate the signature:
$signature = $passport->signature( $passport->getParameter('app_id'), $passport->getParameter('type'), );
Alternatively, use withSignature() to append the generated signature:
$response = Passport::response([ 'foo' => 'bar', ])->withSignature( $appId, 'SHA512', )->getResponse();
The resulting data contains the signature:
[
'foo' => 'bar',
'signature' => '...',
]
Custom Public Parameters
You can customize the signature field name, public parameter list, and HTTP header prefix using Passport::make().
use Jundayw\Passport\Facades\Passport; $passport = Passport::make( 'sign', [ 'key', 'hash', 'nonce', ], 't', );
The arguments are:
Passport::make( string $signatureKey = 'signature', array $params = [], string|null $prefix = 'x', );
| Argument | Description |
|---|---|
$signatureKey |
Custom signature field name. |
$params |
Custom public parameter names. |
$prefix |
Prefix used when converting public parameters to HTTP header names. |
Custom Signature Field
The first argument defines the signature field name.
$passport = Passport::make('sign');
The signature field is now:
sign
Instead of the default:
signature
Custom Parameter List
The second argument defines the public parameters used by the passport instance.
$passport = Passport::make( 'sign', [ 'key', 'hash', 'nonce', ], );
The configured public parameters are:
[
'key',
'hash',
'nonce',
]
The custom signature field sign is automatically included in the parameter list.
Custom HTTP Header Prefix
The third argument defines the prefix used for HTTP header names.
$passport = Passport::make( 'sign', [ 'app_id', 'action', ], 't', );
With the t prefix, the parameter app_id is converted to:
T-App-Id
The parameter action is converted to:
T-Action
To disable the prefix, pass null:
$passport = Passport::make( 'sign', [ 'app_id', 'action', ], null, );
The corresponding header names will be:
App-Id Action
Custom Parameter Example
For an API using the following parameters:
[
'client_id',
'request_id',
'algorithm',
'timestamp',
]
with a custom signature field:
sign
and a custom header prefix:
api
you can configure Passport as follows:
$passport = Passport::make( 'sign', [ 'client_id', 'request_id', 'algorithm', 'timestamp', ], 'api', );
The corresponding HTTP headers become:
Api-Client-Id
Api-Request-Id
Api-Algorithm
Api-Timestamp
Api-Sign
Explicit Parameter Configuration
If you need to override the default parameter configuration for a particular request, use parameters():
$passport = Passport::parameters([ 'app_id', 'action', 'type', 'charset', 'format', 'method', 'version', 'timestamp', 'nonce', 'signature', ]);
This is useful when a specific API uses a parameter set different from the application's default.
The normal fluent API can then be used:
$passport = Passport::parameters([ 'app_id', 'action', 'type', 'timestamp', 'nonce', 'signature', ])->query($request->query()) ->request($request->post());
In most cases, however, the built-in parameters are sufficient and manual configuration is unnecessary.
Configuration
Passport supports configuration options for controlling request verification and response signing.
Disable Signature Verification
Passport supports disabling request verification through configuration.
When request verification is disabled, verify() returns true without performing signature verification.
This can be useful for development, testing, or environments where signature verification is handled externally.
The corresponding configuration options are:
'passport.enabled'
'passport.ignore.request'
Disable Response Signing
Response signing can similarly be controlled with:
'passport.ignore.response'
Custom Signer
Passport uses a driver-based signing architecture.
The default driver is:
hash_hmac
You can register your own signing driver by implementing the Signer contract.
use Jundayw\Passport\Contracts\Signer; use Jundayw\Passport\Facades\Passport; Passport::extend('AES', function () { return new class implements Signer { public function sign( string $algo, array $data, string $secret, ): string { // Implement your signing algorithm. } public function verify( string $algo, array $data, string $secret, string $signatureValue, ): bool { // Implement your verification algorithm. } }; });
The custom driver can then be selected when verifying a request:
$passport->verify( $appId, 'AES-256-CBC', 'AES', );
Or when generating a signature:
$passport->signature( $appId, 'AES-256-CBC', 'AES', );
The third argument identifies the registered driver:
Passport::extend('AES', ...);
This allows application-specific signing strategies to be added without modifying Passport's core implementation.
Complete Example
A typical Laravel application can use Passport as follows.
Request
use Illuminate\Http\Request; use Jundayw\Passport\Facades\Passport; $passport = Passport::query($request->query()) ->request($request->post()); if (! $passport->verify( $passport->getParameter('app_id'), $passport->getParameter('type'), )) { throw new InvalidSignatureException; }
Response
$passport = Passport::response([ 'code' => 200, 'data' => [ 'id' => 1001, ], ]); $response = $passport->withSignature( $passport->getParameter('app_id'), $passport->getParameter('type'), )->getResponse();
Custom Protocol
If your API uses:
signature field: sign
public parameters: client_id, timestamp, nonce
header prefix: api
configure it with:
$passport = Passport::make( 'sign', [ 'client_id', 'timestamp', 'nonce', ], 'api', );
The corresponding headers are:
Api-Client-Id
Api-Timestamp
Api-Nonce
Api-Sign
Design Overview
Passport separates request data collection, parameter resolution, signature generation, and credential resolution.
Laravel Request
│
┌───────────────┼───────────────┐
▼ ▼ ▼
Headers Query Body
│ │ │
└───────────────┼───────────────┘
▼
Passport
│
Public Parameters
│
▼
Remove Signature
│
▼
Sign / Verify
│
┌───────────┴───────────┐
▼ ▼
Request Response
Verify Sign
This architecture allows Passport to support different API protocols without coupling the signing logic to a specific request transport.
Security Considerations
When implementing request signing, consider the following:
- Keep application secrets private and never include them in requests.
- Validate timestamps to prevent replay attacks.
- Use a unique nonce for each request.
- Ensure the signature algorithm is agreed upon by both client and server.
- Use HTTPS to protect request data in transit.
- Keep the parameter list and canonicalization rules consistent between signing and verification.
- Store application secrets in a secure credential provider.
- Handle invalid signatures through application-specific exceptions and error responses.
Contributing
Contributions are welcome and greatly appreciated.
If you have an idea, improvement, or bug fix, feel free to open an issue or submit a pull request.
Development Workflow
-
Fork the project.
-
Create your feature branch:
git checkout -b feature/AmazingFeature
-
Commit your changes:
git commit -m "Add some AmazingFeature" -
Push your branch:
git push origin feature/AmazingFeature
-
Open a Pull Request.
If you find the project useful, consider giving it a star on GitHub.
Contributors
Thanks to all the people who have contributed to this project.
Contributions of any kind are welcome!
License
Distributed under the MIT License. See the License File for more information.