How To Add Custom API Authentication To Laravel 5.2

By everybody , aka mind add custom api authentication

add custom api authentication

Note: This post is based on Laravel 5.2 and has not been updated for newer versions!

Sometimes the default authentication system is not enough. When that happened to us with Laravel, here’s how we added a custom API authentication:

1. Install The Necessary Packages

These overrides require two packages that are not in the default Laravel installation: a better caching system for login throttling and a PHP HTTP client to send HTTP requests to the desired API.

  1. Cache – Redis: https://laravel.com/docs/5.2/redis
  2. HTTP Client – Guzzle: https://github.com/guzzle/guzzle

Bonus – Set up a helper class to wrap RESTful API calls.

2. Override The Auth Routes

The first step to implementing custom authentication is to override the default routes in the routes file generated by Laravel’s Auth::routes(). The routes generated by the function are:

/* Original Routes */
// Authentication Routes...
Route::get('login', 'Auth\AuthController@showLoginForm');
Route::post('login', 'Auth\AuthController@login');
Route::get('logout', 'Auth\AuthController@logout');

// Registration Routes...
Route::get('register', 'Auth\AuthController@showRegistrationForm');
Route::post('register', 'Auth\AuthController@register');

// Password Reset Routes...
Route::get('password/reset/{token?}', 'Auth\PasswordController@showResetForm');
Route::post('password/email', 'Auth\PasswordController@sendResetLinkEmail');
Route::post('password/reset', 'Auth\PasswordController@reset');

Depending on the extent of the authentication API being used, each of these routes can be overwritten. Generally, if an API has authentication endpoints, it will have registration and password reset endpoints as well. Replace each of the authentication routes to point to custom authentication controllers as seen below. This requires three new controllers: CustomAuthController, CustomRegistrationController, and CustomPasswordController.

 

/* Custom Routes */

// Authentication Routes...
Route::get('login', 'Auth\CustomAuthController@showLoginForm');
Route::post('login', 'Auth\CustomAuthController@login');
Route::get('logout', 'Auth\CustomAuthController@logout');

// Registration Routes...
Route::get('register', 'Auth\CustomRegistrationController@showRegistrationForm');
Route::post('register', 'Auth\CustomRegistrationController@register');

// Password Reset Routes...
Route::get('password/reset/{token?}', 'Auth\CustomPasswordController@showResetForm');
Route::post('password/email', 'Auth\CustomPasswordController@sendResetLinkEmail');
Route::post('password/reset', 'Auth\CustomPasswordController@reset');

3. Create Custom Auth Controllers

CustomAuthController.php

The custom authentication controller needs to have three main components:

  1. An index function that shows the login form
  2. An authenticate function that processes the login form
  3. Login throttling to prevent too many login attempts
<?php
namespace App\Http\Controllers;

use App\Helpers\HttpHelper;
use App\Traits\Throttles;
use Exception;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;

class CustomAuthController extends Controller
{
    //implement App\Traits\Throttles;
    use Throttles;

    private $httpHelper;
    /**
     * CustomAuthController constructor.
     */
    public function __construct() {
        //initialize HttpHelper
        $this->httpHelper = new HttpHelper();
    }

    /**
     * Show the main login page
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
     */
    public function showLoginForm() {
        return view("auth.login");
    }
    /**
     * Authenticate against the  API
     * @param AuthenticationRequest $request
     * @return \Illuminate\Http\RedirectResponse
     */
     public function authenticate(AuthenticationRequest $request) {
        //too many failed login attempts
       if ($this->getThrottleValue("login", $this->generateLoginThrottleHash($request)) > 10) {
            return redirect()->back()->with('error', 'Too many failed login attempts.');
        }
        
        //attempt API authentication
        try {   
            $result = $this->httpHelper->post("authenticate", [
                'username' => $request->email, 
                'password' => $request->password
            ]);

            //create user to store in session
            $user = new User();
            /* Set any  user specific fields returned by the api request*/
            $user->email = $request->email;
            $user->field_1 = $result->field_1;
            //..

            //store authenticated and user in session to be checked by authentication middleware
            $request->session()->put('authenticated',true);
            $request->session()->put('user', $user);
        } catch(\GuzzleHttp\Exception\ClientException $e) {
            //track login attempt
            $this->incrementThrottleValue("login", $this->generateLoginThrottleHash($request));

            //remove user and authenticated from session
            $request->session()->forget('authenticated');
            $request->session()->forget('user');
            //redirect back with error
            return redirect()->back()->with('error', 'The credentials do not match our records');
        }
        //login success - redirect to home page
        $this->resetThrottleValue("login", $this->generateLoginThrottleHash($request));
        return redirect()->action("HomeController@home");
    }
    
    /**
     * Log user out
     * @param Request $request 
     * @return type
     */
    public function logout(Request $request) {
        //remove authenticated from session and redirect to login
        $request->session()->forget('authenticated');
        $request->session()->forget('user');
        return redirect()->action("CustomAuthController@showLoginForm");
    }


    
    // Login throttling functions
     
    /**
     * @param AuthenticationRequest $request
     * @return string
     */
    private function generateLoginThrottleHash(AuthenticationRequest $request) {
        return md5($request->email . "_" . $request->getClientIp());
    }
}

Throttles.php

<?php

namespace App\Traits;

use Illuminate\Support\Facades\Cache;

trait Throttles
{
    /**
     * Get the current throttle value
     * @param $throttleName
     * @param $tag
     * @return mixed
     */
    protected function getThrottleValue($throttleName, $tag)
    {
        return Cache::tags("$throttleName.throttle")->get($tag);
    }

    /**
     * Increment the throttle value
     * @param $throttleName
     * @param $tag
     */
    protected function incrementThrottleValue($throttleName, $tag)
    {
        Cache::tags("$throttleName.throttle")->put(
            $tag,
            (int)Cache::tags("$throttleName.throttle")->get($tag)+1,
            2
        );
    }

    /**
     * Reset the throttle value
     * @param $throttleName
     * @param $tag
     */
    protected function resetThrottleValue($throttleName, $tag)
    {
        Cache::tags("$throttleName.throttle")->forget($tag);
    }
}

 

CustomRegistrationController.php

  1. Show registration form
  2. Process registration form
<?php
namespace App\Http\Controllers;

use App\Helpers\HttpHelper;
use Illuminate\Http\Request;

class CustomRegistrationController extends Controller
{
    private $httpHelper;
    /**
     * CustomRegistrationController constructor.
     */
    public function __construct() {
        //initialize HttpHelper
        $this->httpHelper = new HttpHelper();
    }

    /**
     * Show registration form
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
     */
    public function showRegistrationForm() {
        return view("auth.register");
    }

    //
    public function register(Request $request) {
       try {   
            $result = $this->httpHelper->post("register", [
              //insert required registration fields
            ]);

           
        } catch(\GuzzleHttp\Exception\ClientException $e) {
          //return back with errors
        }

        //return to login page after registration
        return redirect('/login');
    }
    
}

CustomPasswordController.php

  1. Password reset link
  2. Send reset email
  3. Process reset email
  4. Password reset form
<?php
namespace App\Http\Controllers;

use App\Helpers\HttpHelper;
use Illuminate\Http\Request;


class CustomPasswordController extends Controller
{
    private $httpHelper;
    /**
     * CustomRegistrationController constructor.
     */
    public function __construct() {
        //initialize HttpHelper
        $this->httpHelper = new HttpHelper();
    }

    /**
     * Show password reset form
     * @return type
     */
    public function showResetForm() {
        return view('auth/password-reset');
    }

    /**
     * Send reset password email
     * @return type
     */
    public function sendResetLinkEmail(Request $request) {
        //get password reset token from api
       try {   
            $result = $this->httpHelper->post("reset-password-token", [
              //insert required password reset fields
            ]); 
        } catch(\GuzzleHttp\Exception\ClientException $e) {
          //return back with errors
        }

        //send password reset email with token from api
        $data = array( 
            'email' => $request->email,
            'token' => $result->token
        );
        Mail::send('auth/emails.password',$data, function($message) use ($data) {
            $message->from('[email protected]');
            $message->to($data['email']);
            $message->subject('Password Reset');
        });

        $request->session()->forget('authenticated');
        $request->session()->forget('user');

        //return success message
        return redirect()->back()->with('success', 'Please check your email to continue');
    }

    public function reset(Request $request) {
        try {   
            $result = $this->httpHelper->post("reset-password", [
              'token' => $request->token,
              'passowrd' => $request->password
            ]); 
        } catch(\GuzzleHttp\Exception\ClientException $e) {
          //return back with errors
        }

        //redirect to login with success message
        return redirect('/login')->with('success', 'Your password has been reset.');
    }
    
}

AuthenticationRequest.php

<?php

namespace App\Http\Requests;

use App\Http\Requests\Request;

class AuthenticationRequest extends Request
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'email' => 'required|email',
            'password' => 'required|string',
        ];
    }
}

 

4. Create Custom Auth Middleware

The final step in overriding Laravel’s authentication is to set up custom authentication middleware. Create a new file App/Http/Authenticate.php. This file will check every request throughout the application.

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;

class Authenticate
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string|null  $guard
     * @return mixed
     */
    public function handle($request, Closure $next, $guard = null)
    {
       // if the session does not have 'authenticated' forget the user and redirect to login
       if ($request->session()->get('authenticated',false) === true)
        {
            return $next($request);
        }
        $request->session()->forget('authenticated');
        $request->session()->forget('user');
        return redirect()->action("CustomAuthController@showLoginForm")->with('error', 'Your session has expired.');

    }
}

A few things to keep in mind are:

  1. To retrieve the authenticated user, replace Auth::user() with$request>session()>get(‘user’);
  2. Set expiration times on your session
  3. Some APIs may not have a password reset functionality, so reset tokens need to be generated and stored manually.

That’s it! Your app will now use API endpoints for authentication.

View Comments

24 responses to “How To Add Custom API Authentication To Laravel 5.2”

    • Laravel Passport is used to make an Auth server. This article is not about that, it’s about authenticating to a third party server that is not using oAuth.

  1. Hello,
    I’ve searched for days and read so many tutorials and stackoverflow answers but your article is what came the closest to what I’ve been trying to do, so thank you.
    Now, I’m a complete newbie here, so excuse me if the following question is too silly: how should I “mark” the routes that are to be available only through authentication? (I was using ->middleware(‘auth’); but that’s not working with this approach… what am I missing?
    Thanks again đŸ™‚

    • Hi!

      After spending a bit of time thinking about it, I’m not 100% sure I’m reading the question correctly…could you give us a better idea of what you’re working with? What’s above is for authenticating a login against a third-party system…if memory serves, we did this as part of a “customer portal” for a CRM that didn’t do customer logins (only company employee logins) natively.

      If all you need is to authenticate within your system, you wouldn’t need to jump through some of these hoops. Either way, please let me know how we can help?

  2. Thank you for publishing this. It is extremely helpful and much appreciated!
    I am having problems with the throttles … there is no example of the App/Traits/Throttles.php file, where I assume the methods are written out. I have, perhaps, missed something obvious, but, I am stuck. Any help would be greatly appreciated.
    k

  3. Hi, I have followed you tutorial and I love it.
    I encountered an issue with a missing class “AuthenticationRequest” which was passed as an argument to the generateLoginThrottleHash method.
    Kindly provide the class

  4. hi, this is exactly what I’m looking for, yet it’s failing for me and all I need is the CustomAuthController. You have the namespace as Controller yet in the routes you call it ‘Auth\Custom…’
    I noticed AuthController is part of 5.2 and below and new version use LoginController… any advice would help (what threw me off was your reference to 5.4 Redis install docs).

    • Sorry for the confusion. You can change the namespace & make sure the authentication controllers are all in app/Http/Controllers/Auth, or change the routes to match.

  5. Hi, thanks for your article, it gave me an idea of how to implement my system.
    However the @guest directive in app.blade.php doesn’t recognize the session and shows the guest navigation (with the possibility to Login or Register) instead of the Name of the user.

    • Hi Andrea, from this example, you’d want to check the session eg $request->session()->get(‘authenticated’) or write new directives.

  6. hey,
    where I may find more details about rules() from AuthenticationRequest

    I need to use login and password not email – would like to know why and how đŸ˜‰

    Good tutorial.

    • For the current version of Laravel (5.8), you can find the list of all available Form Validation rules here.

      I don’t know if that’s what you need, though…what rules() does on a Request controller is validate form input such as “e-mail must be in the format of an email address”, it won’t change which fields are used for the login / reset / etc process itself. Changing which fields get used for a login is relatively easy…edit /app/http/controllers/auth/LoginController and change the contents of the ‘username’ function so that it returns whichever database field you want to authenticate against, and then modify your /resources/views/login.blade.php accordingly.

      Allowing password resets to use fields other than e-mail gets much more involved, and the best information I could find on it (as I haven’t done it in a while myself) dates back to Laravel 5.3, almost 3 years ago. If you’d like to read over it anyway, you can find a pretty decent breakdown via Laracasts here…I just can’t vouch for whether it’ll work in 5.8.

  7. Good Morning,
    I worked with your great example. Works everything fine except… Assume my fault but I’m not able to resolve it.
    After sending request and receiving response I have to save data at session.
    Receiving well known error đŸ™‚
    “Session store not set on request.”

    Any idea what could be wrong? Middleware (Kernel.php) from default installation/
    Best regards,
    Pech

    • May I ask which routes file you’re working with? Laravel uses sessions within the ‘web’ middleware, and to invoke that automatically you’d have to put your routes in /routes/web.php. You might be able to access them by manually by adding ‘web’ to the “middleware” entry of a Route Group in another file, but without spinning up an app and playing with it I can’t be sure if that’ll work, since you’d have manually pass the session token around.

  8. Hi! Thank you for the tutorial. I’m having problems with the flash session in 5.8 version of Laravel. When I redirect to login to show some message, the flash session is empty. Someone knows what’s happening? Thanks!

  9. Hi i have a problem with cache and redis config
    Could you help me. Ive just installed predis with composer and don’t know what to do next.
    And there is error from throttle that cache tags are not supported.
    Laravel 5.8

  10. The item “4. Create Custom Auth Middleware” says “Create a new file App/Http/Authenticate.php.”

    But in the example file, the namespace is: “namespace App\Http\Middleware;”

    Wich one is correct?

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Are You Ready To Do More with Mind?

Click the link below to get started!

Work With Mind