Appearance
Meeting 18 – Basic Security in Laravel
Goal: Give a simple, practical overview of everyday Laravel security. (Advanced / enterprise topics removed.)
1. Core Principles (Easy to Remember)
- Validate all input
- Escape all output
- Protect write requests with CSRF
- Use built‑in auth & hashing (never roll your own)
- Least privilege: only expose what is needed
2. Validation & Mass Assignment
Use Form Requests or ->validate() in controllers.
php
// Controller quick example
$data = $request->validate([
'title' => ['required','string','max:150'],
'body' => ['required','string']
]);
$post = Post::create($data); // safe if model has $fillable// Controller quick example
$data = $request->validate([
'title' => ['required','string','max:150'],
'body' => ['required','string']
]);
$post = Post::create($data); // safe if model has $fillableModel example:
php
class Post extends Model {
protected $fillable = ['title','body']; // prevents overposting
}class Post extends Model {
protected $fillable = ['title','body']; // prevents overposting
}3. XSS (Cross-Site Scripting)
Rule: Output user text with so Blade escapes it.
blade
{{-- Safe --}}
{{ $comment->body }}
{{-- Unsafe (only if you are 100% sure it is sanitized) --}}
{!! $comment->body !!}{{-- Safe --}}
{{ $comment->body }}
{{-- Unsafe (only if you are 100% sure it is sanitized) --}}
{!! $comment->body !!}4. SQL Injection
Laravel automatically binds parameters. Just avoid manual string concatenation.
php
// Good
User::where('email', $email)->first();
// Avoid
DB::select("SELECT * FROM users WHERE email = '$email'");// Good
User::where('email', $email)->first();
// Avoid
DB::select("SELECT * FROM users WHERE email = '$email'");5. CSRF Protection
All POST/PUT/PATCH/DELETE forms need a token (Blade adds it with @csrf).
blade
<form method="POST" action="/profile">
@csrf
<!-- fields -->
</form><form method="POST" action="/profile">
@csrf
<!-- fields -->
</form>API (token / mobile) endpoints typically use routes/api.php and do not use session cookies, so CSRF token not required there.
6. Passwords & Authentication
Laravel uses strong hashing by default (bcrypt / Argon). Always hash via Hash::make().
php
$user->password = Hash::make($request->input('password'));$user->password = Hash::make($request->input('password'));Add basic password rule:
php
Password::min(8); // simple baselinePassword::min(8); // simple baselineUse auth() / middleware: Route::middleware('auth')->get('/dashboard', ...);
Password check example
php
if (Hash::check($request->input('password'), $user->password)) {
// password matches
}if (Hash::check($request->input('password'), $user->password)) {
// password matches
}Model mutator (auto-hash)
php
class User extends Model {
protected function password(): Attribute {
return Attribute::make(
set: fn($value) => $value ? Hash::make($value) : null,
);
}
}class User extends Model {
protected function password(): Attribute {
return Attribute::make(
set: fn($value) => $value ? Hash::make($value) : null,
);
}
}Hash vs Encrypt
| Feature | Hash (bcrypt/Argon: Hash::make) | Encrypt (Crypt::encryptString) |
|---|---|---|
| Direction | One-way | Two-way (reversible) |
| Use case | Passwords / secrets you only verify | Small data you must read back |
| Storage | Store hash only | Store encrypted string |
| Verify | Hash::check($plain,$hash) | Decrypt then compare |
| Never use for | Reversible secret storage | Password hashing |
Reversible encryption example
php
use Illuminate\Support\Facades\Crypt;
$encrypted = Crypt::encryptString('note:123');
$plain = Crypt::decryptString($encrypted); // 'note:123'use Illuminate\Support\Facades\Crypt;
$encrypted = Crypt::encryptString('note:123');
$plain = Crypt::decryptString($encrypted); // 'note:123'Encrypted attribute pattern
php
class ApiKey extends Model {
protected function secret(): Attribute {
return Attribute::make(
get: fn($v) => $v ? Crypt::decryptString($v) : null,
set: fn($v) => $v ? Crypt::encryptString($v) : null,
);
}
}class ApiKey extends Model {
protected function secret(): Attribute {
return Attribute::make(
get: fn($v) => $v ? Crypt::decryptString($v) : null,
set: fn($v) => $v ? Crypt::encryptString($v) : null,
);
}
}7. Authorization (Who Can Do What)
Policies keep logic clean.
php
// In controller
$this->authorize('update', $post);// In controller
$this->authorize('update', $post);Generate: php artisan make:policy PostPolicy --model=Post
8. Sessions & Cookies (Quick Settings)
Check config/session.php:
secure=> true (when using HTTPS)http_only=> true (default)same_site=> 'lax'
9. File Upload Basics
Validate type & size, store with generated name.
php
$path = $request->file('avatar')->store('avatars');$path = $request->file('avatar')->store('avatars');Validation rule:
php
['avatar' => ['required','image','max:2048']]['avatar' => ['required','image','max:2048']]10. Rate Limiting (Simple)
Protect login or API routes:
php
Route::middleware('throttle:60,1')->group(function(){
Route::post('/login', LoginController::class);
});Route::middleware('throttle:60,1')->group(function(){
Route::post('/login', LoginController::class);
});11. Logging Basics
Use logs for security‑relevant events (login failed, password reset sent):
php
Log::warning('login.failed', ['email' => $request->input('email')]);Log::warning('login.failed', ['email' => $request->input('email')]);12. Environment & Secrets
Never commit .env. Check in production:
APP_DEBUG=false- Strong unique
APP_KEY
13. Summary
Focus on basics first: validate input, escape output, protect forms, hash passwords, restrict actions with policies, and keep secrets out of version control. These alone mitigate the most common beginner mistakes.