Skip to content

Instantly share code, notes, and snippets.

@JacobBennett
Last active March 26, 2021 02:47
Show Gist options
  • Star 31 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save JacobBennett/090369fbab0b31130b51 to your computer and use it in GitHub Desktop.
Save JacobBennett/090369fbab0b31130b51 to your computer and use it in GitHub Desktop.
API Token Authentication in Laravel 5.2 & 5.3

I recently had the need to write a small url shortening application. I am aware that this problem has been solved quite a few times before, but what is being a developer if not reinventing the wheel just for the heck of it? Custom CMS anyone?

Knowing that this was going to be a tiny RESTful API and also knowing that Laravel 5.2 had API rate limiting built in, I was eager to give it a try. Taylor Otwell being Taylor Otwell shipped 5.2 with the rate limiting defaults set up out of the box and I had my application building out short url's in a matter of minutes. The problem for me came when I wanted to start associating those short urls with a user.

Typically my applications have a UI and authentication is done through a simple login page. Obviously for a RESTful API, having a login page isn't ideal. Instead, my hope was to have users append an api_token to the end of their query string and use that to authenticate their request. I was happy to find that 5.2 also ships with a TokenGuard(link) class that allows you to do exactly that, but the documentation on getting it to work was a bit thin, so here you go.

Set up Token based Authenticaton

1. Add an api_token

The first think you need to do is to add an api_token column to your users table. If you are just starting your application you can likely get away with modifying the user migration that ships with Laravel to include your new column.

	// add this to your users_table migration
	$table->string('api_token', 60)->unique();

Note: Be sure to generate and assign an api_token to new users. Something like str_random(60) should be sufficient.

2. Wrap your routes

Second, we need to make sure that any routes that will be using Token Authentication are being protected by the auth:api middleware. Use the following route group as an example of what your routes might look like.

	Route::group(['prefix' => 'api/v1', 'middleware' => 'auth:api'], function () {
    	Route::post('/short', 'UrlMapperController@store');
	});

Note: Typically when protecting routes from unauthenticated users, we use the auth middleware, but by appending :api to the end we are telling Laravel that we want to use the driver for the api guard which is set up in the config/auth.php and is defaulted to token.

At this point, any routes wrapped with your auth:api middleware are only accessible to those that visit the route with a valid api_token in their request.

3. Getting the User

To get the authenticated user for this API request, use the following snippet:

	Auth::guard('api')->user();

Just like when we called the middleware, we have to let Laravel know that we want the api guard instead of the default web guard.

Extras

In the App\Http\Middleware\Authenticate middleware, you might want to change the following lines:

Update: This has been merged into 5.2. Check out the current Authenticate Middleware here

	// Original
	    if ($request->ajax()) {
	        return response('Unauthorized.', 401);
	    } else {
	        return redirect()->guest('login');
	    }
	    
	// Updated
		if ($request->ajax() || $request->wantsJson()) {
	        return response('Unauthorized.', 401);
	    } else {
	        return redirect()->guest('login');
	    }

This will return a 401 status code to unauthorized API requests instead of redirect it to a login page.

Lastly, if you are planning on primarily using the TokenGuard to authenticate requests, change the default guard in config/auth.php to be api instead of web. That should prevent you from having to tell Laravel to use the api version of the middleware or guard since Laravel will use by default what you have set in config/auth.php.

Wrapping Up

Well there you have it, authenticating users to your api using nothing more than an api_token in the request and an api_token column on your user table. Hopefully this will save you the time it took me digging through the TokenGuard and Issues to figure it out.

published: true
preview: I was happy to find that Laravel 5.2 & 5.3 ships with a TokenGuard class that allows users to authenticate by sending an api_token along with their request, but the documentation on getting it to work is a bit thin, so here you go.
@progmars
Copy link

Not sure if this is any better than good old SessionId.

At least, SessionId is regenerated often (normally, on each log-in, if you have it implemented correctly), so even if someone stole it, it will be usable only for a short period of time.

Such database token, on the contrary, is not regenerated, and even if you encrypt it, it still is the same and being sent to the server and could be intercepted almost the same way as SessionID. Also, it does not offer any benefits - you still have to hit the database on each request. But in comparison, session could be implemented as a Redis cache, fast SSD storage - you name it.

If you really want to use true token mechanism, you have to implement it fully - to create a short term tickets (e.g. JWT) which contain claims about the caller identity, so you can grant access to the API service without hitting the database. All the other in-between solutions for sessionId<->token are redundant and not any better than using SessionId directly.

@meredevelopment
Copy link

meredevelopment commented Sep 22, 2016

Thanks for the article @JacobBennett, it filled a gap in the Laravel docs for me. I'm getting this working with 5.3, and despite the security concerns, and the fact that all new docs seem to point to Passport / OAuth solutions, I really just want this simple token auth for a simple API.

Overall this works for me, I can 'protect' some routes with an 'api_token' in a GET request. However I'm having trouble with the 'Extras' section above. 5.3 doesn't have App\Http\Middleware\Authenticate but does have App\Http\Middleware\RedirectIfAuthenticated and in there things seem to be reversed.

So Instead of this:

public function handle($request, Closure $next, $guard = null)
    {
        if (Auth::guard($guard)->guest()) {
            if ($request->ajax() || $request->wantsJson()) {
                return response('Unauthorized.', 401);
            }
            return redirect()->guest('login');
        }
        return $next($request);
    }

we have this:

public function handle($request, Closure $next, $guard = null)
    {
        if (Auth::guard($guard)->check()) {
            return redirect('/home');
        }

        return $next($request);
    }

and then in App\Exceptions\Handler we have:

protected function unauthenticated($request, AuthenticationException $exception)
    {
        if ($request->expectsJson()) {
            return response()->json(['error' => 'Unauthenticated.'], 401);
        }

        return redirect()->guest('login');
    }

Using the old code results in a redirect loop. Can anyone help me figure out how to get a JSON error returned when 'api_token' is incorrect, and not a redirect to 'login'? It seems that the 'expectsJson()' is always false or something.

EDIT / ANSWER
Turns out I needed to use the 'Accept' header in all requests and set it to 'application/json'.

@bot101
Copy link

bot101 commented Nov 11, 2016

Thanks for this heads up. I already did something similar and had to change from authToken to api_token on the users table and in code.

@skygdi
Copy link

skygdi commented Nov 14, 2016

@progmars I don't agree with you . because that token could be regenerate after login like session behavior. and as a database token , you could be flexible than session in expiration , remove/change it for an Interruption.
added, this token mechanism could be use at APP or the third part application through Internet which use a not standard HTTP protocol.

@skygdi
Copy link

skygdi commented Nov 14, 2016

@meredevelopment I think you should deal those requests from api_token with an individual middleware instead of the original one. and then echo a Json message instead of redirect.

@morganzysman
Copy link

Hello, Thank you for your article, it works great with users. I want to be able to do this using an other table called clients.

Could you specify the steps needed.

Thanks in advance
Morgan,

@andrew-za
Copy link

Adding api_token to the header in Vueify files:
http://stackoverflow.com/a/41714231/3273588

@pdesign
Copy link

pdesign commented Jan 24, 2017

@Sojic @JacobBennett yea, i am pulling my hair off... how to have multiple access and refresh tokens for a user... also should there be device token on the same table to be able to send notifications to the related device

@pdesign
Copy link

pdesign commented Jan 24, 2017

@Sojic @JacobBennett yea, i am pulling my hair off... how to have multiple access and refresh tokens for a user... also should there be device token on the same table to be able to send notifications to the related device

@skobak
Copy link

skobak commented Jan 25, 2017

to @jonwhittlestone
I faced the same problem , my solution is little changes Middlewar/Authenticate.php:

use Closure;
use Illuminate\Contracts\Auth\Guard;

class Authenticate {

/**
 * The Guard implementation.
 *
 * @var Guard
 */
protected $auth;

/**
 * Create a new filter instance.
 *
 * @param  Guard  $auth
 * @return void
 */
public function __construct(Guard $auth)
{
	$this->auth = $auth;
}

/**
 * Handle an incoming request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Closure  $next
 * @return mixed
 */
public function handle($request, Closure $next, $guard = null)
{
	if (\Auth::guard($guard)->guest()) {
		if ($request->ajax() || $request->wantsJson()) {
			return response('Unauthorized.', 401);
		}
		return redirect()->guest('auth/login');
	}
	return $next($request);

}

@skobak
Copy link

skobak commented Jan 25, 2017

to avoid Unauthorized response, just change Middleware/Authenticate.php
public function handle($request, Closure $next, $guard = null)
{
if (\Auth::guard($guard)->guest()) {
if ($request->ajax() || $request->wantsJson()) {
return response('Unauthorized.', 401);
}
return redirect()->guest('auth/login');
}
return $next($request);
}

@cedch
Copy link

cedch commented Jan 29, 2017

Very useful.
But is there any news that would allow the encryption of the token ?

@samjadps
Copy link

samjadps commented Mar 8, 2017

Thank you sir the concept is clear

@mpwrx
Copy link

mpwrx commented Apr 17, 2017

laravel 5.4 remove authenticate.php :|

@MevlutOzdemir
Copy link

Thanks!!!!

@rabb-bit
Copy link

thank you so much! simple and well explained!

@technoknol
Copy link

everyone explains about passing api_token but how to Login via API and get api_token, no one has written about this.

@sanchitmahajan
Copy link

I am using Passport..My User table contains only id, email and password fields.... When user logged in, the user gets api_token.. When user post some data such as title and description to server. in this what the steps i have to follow, how i can use api_token.. what will be database table structure... I have other table contains title and description fields.. pls help..thank you.

@azazqadir
Copy link

Why not use Laravel Passport for authenticating your Laravel Rest API. https://www.cloudways.com/blog/rest-api-laravel-passport-authentication/

@azazqadir
Copy link

Why not use Laravel Passport for authenticating your Laravel Rest API. https://www.cloudways.com/blog/rest-api-laravel-passport-authentication/

@azazqadir
Copy link

Since this REST API is based on older version of Laravel, I think there is a need to mention how devs can create a REST API on latest version of Laravel, which is currently 5.5 and use a new package for Authentication. We now have Laravel Passport that can be used for easy and quick Laravel REST API authentication (https://www.cloudways.com/blog/rest-api-laravel-passport-authentication/ ). This package provides a full OAuth2 server implementation.

@azazqadir
Copy link

Since this REST API is based on older version of Laravel, I think there is a need to mention how devs can create a REST API on latest version of Laravel, which is currently 5.5 and use a new package for Authentication. We now have Laravel Passport that can be used for easy and quick Laravel REST API authentication (https://www.cloudways.com/blog/rest-api-laravel-passport-authentication/ ). This package provides a full OAuth2 server implementation.

@Milanchhatralia
Copy link

How can we authorize user using Auth::guard('api')->user(); ...
I mean how to check email and password??
Or if it is Using token than how to compare the tokens??

@hosembafer
Copy link

How can I pass email and password of user and get api_token?

@hosembafer
Copy link

How I can pass email and password of user to get api_token?
Do I need write my own route for that?

@azazqadir
Copy link

It is better to use Laravel passport for this. Laravel passport automatically generates api token in Laravel. You just have to install and configure it with your application.

@azazqadir
Copy link

It is better to use Laravel passport for this. Laravel passport automatically generates api token in Laravel. You just have to install and configure it with your application.

@kotofenum
Copy link

Sir, you are my hero for today. I've been so confused with Laravel's passport, but what you explained here is exactly what I was looking for a several days.
Thank you!

@alfredomtzrmz
Copy link

but how do you generate the token and "log in"? i mean how you validate the user credentials and then return the token?

@alfredomtzrmz
Copy link

but how you generate the token and "log in"? i mean how you validate the user credentials

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment