Hidden Features of Laravel Authentication

Laravel provides excellent authentication mechanism. Have you ever wondered how it works? Documentation states, that it is very flexible, but does not show flexibility of it.

I will show you some features, that I personally love, and how I managed to find those, so you can start your own journey. Lets begin!

First of all, with php artisan make:auth, you get whole authentication system already configured for most cases! But if you are like me and want to know everything about systems you are building, you must know how it works. Or sacrifice quality of sleep. I love sleep, so.. my choice is obvious.

Default Authentication

Let’s take a look at heart of authentication: AuthController

<?php

namespace App\Http\Controllers\Auth;

use App\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;

class AuthController extends Controller
{
    use AuthenticatesAndRegistersUsers, ThrottlesLogins;

    /**
     * Where to redirect users after login / registration.
     *
     * @var string
     */
    protected $redirectTo = '/';

    /**
     * Create a new authentication controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware($this->guestMiddleware(), ['except' => 'logout']);
    }

    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|max:255',
            'email' => 'required|email|max:255|unique:users',
            'password' => 'required|min:6|confirmed',
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return User
     */
    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
        ]);
    }
}

Since most of you will probably just need to change fields, then you don’t have to dive any deeper. validator method takes care of validation and create method takes care of creation. Cool, right?

Understanding current logic

But, say, we have two different kinds of users: users and administrators. After successful login, we want users to be sent to homepage and administrators to protected area. How the f*** should we do this if we have only two methods?

Think about it for a minute. My first idea was to intercept redirect, so lets check code and try to understand logic that is currently happening.

On top of class, you can see:

use AuthenticatesAndRegistersUsers, ThrottlesLogins;

These are traits, that power controller. Names gently suggest what they do.

ThrottlesLogins trait is responsible for protecting your login form from brute-force attacks. However, we are more interested about AuthenticatesAndRegistersUsers trait. It is pretty clean:

<?php

namespace Illuminate\Foundation\Auth;

trait AuthenticatesAndRegistersUsers
{
    use AuthenticatesUsers, RegistersUsers {
        AuthenticatesUsers::redirectPath insteadof RegistersUsers;
        AuthenticatesUsers::getGuard insteadof RegistersUsers;
    }
}

Since we want to intercept redirection part after successful authentication, lets look at AuthenticatesUsers trait. I found method named handle user was authenticated (do you notice, how readable it is?). Try to understand what it should do:

<?php

namespace Illuminate\Foundation\Auth;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Lang;

trait AuthenticatesUsers
{
    /**
     * Send the response after the user was authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  bool  $throttles
     * @return \Illuminate\Http\Response
     */
    protected function handleUserWasAuthenticated(Request $request, $throttles)
    {
        if ($throttles) {
            $this->clearLoginAttempts($request);
        }

        if (method_exists($this, 'authenticated')) {
            return $this->authenticated($request, Auth::guard($this->getGuard())->user());
        }

        return redirect()->intended($this->redirectPath());
    }
}

Algorithm is pretty easy to understand:

  1. Clear login attempts if needed;
  2. If method authenticated exists, return its result;
  3. If no redirection happened in step 2, we call default redirection.

Wuhu! That’s exact thing we are looking for.

Making it work for us

Lets implement our authenticated method in AuthController.

<?php

namespace App\Http\Controllers\Auth;

class AuthController {
    // ...

    /**
     * Chooses where to redirect user after successful login
     *
     * @param Request $request
     * @param User $user
     * @return \Illuminate\Http\RedirectResponse
     */
    protected function authenticated(Request $request, User $user)
    {
        if ($user->isAdmin()) {
            return redirect()->to('/admin');
        }

        return redirect()->to('/');
    }

    // ...
}

Enjoy!

Enjoy the feel that you understand reasons behind implementation. You can sleep well at night now. 🙂

Do it yourself!

You can use same process to find undocumented features on your own! Just take a look at source code of traits (they can be anywhere and Laravel uses them extensively).

If you want to change behaviour:

  • Try to understand it’s current implementation
  • Provide your code for some tasks
  • Share the experience!