Skip Content

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.php
public 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.php
public 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=production
APP_DEBUG=false
 
SESSION_SECURE_COOKIE=true
SESSION_HTTP_ONLY=true
SESSION_SAME_SITE=lax
 
# Example: lock CORS to your real frontend origins
CORS_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 audit
composer 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.php
return Crypt::encryptString($plain);

Recommended .env key rotation pattern:

APP_KEY=base64:NEW_KEY_HERE
APP_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.php
Log::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.php
try {
$isSpam = $spamDetectionService->isSpam($request->string('text')->toString());
} catch (SpamServiceTimeout $e) {
report($e);
return response()->json(['message' => 'Temporary outage.'], 503);
}