Security is paramount in web applications. A single authentication vulnerability can compromise your entire system and user data. Laravel provides robust authentication features out of the box, but you need to implement them correctly. At ZIRA Software, we've secured hundreds of Laravel applications—here's our comprehensive guide.
Laravel's Built-in Authentication
Laravel includes authentication scaffolding that handles:
- User registration
- Login/logout
- Password hashing
- Remember me functionality
- Password reset via email
- CSRF protection
Generate authentication scaffolding:
php artisan auth:make
This creates controllers, views, and routes for a complete authentication system.
User Model
Laravel's default User model (app/models/User.php):
<?php
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
class User extends Eloquent implements UserInterface, RemindableInterface {
protected $table = 'users';
protected $hidden = ['password'];
protected $fillable = ['email', 'password'];
/**
* Get the unique identifier for the user.
*/
public function getAuthIdentifier()
{
return $this->getKey();
}
/**
* Get the password for the user.
*/
public function getAuthPassword()
{
return $this->password;
}
/**
* Get the e-mail address for password reminders.
*/
public function getReminderEmail()
{
return $this->email;
}
// Automatically hash passwords
public function setPasswordAttribute($password)
{
$this->attributes['password'] = Hash::make($password);
}
}
Database Schema
Create users table migration:
<?php
Schema::create('users', function($table) {
$table->increments('id');
$table->string('email')->unique();
$table->string('password', 60);
$table->string('remember_token', 100)->nullable();
$table->timestamps();
});
// Optional: Add more fields
Schema::table('users', function($table) {
$table->string('name');
$table->string('role')->default('user');
$table->timestamp('last_login')->nullable();
$table->boolean('is_active')->default(true);
});
Registration
Registration Form
<!-- app/views/auth/register.blade.php -->
<form method="POST" action="{{ route('register') }}">
{{ csrf_field() }}
<div class="form-group {{ $errors->has('name') ? 'has-error' : '' }}">
<label for="name">Name</label>
<input type="text" name="name" id="name" class="form-control"
value="{{ old('name') }}" required autofocus>
@if ($errors->has('name'))
<span class="help-block">{{ $errors->first('name') }}</span>
@endif
</div>
<div class="form-group {{ $errors->has('email') ? 'has-error' : '' }}">
<label for="email">Email</label>
<input type="email" name="email" id="email" class="form-control"
value="{{ old('email') }}" required>
@if ($errors->has('email'))
<span class="help-block">{{ $errors->first('email') }}</span>
@endif
</div>
<div class="form-group {{ $errors->has('password') ? 'has-error' : '' }}">
<label for="password">Password</label>
<input type="password" name="password" id="password" class="form-control" required>
@if ($errors->has('password'))
<span class="help-block">{{ $errors->first('password') }}</span>
@endif
</div>
<div class="form-group">
<label for="password_confirmation">Confirm Password</label>
<input type="password" name="password_confirmation" id="password_confirmation"
class="form-control" required>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
Registration Controller
<?php
class AuthController extends BaseController {
public function register()
{
return View::make('auth.register');
}
public function store()
{
$validator = Validator::make(Input::all(), [
'name' => 'required|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:6|confirmed',
]);
if ($validator->fails()) {
return Redirect::back()
->withErrors($validator)
->withInput();
}
$user = User::create([
'name' => Input::get('name'),
'email' => Input::get('email'),
'password' => Input::get('password'), // Auto-hashed by mutator
]);
// Log the user in
Auth::login($user);
// Send welcome email
Mail::send('emails.welcome', ['user' => $user], function($message) use ($user) {
$message->to($user->email)->subject('Welcome!');
});
return Redirect::route('dashboard')
->with('success', 'Registration successful!');
}
}
Login
Login Form
<!-- app/views/auth/login.blade.php -->
<form method="POST" action="{{ route('login') }}">
{{ csrf_field() }}
<div class="form-group {{ $errors->has('email') ? 'has-error' : '' }}">
<label for="email">Email</label>
<input type="email" name="email" id="email" class="form-control"
value="{{ old('email') }}" required autofocus>
@if ($errors->has('email'))
<span class="help-block">{{ $errors->first('email') }}</span>
@endif
</div>
<div class="form-group {{ $errors->has('password') ? 'has-error' : '' }}">
<label for="password">Password</label>
<input type="password" name="password" id="password" class="form-control" required>
@if ($errors->has('password'))
<span class="help-block">{{ $errors->first('password') }}</span>
@endif
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="remember"> Remember Me
</label>
</div>
<button type="submit" class="btn btn-primary">Login</button>
<a href="{{ route('password.request') }}" class="btn btn-link">Forgot Password?</a>
</form>
Login Controller
public function login()
{
return View::make('auth.login');
}
public function authenticate()
{
$credentials = [
'email' => Input::get('email'),
'password' => Input::get('password'),
];
$remember = Input::get('remember', false);
if (Auth::attempt($credentials, $remember)) {
// Update last login
Auth::user()->update(['last_login' => Carbon::now()]);
return Redirect::intended('dashboard')
->with('success', 'Welcome back!');
}
return Redirect::back()
->withInput(Input::only('email'))
->withErrors(['email' => 'Invalid credentials']);
}
public function logout()
{
Auth::logout();
return Redirect::route('home')
->with('success', 'You have been logged out');
}
Protecting Routes
Authentication Filter
// app/filters.php
Route::filter('auth', function() {
if (Auth::guest()) {
return Redirect::guest('login');
}
});
Route::filter('auth.admin', function() {
if (Auth::guest() || Auth::user()->role !== 'admin') {
return Redirect::route('home')
->with('error', 'Unauthorized access');
}
});
Apply to Routes
// app/routes.php
// Single route
Route::get('dashboard', ['before' => 'auth', 'uses' => 'DashboardController@index']);
// Group of routes
Route::group(['before' => 'auth'], function() {
Route::get('dashboard', 'DashboardController@index');
Route::resource('posts', 'PostController');
Route::resource('comments', 'CommentController');
});
// Admin routes
Route::group(['before' => 'auth.admin', 'prefix' => 'admin'], function() {
Route::get('users', 'AdminController@users');
Route::get('settings', 'AdminController@settings');
});
Password Reset
Database Table
Schema::create('password_reminders', function($table) {
$table->string('email')->index();
$table->string('token')->index();
$table->timestamp('created_at');
});
Request Reset Form
<!-- app/views/auth/password-request.blade.php -->
<form method="POST" action="{{ route('password.email') }}">
{{ csrf_field() }}
<div class="form-group">
<label for="email">Email</label>
<input type="email" name="email" id="email" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary">Send Reset Link</button>
</form>
Controller
public function requestReset()
{
return View::make('auth.password-request');
}
public function sendResetLink()
{
$credentials = ['email' => Input::get('email')];
$response = Password::remind($credentials, function($message) {
$message->subject('Password Reset');
});
switch ($response) {
case Password::INVALID_USER:
return Redirect::back()->withErrors(['email' => 'Email not found']);
case Password::REMINDER_SENT:
return Redirect::back()->with('success', 'Reset link sent to your email');
}
}
public function showReset($token)
{
return View::make('auth.password-reset')->with('token', $token);
}
public function reset()
{
$credentials = [
'email' => Input::get('email'),
'password' => Input::get('password'),
'password_confirmation' => Input::get('password_confirmation'),
'token' => Input::get('token'),
];
$response = Password::reset($credentials, function($user, $password) {
$user->password = $password;
$user->save();
});
switch ($response) {
case Password::PASSWORD_RESET:
return Redirect::route('login')
->with('success', 'Password reset successful');
default:
return Redirect::back()
->withErrors(['email' => Lang::get($response)]);
}
}
Security Best Practices
1. Password Requirements
'password' => 'required|min:8|regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/',
This requires:
- Minimum 8 characters
- At least one uppercase letter
- At least one lowercase letter
- At least one number
2. Rate Limiting
Prevent brute force attacks:
Route::filter('throttle', function() {
$key = 'login_attempts_' . Request::ip();
$attempts = Cache::get($key, 0);
if ($attempts >= 5) {
return Response::make('Too many login attempts', 429);
}
Cache::put($key, $attempts + 1, 15); // 15 minutes
});
Route::post('login', ['before' => 'throttle', 'uses' => 'AuthController@authenticate']);
3. CSRF Protection
Laravel includes CSRF protection by default. Always use {{ csrf_field() }} in forms:
<form method="POST">
{{ csrf_field() }}
<!-- form fields -->
</form>
4. SQL Injection Protection
Always use Eloquent or parameter binding:
// GOOD - Protected
User::where('email', $email)->first();
DB::select('SELECT * FROM users WHERE email = ?', [$email]);
// BAD - Vulnerable
DB::select("SELECT * FROM users WHERE email = '$email'");
5. XSS Protection
Blade templates escape output by default:
{{ $user->name }} <!-- Escaped automatically -->
{!! $html !!} <!-- Unescaped - use carefully -->
6. Session Security
Configure sessions securely (app/config/session.php):
return [
'secure' => true, // HTTPS only
'http_only' => true, // No JavaScript access
'lifetime' => 120, // 2 hours
];
7. Two-Factor Authentication (2FA)
For high-security applications, implement 2FA:
// After successful login
if ($user->two_factor_enabled) {
Session::put('2fa_user_id', $user->id);
return Redirect::route('2fa.verify');
}
Checking Authentication in Views
@if (Auth::check())
<p>Welcome, {{ Auth::user()->name }}!</p>
<a href="{{ route('logout') }}">Logout</a>
@else
<a href="{{ route('login') }}">Login</a>
<a href="{{ route('register') }}">Register</a>
@endif
@if (Auth::check() && Auth::user()->isAdmin())
<a href="{{ route('admin.dashboard') }}">Admin Panel</a>
@endif
Testing Authentication
class AuthTest extends TestCase {
public function testUserCanRegister()
{
$response = $this->call('POST', 'register', [
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => 'password123',
'password_confirmation' => 'password123',
]);
$this->assertRedirectedToRoute('dashboard');
$this->assertTrue(Auth::check());
}
public function testUserCanLogin()
{
$user = User::create([
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => 'password123',
]);
$response = $this->call('POST', 'login', [
'email' => 'john@example.com',
'password' => 'password123',
]);
$this->assertTrue(Auth::check());
$this->assertEquals($user->id, Auth::id());
}
}
Conclusion
Laravel's authentication system provides a solid foundation for securing your application. By following these best practices—strong passwords, rate limiting, CSRF protection, and proper session management—you'll create a secure authentication system that protects your users and application.
Security is an ongoing process. Stay updated on Laravel security announcements and regularly audit your authentication implementation.
Need a secure Laravel application? Contact ZIRA Software to discuss security audits, authentication implementation, and best practices for your project.