Skip to content

Security Attributes

lpachecob edited this page Mar 24, 2026 · 3 revisions

πŸ”’ Security Attributes

BOUNDLY provides declarative security attributes to protect sensitive data, control access, and ensure data integrity at the attribute level.


πŸ“ Location

Infrastructure\FrameworkCore\Attributes\Security\

🎭 #[Hidden]

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.


πŸ” #[Encrypted]

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_KEY must be set in your .env file
  • 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)
-- aW5zZXJ0LXZlcnktbG9uZy1hcGkta2V5LXZhbHVl

API Behavior:

// GET /api/integrations/1
{
  "id": 1,
  "name": "Stripe",
  "apiKey": "sk_live_abc123...",  // Automatically decrypted
  "webhookSecret": "whsec_xyz789..."  // Automatically decrypted
}

#️⃣ #[Hashed]

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.H7pB7p4ZZ7Z7LK

Important Notes:

  1. Hashing is one-way - You cannot retrieve the original value
  2. Hashes on INSERT - Automatically hashes when creating records
  3. Hashes on UPDATE - Only if the value has changed
  4. Verification - Use Hash::check($plain, $hashed) to verify:
if (Hash::check($request->password, $user->password)) {
    // Password is correct
}

🎯 Combining Security Attributes

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;
}

πŸ”„ Security Attribute Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        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             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“‹ Security Attributes Quick Reference

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

⚠️ Best Practices

  1. Always use #[Hidden] on passwords - Never expose hash in API
  2. Use #[Encrypted] for PII - Names, addresses, phone numbers
  3. Use #[Hashed] for credentials - Passwords that verify, never display
  4. Rotate encryption keys carefully - Encrypted data will become unreadable
  5. Never commit APP_KEY - Use environment variables

πŸ›‘οΈ Enterprise Security Features

BOUNDLY v0.9.0 includes comprehensive security middleware and services:

Security Middlewares

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

Security Services

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

Behavioral Security Attributes

Attribute Purpose
#[ApiKey] Declare API Key authentication requirements
#[ThrottleLogin] Configure login throttling per endpoint
#[Ownership] Define object-level authorization rules

Security Events (logged by SecurityLogger)

  • 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.violation
  • security.request_size.exceeded

🌐 IP Access Control

BOUNDLY v0.9.0 includes IP-based access control with whitelist/blacklist support.

Configuration

// 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',
],

IpAccessControl Service

$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');

Supported Patterns

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

πŸ” Request Signing (HMAC)

Verify request authenticity with HMAC-SHA256 signatures.

Configuration

// config/boundly.php
'security' => [
    'request_signing' => [
        'enabled' => true,
        'algorithm' => 'sha256',
        'secret_key' => env('REQUEST_SIGNING_SECRET'),
        'timestamp_tolerance' => 300,  // 5 minutes
    ],
],

Client-Side Signing

// 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"}'

Server-Side Verification

$signingService = app(RequestSigningService::class);

// Verify incoming request
if (!$signingService->verifySignature($request)) {
    return response()->json(['error' => 'Invalid signature'], 401);
}

πŸ“Š Tier-Based Throttling

Implement different rate limits per API tier (free/basic/pro/enterprise).

Configuration

// 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,
            ],
        ],
    ],
],

Usage

$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);

Response Headers

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 845

Next Step: Behavioral-Traits πŸ›‘οΈ

Clone this wiki locally