Example Web Application (Laravel 12)
This repository is the example application.
The vendor/ and node_modules/ directories are intentionally not documented here.
Project Layout (High Level)
app/ Example/ # A01-A10 example code (safe patterns) Http/Middleware/bootstrap/config/public/resources/ docs/ # Markdown docs rendered as views views/routes/storage/tests/
OWASP 2025 A01-A10 (Code Map)
A01: Broken Access Control
Policies + request authorization (public read, restricted writes):
Files:
app/Example/Policies/PostPolicy.php
app/Example/Http/Requests/UpdatePostRequest.php
app/Example/Policies/CommentPolicy.php
app/Example/Http/Requests/StoreCommentRequest.php
// app/Example/Policies/PostPolicy.php// Only authors can create.public function create(User $user): Response{ return (bool) $user->getAttribute('is_author') ? Response::allow() : Response::denyAsNotFound();}
// app/Example/Http/Requests/UpdatePostRequest.phppublic function authorize(): bool{ return $this->user()?->can('update', $this->route('post')) ?? false;}
// app/Example/Policies/CommentPolicy.php// Any authenticated user can create a comment.public function create(User $user): Response{ return $user->id > 0 ? Response::allow() : Response::denyAsNotFound();} // Only the owner (or an admin) can update/delete.public function update(User $user, Comment $comment): Response{ $isAdmin = (bool) $user->getAttribute('is_admin'); return ($isAdmin || $user->id === $comment->user_id) ? Response::allow() : Response::denyAsNotFound();}
// app/Example/Http/Requests/StoreCommentRequest.phppublic function authorize(): bool{ // Requirement: only authenticated users can comment. return $this->user() !== null;} public function rules(): array{ return [ 'body' => ['required', 'string', 'max:2000'], ];}
A02: Security Misconfiguration
Harden session + CORS and keep it environment-driven.
Files:
config/session.php
config/cors.php
// config/session.php'secure' => env('SESSION_SECURE_COOKIE', env('APP_ENV') === 'production'),'http_only' => env('SESSION_HTTP_ONLY', true),'same_site' => env('SESSION_SAME_SITE', 'lax'),
// config/cors.php'allowed_origins' => array_values(array_filter(array_map( trim(...), explode(',', (string) env('CORS_ALLOWED_ORIGINS', '')),))),'supports_credentials' => (bool) env('CORS_SUPPORTS_CREDENTIALS', false),
Recommended .env baseline (production):
APP_ENV=productionAPP_DEBUG=false SESSION_SECURE_COOKIE=trueSESSION_HTTP_ONLY=trueSESSION_SAME_SITE=lax # Example: lock CORS to your real frontend originsCORS_ALLOWED_ORIGINS=https://app.example.com
A03: Software Supply Chain Failures
Pin dependencies and audit them in CI.
Files:
composer.lock
composer.json (scripts)
Commands:
composer auditcomposer outdated
A04: Cryptographic Failures
Use Hash for passwords and Crypt only for decryptable values, with safe key rotation.
Files:
app/Example/Crypto/LaravelCipher.php
// app/Example/Crypto/LaravelCipher.phpreturn Crypt::encryptString($plain);
Recommended .env key rotation pattern:
APP_KEY=base64:NEW_KEY_HEREAPP_PREVIOUS_KEYS=base64:OLD_KEY_1,base64:OLD_KEY_2
A05: Injection
Never let user input become SQL identifiers (columns/order), raw SQL, or shell commands.
Files:
app/Example/Query/PostIndexQuery.php
$allowedSorts = ['created_at', 'title'];$sort = $request->string('sort')->toString();$sort = in_array($sort, $allowedSorts, true) ? $sort : 'created_at'; $direction = strtolower($request->string('direction')->toString());$direction = in_array($direction, ['asc', 'desc'], true) ? $direction : 'desc'; $query->orderBy($sort, $direction);
A06: Insecure Design
Critical workflows must enforce invariants first: authorization, state transition, audit.
Files:
app/Example/Actions/PublishPostAction.php
use Illuminate\Support\Facades\Gate; Gate::authorize('update', $post);abort_unless((string) $post->getAttribute('status') === 'draft', 422); $post->forceFill(['status' => 'published'])->save();
A07: Authentication Failures
Use Laravel auth patterns (session regeneration on login, invalidate on logout, Sanctum abilities for API tokens).
This docs site itself is not an auth product, but the standard Laravel patterns apply.
References:
resources/docs/en/Vulnerabilities/AuthenticationFailures.md
A08: Software or Data Integrity Failures
Never trust client-submitted ownership (e.g. user_id on comments).
Files:
app/Example/Actions/CreateCommentAction.php
Gate::authorize('create', Comment::class); return Comment::query()->create([ 'post_id' => $post->id, 'user_id' => $user->id, 'body' => $body,]);
A09: Security Logging and Alerting Failures
Centralize request context and emit normalized security events.
Files:
app/Http/Middleware/SecurityLogContext.php
config/logging.php
// app/Http/Middleware/SecurityLogContext.phpLog::withContext([ 'correlation_id' => $correlationId, 'user_id' => $request->user()?->id, 'ip' => $request->ip(), 'user_agent' => $request->userAgent(),]);
// config/logging.php'security' => [ 'driver' => 'stack', 'channels' => explode(',', (string) env('SECURITY_LOG_CHANNELS', 'daily')),],
A10: Mishandling of Exceptional Conditions
For JSON clients in production, return safe generic errors; report internally.
Files:
bootstrap/app.php
app/Example/Http/Controllers/CommentModerationController.php
// bootstrap/app.php$exceptions->render(function (Throwable $throwable, Request $request) { if (! $request->expectsJson() || ! app()->isProduction()) return null; if ($throwable instanceof ValidationException) return null; if ($throwable instanceof HttpExceptionInterface) return null; report($throwable); return response()->json(['message' => 'Server error.'], 500);});
// app/Example/Http/Controllers/CommentModerationController.phptry { $isSpam = $spamDetectionService->isSpam($request->string('text')->toString());} catch (SpamServiceTimeout $e) { report($e); return response()->json(['message' => 'Temporary outage.'], 503);}