Skip to content

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 $fillable

Model 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 baseline
Password::min(8); // simple baseline

Use 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

FeatureHash (bcrypt/Argon: Hash::make)Encrypt (Crypt::encryptString)
DirectionOne-wayTwo-way (reversible)
Use casePasswords / secrets you only verifySmall data you must read back
StorageStore hash onlyStore encrypted string
VerifyHash::check($plain,$hash)Decrypt then compare
Never use forReversible secret storagePassword 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.