-
Notifications
You must be signed in to change notification settings - Fork 0
Security Attributes
BOUNDLY provides declarative security attributes to protect sensitive data, control access, and ensure data integrity at the attribute level.
Infrastructure\FrameworkCore\Attributes\Security\Excludes a property from all API responses. Useful for sensitive data that should never be exposed to clients.
Target: Property
Use Case: Passwords, passwords confirmations, tokens, internal IDs, or any field that should never appear in JSON output.
Example:
use Infrastructure\FrameworkCore\Attributes\Schema\{Entity, Column, Id};
use Infrastructure\FrameworkCore\Attributes\Security\Hidden;
#[Entity(table: 'users', resource: 'users')]
class User extends AggregateRoot
{
#[Id]
private int $id;
#[Column(type: 'string', length: 150)]
private string $name;
#[Column(type: 'string', length: 255)]
private string $email;
#[Hidden]
#[Column(type: 'string')]
private string $password;
#[Hidden]
#[Column(type: 'string', length: 255)]
private string $internalNotes;
}API Response (GET /api/users/1):
{
"id": 1,
"name": "John Doe",
"email": "john@example.com"
}Note: The password and internalNotes fields are completely excluded from the response.
Encrypts the property value before storing in the database and decrypts it when retrieving. Uses AES-256-CBC encryption.
Target: Property
| Parameter | Type | Default | Description |
|---|---|---|---|
algorithm |
string |
'AES-256-CBC' |
Encryption algorithm |
Use Case: Personally Identifiable Information (PII), API keys, secrets, sensitive configuration data.
Requirements:
-
APP_KEYmust be set in your.envfile - Encryption happens transparently on INSERT/UPDATE
- Decryption happens transparently on SELECT
Example:
use Infrastructure\FrameworkCore\Attributes\Schema\{Entity, Column, Id};
use Infrastructure\FrameworkCore\Attributes\Security\Encrypted;
#[Entity(table: 'integrations', resource: 'integrations')]
class Integration extends AggregateRoot
{
#[Id]
private int $id;
#[Column(type: 'string', length: 150)]
private string $name;
#[Encrypted]
#[Column(type: 'text')]
private string $apiKey;
#[Encrypted(algorithm: 'AES-256-CBC')]
#[Column(type: 'text')]
private string $webhookSecret;
}Database Storage:
-- Stored encrypted (not readable in plain text)
-- aW5zZXJ0LXZlcnktbG9uZy1hcGkta2V5LXZhbHVlAPI Behavior:
// GET /api/integrations/1
{
"id": 1,
"name": "Stripe",
"apiKey": "sk_live_abc123...", // Automatically decrypted
"webhookSecret": "whsec_xyz789..." // Automatically decrypted
}Automatically hashes the value before storage using bcrypt or Argon2. Ideal for passwords and sensitive strings that should never be retrievable.
Target: Property
| Parameter | Type | Default | Description |
|---|---|---|---|
algorithm |
string |
'bcrypt' |
Hashing algorithm (bcrypt, argon2i, argon2id) |
options |
array |
[] |
Algorithm-specific options (e.g., ['rounds' => 12]) |
Use Case: Passwords, PINs, security questions answers, or any sensitive data that should be stored irreversibly.
Example:
use Infrastructure\FrameworkCore\Attributes\Schema\{Entity, Column, Id};
use Infrastructure\FrameworkCore\Attributes\Security\Hashed;
#[Entity(table: 'users', resource: 'users')]
class User extends AggregateRoot
{
#[Id]
private int $id;
#[Column(type: 'string', length: 150)]
private string $name;
#[Hashed(algorithm: 'bcrypt', options: ['rounds' => 12])]
#[Column(type: 'string')]
private string $password;
#[Hashed(algorithm: 'argon2id')]
#[Column(type: 'string')]
private string $pin;
}Database Storage:
-- Stored as bcrypt hash (irreversible)
-- $2y$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.H7pB7p4ZZ7Z7LKImportant Notes:
- Hashing is one-way - You cannot retrieve the original value
- Hashes on INSERT - Automatically hashes when creating records
- Hashes on UPDATE - Only if the value has changed
-
Verification - Use
Hash::check($plain, $hashed)to verify:
if (Hash::check($request->password, $user->password)) {
// Password is correct
}You can combine multiple security attributes for maximum protection:
use Infrastructure\FrameworkCore\Attributes\Schema\{Entity, Column, Id};
use Infrastructure\FrameworkCore\Attributes\Security\{Hidden, Encrypted, Hashed};
#[Entity(table: 'users', resource: 'users')]
class User extends AggregateRoot
{
#[Id]
private int $id;
#[Column(type: 'string', length: 150)]
private string $name;
// Never returned in API responses
#[Hidden]
// Encrypted at rest in database
#[Encrypted]
// Hashed before storage (one-way)
#[Hashed(algorithm: 'bcrypt', options: ['rounds' => 12])]
#[Column(type: 'string')]
private string $password;
// Visible in API but encrypted in database
#[Encrypted]
#[Column(type: 'string', length: 20)]
private string $phone;
// Encrypted and hidden
#[Hidden]
#[Encrypted]
#[Column(type: 'text')]
private string $socialSecurityNumber;
}βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β REQUEST RECEIVED β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β BOUNDLY VALIDATION β
β - #[Validation] attributes validate input β
β - Reject invalid data before processing β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β INSERT/UPDATE β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββββββ β
β β #[Hashed] ββ β Hash value ββ β Store in DB (irreversible)β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββββββ β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββββββ β
β β #[Encrypted]ββ β Encrypt AES ββ β Store in DB (encrypted) β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SELECT β
β - #[Encrypted] values are automatically decrypted β
β - #[Hashed] values remain hashed (cannot decrypt) β
β - #[Hidden] values are stripped from response β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β API RESPONSE β
β - Clean, safe data returned to client β
β - No passwords, encrypted values, or hidden fields β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Attribute | Target | Purpose | Reversible |
|---|---|---|---|
#[Hidden] |
Property | Exclude from API responses | N/A |
#[Encrypted] |
Property | Encrypt at rest in database | Yes (with key) |
#[Hashed] |
Property | Hash irreversibly | No |
-
Always use
#[Hidden]on passwords - Never expose hash in API -
Use
#[Encrypted]for PII - Names, addresses, phone numbers -
Use
#[Hashed]for credentials - Passwords that verify, never display - Rotate encryption keys carefully - Encrypted data will become unreadable
-
Never commit
APP_KEY- Use environment variables
BOUNDLY v0.9.0 includes comprehensive security middleware and services:
| Middleware | Purpose |
|---|---|
SecurityHeadersMiddleware |
X-Frame-Options, HSTS, CSP, X-XSS-Protection |
CorsMiddleware |
Cross-Origin Resource Sharing |
RequestSizeLimitMiddleware |
Protects against large payload attacks |
RateLimitMiddleware |
Per-IP/user rate limiting |
BruteForceProtectionMiddleware |
Login attempt throttling |
InputSanitizationMiddleware |
XSS, SQLi, injection prevention |
ApiKeyMiddleware |
API Key authentication |
IpAccessMiddleware |
IP whitelist/blacklist enforcement |
RequestSigningMiddleware |
HMAC request signature verification |
TierThrottleMiddleware |
Tier-based rate limiting |
| Service | Purpose |
|---|---|
SecurityLogger |
Comprehensive audit logging with 16+ event types |
BruteForceProtectionService |
Tracks failed attempts, manages lockouts |
InputSanitizer |
Detects and sanitizes malicious input |
OwnershipValidator |
BOLA/IDOR protection |
SecureFileUploader |
Secure file upload handling |
IpAccessControl |
IP whitelist/blacklist with CIDR support |
RequestSigningService |
HMAC-SHA256 request signing |
TierBasedThrottlingService |
Multi-tier rate limiting |
| Attribute | Purpose |
|---|---|
#[ApiKey] |
Declare API Key authentication requirements |
#[ThrottleLogin] |
Configure login throttling per endpoint |
#[Ownership] |
Define object-level authorization rules |
-
auth.login.success/auth.login.failed -
auth.logout,auth.token.expired,auth.token.invalid security.rate_limit.exceeded-
security.unauthorized,security.forbidden -
security.brute_force.detected,security.brute_force.blocked security.suspicious_input-
security.apikey.created,security.apikey.revoked security.cors.violationsecurity.request_size.exceeded
BOUNDLY v0.9.0 includes IP-based access control with whitelist/blacklist support.
// config/boundly.php
'ip_access' => [
'enabled' => true,
'default_action' => 'deny', // or 'allow'
'whitelist' => [
'192.168.1.0/24', // CIDR notation
'10.0.0.*', // Wildcard notation
'172.16.0.1', // Exact IP
],
'blacklist' => [
'192.168.1.100', // Block specific IP
],
'cache_store' => 'file',
],$accessControl = app(IpAccessControl::class);
// Check if IP is allowed
if (!$accessControl->isAllowed($request->ip())) {
return response()->json(['error' => 'Access denied'], 403);
}
// Runtime management
$accessControl->addToBlacklist('192.168.1.50');
$accessControl->removeFromBlacklist('192.168.1.50');| Pattern | Example | Description |
|---|---|---|
| Exact IP | 192.168.1.1 |
Single IP address |
| Wildcard | 192.168.1.* |
Any IP in range |
| CIDR | 192.168.1.0/24 |
Network subnet |
Verify request authenticity with HMAC-SHA256 signatures.
// config/boundly.php
'security' => [
'request_signing' => [
'enabled' => true,
'algorithm' => 'sha256',
'secret_key' => env('REQUEST_SIGNING_SECRET'),
'timestamp_tolerance' => 300, // 5 minutes
],
],// Generate signature on client
$timestamp = time();
$payload = "{$method}\n{$path}\n{$timestamp}\n{$contentType}\n{$body}";
$signature = hash_hmac('sha256', $payload, $secretKey);
// Send request
curl -X POST /api/users \
-H "X-Timestamp: {$timestamp}" \
-H "X-Signature: {$signature}" \
-H "Content-Type: application/json" \
-d '{"name": "John"}'$signingService = app(RequestSigningService::class);
// Verify incoming request
if (!$signingService->verifySignature($request)) {
return response()->json(['error' => 'Invalid signature'], 401);
}Implement different rate limits per API tier (free/basic/pro/enterprise).
// config/boundly.php
'security' => [
'tier_throttling' => [
'enabled' => true,
'cache_store' => 'file',
'tiers' => [
'free' => [
'requests_per_minute' => 60,
'requests_per_hour' => 1000,
'requests_per_day' => 10000,
],
'basic' => [
'requests_per_minute' => 300,
'requests_per_hour' => 5000,
'requests_per_day' => 50000,
],
'pro' => [
'requests_per_minute' => 1000,
'requests_per_hour' => 20000,
'requests_per_day' => 200000,
],
'enterprise' => [
'requests_per_minute' => 5000,
'requests_per_hour' => 100000,
'requests_per_day' => 1000000,
],
],
],
],$throttlingService = app(TierBasedThrottlingService::class);
// Check tier limits
$result = $throttlingService->checkLimit($request, 'pro');
if (!$result['allowed']) {
return response()->json([
'error' => 'Rate limit exceeded',
'limit_type' => $result['limit_type'],
'retry_after' => $result['retry_after'] ?? 60,
], 429);
}
// Get current usage
$usage = $throttlingService->getUsage($request);X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 845
Next Step: Behavioral-Traits π‘οΈ