10

When we login into our gmail account for the first time or after removing the cache and cookie, we get the window to type a code which is sent to our Mobile.

I am trying to implement this but through email instead of SMS. Below is my approach to implement this.

I am following this link : https://laravel.com/docs/5.2/session

and create a Session table in database. I can also see my browser details in Session Table record. I am not sure if this is the correct approach.

Gmail has provision keep track of multiple browsers. This means if I last time logged in from Firefox and this time from Chrome then I will be asked for code again. Going forward, I will not be asked to fill code for Chrome and Firefox if cache/cookie is not removed.

Can somebody give me any link that explains how to make provision for multiple browsers when it is cache/cookie saved ? so that I can send email for security code

Pankaj
  • 9,749
  • 32
  • 139
  • 283
  • why use gmail for the user agent, you have that in the `$_SERVER` array. http://php.net/manual/en/reserved.variables.server.php `$_SERVER['HTTP_USER_AGENT']` As well as the IP, `$_SERVER['REMOTE_ADDR']` – ArtisticPhoenix Aug 05 '16 at 06:58
  • Many Thanks for the comment. That was just a reference to explain my situation. I am trying to save the state of browser somewhere so that I can check next time whether to send the code or not . – Pankaj Aug 05 '16 at 07:02
  • The logical place to store that is in the session table, then when they attempt to login you can check that users last IP, UserAgent, etc. etc.. Also you could easily create a separate table linked to the user to store the many to one relation ship, one user many user agents / ip addresses. – ArtisticPhoenix Aug 05 '16 at 07:04
  • Personally, I would handle all the data myself, otherwise you rely on an external API for basic functionality ( login ) to work. They change something and your site is broken, it's not like a font we are talking about. – ArtisticPhoenix Aug 05 '16 at 07:08
  • But that is supporting just one browser details. if you login into gmail and then login with Firefox from same IP both time you will be asked for the first time. But next time they will ask for code if cache and cookie is not saved. But in Session Table, we have support for one browser details. – Pankaj Aug 05 '16 at 07:08
  • you can support many, just make an additional table with `userID, userAgent, IP` ( or similar ) and add any new user agents / IP combos in that. Then when they go to login you just check. – ArtisticPhoenix Aug 05 '16 at 07:12
  • Is there any inbuilt package that does this kind of authentication? – Pankaj Aug 05 '16 at 07:13
  • I've never used Laravel, so I couldn't tell you. It's like 3 maybe 4 hours of work though. – ArtisticPhoenix Aug 05 '16 at 07:19

3 Answers3

4

You can achieve this by issuing a extra cookie (lets say browser_cookie) to remember the already authenticated browser.

Implementation:

Create a following table (browser_management) :

token (pk)| user_id (fk) | series_identifier

where:

  • token : hashed form of token issued to the user (using bcrypt or similar algorithm ) (token issued to user, itself, is unguessable randomly generated key from a suitably large space)

  • series_identifier : unguessable randomly generated key from a suitably large space

Whenever, user logs in check for the browser_cookie.

Case 1: User is logging for the first time.

Considering user is logging in for first time browser_cookie won't be present. So, you would send an email with the authentication code.

Once authenticated, generate two random numbers each for token and series_identifier. Store the hashed token and series_identifier in the browser_management table, for the user identified by user_id.

Also, issue the browser_cookie to the user with token and series_identifier.

Case 2: User is re-logging next time.

Now, when same user logs in next time,take the token and find the entry in the browser_management table with the hashed token.

If found, check if the user_id and series_identifier matches.

Case 2.1: Entries matched:

Allow the user to enter the system without the need to re-authenticate the email code.

Generate another token and replace the token in the cookie as well as table with the new one. (This will lower the risk of session hijacking).

Case 2.2: Entries does not match:

Follow the steps for email authentication and notify the user regarding the possible theft.(Like gmail notifying new browser logins).

References:

Update:

Sample code:

Migration:

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class browser_management extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('browser_management', function (Blueprint $table) {
            $table->string('token');
            $table->string('user_id');
            $table->string('series_identifier');            
            $table->timestamps();
            $table->primary('token');
            $table->foreign('user_id')->references('id')->on('users');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('users');
    }
}

Middleware: Create a new middleware

<?php
namespace App\Http\Middleware;

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

class Email_verification
{
    public function handle($request, Closure $next, $guard = null)
    {
        //retrieve $token from the user's cookie
        $token = $request->cookie('browser_cookie');

        //check id token is present
        if($token == null){
            //if token is not present allow the request to the email_verification
            return $next($request);
        }
        else{
            //Retrieve the series_identifier issued to the user
            $series_identifier = Auth::user()
                                    ->series_identifier(Hash::make($token))
                                    ->first()
                                    ->series_identifier;

            //Check if series_identifier matches            
            if($series_identifier != $request->cookie('series_identifier')){
                //if series_identifier does not match allow the request to the email_verification
                return $next($request);
            }
        }

       return redirect('/dashboard'); //replace this with your route for home page
    }
}

Make the middleware's entry in the kernel.php

protected $routeMiddleware = [
        'email_verification' => \App\Http\Middleware\Email_verification::class,
        //your middlewares
];

User Model: Add the following method's to your user model

// method to retrieve series_identifier related to token
public function series_identifier($token){
    return $this->hasMany(Browser_management::class)->where('token',$token);
}

//method to retriev the tokens related to user
public function tokens (){
    return $this->hasMany(Browser_management::class);
}

Browser_management Model: Create a model to represent browser_managements table

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;


class Browser_management extends Model
{
    protected $primaryKey = 'token';
    protected $fillable = array('token','series_identifier');

    public function User(){
        return $this->hasOne('App\Models\User');
    }    
}

Email Verification Methods: Add the following methods to your AuthController for handling the email verification

public function getVerification(Request $request){
    //Create a random string to represent the token to be sent to user via email. 
    //You can use any string as we are going to hash it in our DB
    $token = str_random(16);

    //Generate random string to represent series_identifier
    $series_identifier = str_random(64);

    //Issue cookie to user with the generated series_identifier
    Cookie::queue('series_identifier', $series_identifier,43200,null,null,true,true);

    //Store the hashed token and series_identifier ini DB
    Auth::user()->tokens()->create(['token'=>Hash::make($token)]);

    //Your code to send an email for authentication

    //return the view with form asking for token
    return view('auth.email_verification');
}

public function postVerification(Request $request){
    //Retrieve the series_identifier issued to the user in above method
    $series_identifier = $request->cookie('series_identifier');

    //Retrieve the token associated with the series_identifier
    $token = Auth::user()
                ->tokens()
                ->where('series_identifier',$series_identifier)
                ->first()
                ->value('token');

    //Check if the user's token's hash matches our token entry
    if(Hash::check($request->token,$token)){
        // If token matched, issue the cookie with token id in it. Which we can use in future to authenticate the user
        Cookie::queue('token', $token,43200,null,null,true,true);
        return redirect('dashboard');
    }

    //If token did not match, redirect user bak to the form with error
    return redirect()->back()
                ->with('msg','Tokens did not match');
}

Routes: Add these routes for handling email verification requests. We will also add the email_verification middleware to it.

Route::get('/auth/email_verification',`AuthController@getVerification')->middleware('email_verification');
Route::post('/auth/email_verification',`AuthController@postVerification')->middleware('email_verification');<br/>

Update 2:

Regarding the flow of gmail..
I followed following steps:
1)Log into gmail followed by 2-step verification.
2)Log out
3)Clear cache link
4)Log in again

When I loged in again, after clearing the cache, it did not ask me for 2-step verification.

Though, If you clear the cookies, it will ask for 2-step verification. Reason:
All the user data which identifies the user (here token) is stored in the cookies. And if you clear the cookies, server will have no mechanism to identify the user.

Update 3:

Gmail asking for 2-step verification:
First of all Gmail or any other website is not notified about the clearing of cache
As given here:

The cache is nothing more than a place on your hard disk where the browser keeps things that it downloaded once in case they’re needed again.

Now, cookies are the small text files issued by server to store user related information. As given here

The main purpose of a cookie is to identify users and possibly prepare customized Web pages or to save site login information for you.

So, basically when you clear the cookies in your browser, webserver will not get any user data. So, the user will be considered as guest and will be treated accordingly.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
jaysingkar
  • 4,315
  • 1
  • 18
  • 26
  • I am using two data cards to access internet. Both have their own IP address. Should I take care of IP address also? – Pankaj Aug 15 '16 at 02:02
  • I don't think it is needed, as IP addresses tends to change. That's why I did not include IP address in the table. – jaysingkar Aug 15 '16 at 02:03
  • As you can see in the gmail's example,source of your internet connection doesn't matter in this process. – jaysingkar Aug 15 '16 at 02:04
  • Will I use token is the remember token in user table? – Pankaj Aug 15 '16 at 02:06
  • You can. But, since this is complexity different than user authentication, I would suggest to use different value all together. – jaysingkar Aug 15 '16 at 02:08
  • You can use the same method to generate the token though. Also, keep the `browser_cookie`'s expiry value to low – jaysingkar Aug 15 '16 at 02:09
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/120937/discussion-between-helper-and-jaysingkar). – Pankaj Aug 15 '16 at 02:10
  • Hi, there is one serious issue in your suggestion. If you clear cache of your browser...then you will be send code again. In this scenario, what's the benefit of keeping the records in database ? – Pankaj Aug 15 '16 at 15:44
  • Gmail send the code even if we remove cache and cookies of our browser. Then what's the use of creating records in db ? Do you think it has something to do with client cache? – Pankaj Aug 16 '16 at 03:13
  • We are keeping the records in the database for security reason and not for retrieving the session even after the browser cache is cleared It is not possible for to identify or in this case authenticate the user without the cookies Also, lets say we do not save the data in DB, then how would we identify that the token presented by user during the session belongs to him – jaysingkar Aug 16 '16 at 08:09
  • I understand. Kindly go through these steps. Login into your Gmail Account. First time or if browser's cache is cleared...it will send the code to your Mobile. Next time, it will not send code. But if you clear cache again, it will send code again for security purposes. **Question**: if you clear browser cache...Does it fires an event to Gmail Server to clear the record from DB or Are they maintaining 100s of Millions of User's Session records in their database? Are they inserting/removing the records on cache clearing? – Pankaj Aug 16 '16 at 08:16
1

Create an additional table ( besides the session one )

Something like

UserId | UserAgent | IP

And when they go to login check that against their current values in the $_SERVER array. If it's in there all is good, if not interrupt the login, and send them a link to confirm the new data. You'll probably want to do some kind of ajax on the original login to check when they are logged in, then once that happens do the redirect to where they were going.

Make sense.

As I said in the comments for maintainability I would handle it myself and not use any third party APIs, the data is easy enough to verify. That part is relatively trivial, continuing the login process not so much.

ArtisticPhoenix
  • 21,464
  • 2
  • 24
  • 38
  • I just realized that Gmail ask for the code even if we clear browser cache. In that case, Session table or a new Relational table with Session Table will not make any sense? – Pankaj Aug 05 '16 at 07:19
1

OP, if I understand you clearly, you simply want to understand how to implement the laravel session table so you can have multiple login from same user in the same browser:

Schema::create('sessions', function ($table) {
    $table->string('id')->unique();
    $table->integer('user_id')->nullable();
    $table->string('ip_address', 45)->nullable();
    $table->text('user_agent')->nullable();
    $table->text('payload');
    $table->integer('last_activity');
});

While this question has been answered before here I will add that you can easily achieve this feature in your actual login method without modifying your core files.

To do this, you can follow the following logic

Before login, check manually if request user agent header is same as session user agent in session, i.e.:

public function authenticate()
{
    $data = Input::all();
    $user = User::where('email', '=', $data['email'])->first();
    if($user != null)
    {
         //check if user agent different from session
         if($request->header('User-Agent') != session('user_agent'))
         {
             //do some extra login/password validation check
         }
         if(Auth::attempt($data))
         {
             //here you now may manually update the session ID on db
         }
    }
}

You will have to do substantially more work than this but I hope you get the concept.

Community
  • 1
  • 1
Chibueze Opata
  • 9,856
  • 7
  • 42
  • 65
  • **This is complete wrong answer.** Why? If you login into your Gmail account first time or after clearing the cache or cookie, it will send you code for security. if again you clear cache/cookie then again it will send code. So, Gmail use this concept if you clear client side caching. So, what's the use of creating tables? – Pankaj Aug 19 '16 at 19:30
  • Your session information can either be saved in a table, in memory or file as shown in the laravel session configuration, the only reason why we have a table here is to provide more flexibility. Furthermore, I answered based on an assumptions clearly stated above -- i.e. how do you prompt the user when we no longer recognize their browser. – Chibueze Opata Aug 20 '16 at 17:17