Hire Laravel developers with AI expertise at $20/hr. Get started in 48 hours.

Laravel Bastion

Laravel Bastion stats

Downloads
6
Stars
55
Open Issues
4
Forks
2

View on GitHub →

Stripe-inspired API authentication with environment isolation, granular scopes, and built-in security.

Laravel Bastion Logo

Laravel Bastion

Stripe-inspired API authentication with environment isolation, granular scopes, and built-in security.

Features

  • πŸ” Stripe-style API Tokens - Prefixed tokens with environment indicators (app_test_pk_*, app_live_sk_*)
  • 🌍 Environment Isolation - Separate test and live environments with automatic validation
  • 🎯 Granular Scopes - Fine-grained permission control with wildcard support
  • πŸ”‘ Token Types - Public, Secret, and Restricted keys with different access levels
  • πŸ“ Audit Logging - Comprehensive activity tracking for compliance and debugging
  • πŸͺ Webhook Support - Built-in webhook endpoints with signature verification
  • πŸ›‘οΈ Security First - Expiration dates and secure token hashing
  • ⚑ Laravel Native - Built with Laravel conventions and best practices

Requirements

  • PHP 8.4 or higher
  • Laravel 12.x

Installation

Install the package via Composer:

composer require juststeveking/laravel-bastion

Run the installation command:

php artisan bastion:install

This will:

  1. Publish the configuration file to config/bastion.php
  2. Publish the database migrations
  3. Optionally run the migrations

Add the Trait to Your User Model

use JustSteveKing\Bastion\Concerns\HasBastionTokens;
 
class User extends Authenticatable
{
use HasBastionTokens;
 
// ...
}

Quick Start

Generate a Token

use JustSteveKing\Bastion\Enums\TokenEnvironment;
use JustSteveKing\Bastion\Enums\TokenType;
 
$result = $user->createBastionToken(
name: 'My API Key',
scopes: ['users:read', 'users:write'],
environment: TokenEnvironment::Test,
type: TokenType::Restricted,
);
 
// Store this securely - it's only shown once!
$token = $result['plainTextToken'];
// Example: app_test_rk_a8Kx7mN2pQ4vW9yB1cD3eF5gH6jK8lM
 
echo "Token: " . $token;

Protect Routes with Middleware

use JustSteveKing\Bastion\Http\Middleware\AuthenticateToken;
 
Route::middleware(AuthenticateToken::class)->group(function () {
Route::get('/api/users', [UserController::class, 'index']);
});
 
// Require specific scope
Route::middleware([AuthenticateToken::class . ':users:write'])
->post('/api/users', [UserController::class, 'store']);

Make Authenticated Requests

curl -H "Authorization: Bearer app_test_rk_..." \
https://your-api.com/api/users

Token Types

Bastion supports three token types, inspired by Stripe:

Public Keys (pk)

TokenType::Public
  • Prefix: app_{env}_pk_*
  • Limited access, safe for client-side use
  • Ideal for JavaScript/mobile apps
  • Cannot perform sensitive operations

Secret Keys (sk)

TokenType::Secret
  • Prefix: app_{env}_sk_*
  • Full access to all permitted scopes
  • Must be kept secure on the server
  • Use for backend integrations

Restricted Keys (rk)

TokenType::Restricted
  • Prefix: app_{env}_rk_*
  • Scoped access with specific permissions
  • Best for third-party integrations
  • Follows principle of least privilege

Environments

Bastion isolates test and production data:

Test Environment

TokenEnvironment::Test
  • For development and testing
  • Higher rate limits (default: 100/min)
  • Can be used in any environment

Live Environment

TokenEnvironment::Live
  • For production traffic
  • Standard rate limits (default: 60/min)
  • Can be restricted from non-production environments (configurable)

Advanced Features

Token Rotation

Rotate tokens to create a new token while revoking the old one:

$result = $token->rotate();
 
// Get the new token (store securely)
$newToken = $result['plainTextToken'];
$newTokenModel = $result['token'];
 
// The old token is automatically revoked

You can also rotate via CLI:

php artisan bastion:rotate {token-id}

Scopes and Permissions

Bastion uses a flexible scope system with wildcard support:

// Grant specific permissions
$user->createBastionToken(
name: 'User Manager',
scopes: ['users:read', 'users:write'],
);
 
// Use wildcards for category-level access
$user->createBastionToken(
name: 'Payment API',
scopes: ['payments:*'], // All payment operations
);
 
// Full access
$user->createBastionToken(
name: 'Admin Token',
scopes: ['*'], // All scopes
);

Built-in Scope Examples

The package includes example scopes in ApiScope enum:

  • users:read, users:write, users:delete
  • payments:read, payments:create, payments:refund
  • webhooks:read, webhooks:write
  • * (admin/full access)

You can define your own scopes - they're just strings following the resource:action pattern.

Webhooks

Create webhook endpoints to receive real-time notifications:

use JustSteveKing\Bastion\Models\WebhookEndpoint;
 
$result = WebhookEndpoint::createEndpoint([
'user_id' => $user->id,
'url' => 'https://your-app.com/webhooks/bastion',
'events' => ['token.created', 'token.revoked', 'token.used'],
'environment' => TokenEnvironment::Live,
'is_active' => true,
]);
 
// Store the signing secret securely!
$signingSecret = $result['signingSecret'];
// Example: whsec_a8Kx7mN2pQ4vW9yB1cD3eF5gH6jK8lM

Verifying Webhook Signatures

use JustSteveKing\Bastion\Models\WebhookEndpoint;
 
Route::post('/webhooks/bastion', function (Request $request) {
$endpoint = WebhookEndpoint::where('secret_prefix', '...')->first();
 
$signature = $request->header('X-Bastion-Signature');
$timestamp = $request->header('X-Bastion-Timestamp');
$payload = $request->getContent();
 
if (!$endpoint->verifySignature($payload, $signature, (int)$timestamp)) {
abort(401, 'Invalid signature');
}
 
// Process webhook...
$event = $request->input('event');
$data = $request->input('data');
 
return response()->json(['received' => true]);
});

Events

Bastion dispatches events for all token lifecycle actions:

use JustSteveKing\Bastion\Events\{
TokenCreated,
TokenUsed,
TokenRevoked,
TokenRotated,
TokenExpired
};
 
// Listen to events in your EventServiceProvider
Event::listen(TokenCreated::class, function (TokenCreated $event) {
// $event->token - The BastionToken model
// $event->plainTextToken - The plain text token (only in TokenCreated)
Log::info('Token created', ['token_id' => $event->token->id]);
});
 
Event::listen(TokenUsed::class, function (TokenUsed $event) {
// $event->token
// $event->ipAddress
// $event->userAgent
// $event->endpoint
});
 
Event::listen(TokenRevoked::class, function (TokenRevoked $event) {
// $event->token
// $event->reason
Mail::to($event->token->user)->send(new TokenRevokedNotification($event));
});

Audit Logging

Enable comprehensive API request auditing by adding the middleware:

use JustSteveKing\Bastion\Http\Middleware\{AuthenticateToken, AuditApiRequest};
 
Route::middleware([AuthenticateToken::class, AuditApiRequest::class])
->group(function () {
// All requests will be logged
Route::get('/api/users', [UserController::class, 'index']);
});

Audit logs capture:

  • Request method, path, and query parameters
  • Response status code and time
  • IP address and user agent
  • Token and user information
  • Request/response bodies (configurable)

Query audit logs:

use JustSteveKing\Bastion\Models\AuditLog;
 
// Get recent activity for a token
$logs = AuditLog::where('bastion_token_id', $token->id)
->latest()
->take(100)
->get();
 
// Find failed requests
$failures = AuditLog::where('status_code', '>=', 400)
->where('created_at', '>=', now()->subDay())
->get();

CLI Commands

Bastion provides several Artisan commands for token management:

Generate Token

php artisan bastion:generate {user-id} "Token Name" \
--environment=test \
--type=restricted \
--scopes=users:read --scopes=users:write

Revoke Token

# Revoke by token ID
php artisan bastion:revoke 123 --reason="Security incident"
 
# Revoke by token prefix
php artisan bastion:revoke abc12345 --reason="No longer needed"
 
# Revoke all tokens for a user
php artisan bastion:revoke 0 --all-user=456 --reason="User offboarded"

Rotate Token

php artisan bastion:rotate {token-id}

Prune Expired Tokens

# Prune expired tokens
php artisan bastion:prune-tokens --expired
 
# Prune tokens unused for 90 days
php artisan bastion:prune-tokens --days=90

Prune Old Audit Logs

# Use config default (90 days)
php artisan bastion:prune-logs
 
# Custom retention period
php artisan bastion:prune-logs --days=30

Schedule these commands in your app/Console/Kernel.php:

protected function schedule(Schedule $schedule): void
{
$schedule->command('bastion:prune-tokens --expired')->daily();
$schedule->command('bastion:prune-logs')->weekly();
}

Configuration

Publish and edit the configuration file:

php artisan vendor:publish --tag=bastion-config

Key Configuration Options

return [
// Table names (customizable)
'tables' => [
'tokens' => 'bastion_tokens',
'audit_logs' => 'bastion_audit_logs',
'webhooks' => 'bastion_webhook_endpoints',
],
 
// Token expiration (days)
'token_expiration_days' => null,
 
// Audit log retention (days)
'audit_log_retention_days' => 90,
 
// Rate limits per minute
'rate_limits' => [
'test' => 100,
'live' => 60,
],
 
// Security settings
'security' => [
'prevent_test_tokens_in_production' => true,
'enable_audit_logging' => true,
'enable_alerting' => true,
],
 
// Error response format
'errors' => [
'use_rfc7807' => true, // RFC 7807 Problem Details
'base_url' => 'https://bastion.laravel.com/errors/', // Base for problem type URLs
],
 
// User model
'user_model' => App\Models\User::class,
];

RFC 7807 Base URL

Bastion returns errors in RFC 7807 Problem Details format by default. You can customize the base URL used for the type field in error responses:

// config/bastion.php
'errors' => [
'use_rfc7807' => true,
'base_url' => 'https://bastion.laravel.com/errors/',
],

With this configuration, an unauthenticated request will return a type like:

  • https://bastion.laravel.com/errors/token_missing
  • https://bastion.laravel.com/errors/token_invalid
  • https://bastion.laravel.com/errors/insufficient_scope

Adjust base_url to point to your own error documentation if desired.

Security Best Practices

  1. Never log tokens - Only the HMAC hash is stored in the database
  2. Show tokens once - Display the plain text token only at creation time
  3. Use HTTPS exclusively - Always transmit tokens over encrypted connections
  4. Use restricted tokens - Grant minimum necessary permissions (principle of least privilege)
  5. Set expiration dates - Especially for temporary integrations
  6. Rotate tokens regularly - Implement a token rotation policy (e.g., every 90 days)
  7. Monitor audit logs - Watch for suspicious activity and unusual patterns
  8. Use test tokens in development - Keep live tokens in production only
  9. Store tokens securely - Use environment variables or secure vaults (AWS Secrets Manager, HashiCorp Vault)

Token Security Features

Laravel Bastion implements multiple security layers:

  • HMAC-SHA256 hashing - Tokens are hashed with your application key
  • Constant-time comparison - Prevents timing attacks during token lookup
  • Cryptographically secure RNG - Uses random_bytes() for token generation
  • Environment isolation - Prevents test tokens in production (configurable)
  • Automatic event dispatching - Monitor all token lifecycle events

Community Requests

Have a feature idea? Open an issue with the enhancement label.

Out of Scope

Bastion focuses on token-based authentication with scopes and environments. It does not implement:

  • IP allowlisting or CIDR-based restrictions
  • Domain/host origin restrictions

If you need these controls, add them at your application layer (e.g., trusted proxies, firewall/WAF rules, or custom middleware) alongside Bastion.

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.


Juststeveking Laravel Bastion Related Articles

Laravel Envoyer API Package image

Laravel Envoyer API Package

Read article
Tighten logo

Tighten

We help companies turn great ideas into amazing apps, products, and services.

Tighten
DreamzTech logo

DreamzTech

Hire 6-10+ Yrs. experienced skilled Laravel Developers from DreamzTech. We ensure NDA protected, 100% quality delivery. Contact Us & Discuss Your Need.

DreamzTech
Statamic logo

Statamic

The drop-in ready Laravel CMS you’re been waiting for. Go full-stack or headless, flat file or database – it’s up to you.

Statamic
Harpoon: Next generation time tracking and invoicing logo

Harpoon: Next generation time tracking and invoicing

The next generation time-tracking and billing software that helps your agency plan and forecast a profitable future.

Harpoon: Next generation time tracking and invoicing
Celebian logo

Celebian

Celebian is a social media marketing agency specializing in helping their clients go viral on TikTok. Whether you're looking to reach a bigger audience or gain more Tiktok followers, likes, and views, they've got you covered.

Celebian
LoadForge logo

LoadForge

Scalable load testing for web apps & APIs. Simulate real-world traffic and identify breaking points and performance limits with powerful, scalable load tests designed for Laravel.

LoadForge