Site icon MIND Development & Design, LLC

How To Add Custom API Authentication To Laravel 5.2

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('support@website.com');
            $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.

Exit mobile version