Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
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.

Will you sen a pull request to add the $request->wantsJson() check to the shipped middleware?

I think it is a good idea, thanks for this tip. And nice article.

Is there any way to use this api_token authentication, but send the token in request body instead of query parameter?

Owner

JacobBennett commented Jan 22, 2016

@rodrigopedra can do. I'm going to add a PR for the docs as well to make it a little more clear.

Owner

JacobBennett commented Jan 22, 2016

@carc1n0gen yes, if you look at https://github.com/laravel/framework/blob/master/src/Illuminate/Auth/TokenGuard.php#L81-L83 you can see that it uses the input which checks both the get and post for the api_token

juukie commented Jan 22, 2016

Good to know! Thanks :)

juukie commented Jan 22, 2016

@rodrigopedra it has been merged :)

What about if a third party gains access to the api token? It looks like they can then make requests without being "authenticated" in any way.

Owner

JacobBennett commented Jan 24, 2016

@noleafclover614 correct as the api_token is the secret used to authenticate the user. You would want to caution your users to protect this api_token just like they would a password.

izdiwho commented Jan 24, 2016

what about putting it in the header? like how dingo does it with the authorization header. is that possible?

You can post any method example for use with both Auth (session) and Auth (Api) ?

Thank you for this. Been looking for a simple solution to tokens. It's useful if you're building SPAs as well.

Considering laravel does not encrypt this token in any way, is this really safe way to implement authenticated requests? To me it looks like laravel is storing the password in plain text.

Owner

JacobBennett commented Jan 26, 2016

@baileylo this is a great question. Looked at this article for possible solutions but you are correct, currently they are not encrypted.

You have any ideas for how this could be improved? I'm sure @taylorotwell would entertain a PR on it if it was solid.

Owner

JacobBennett commented Jan 26, 2016

@andreliem same here. Had some trivial stuff that was just wide open, but this adds a layer of security that helps me sleep at night 👍

Now things are more happy! What about built in CORS on this new api Gaurd?

Owner

JacobBennett commented Feb 1, 2016

@calebeaires I am not aware of a CORS middleware out of the box with Laravel but you could certainly make one and PR it if you wanted. Could be a great addition. Know I have had to write that a few times in the past.

Owner

JacobBennett commented Feb 10, 2016

@simondepelchin that looks like an interesting way to solve the problem. I can't think of any catches for why this wouldn't work but I'm sure @taylorotwell would have more insight into that. You should submit a PR and see what he says. Looks like an interesting fix.

Looks like token guard is just a simple token solution which is like using a password. TokenGuard is looking for the token in 3 places:

  1. in the URL for parameter ?api_token=XXX
  2. in the header for "Authorization: Bearer XXX". Which is used in JWT, Oauth, etc.
  3. in the header for "Authorization: Basic XXX". Which is Basic HTTP auth where XXX is base64 encoded username:password. The password is used as the token.

This doesn't prevent DB looking on every API call or I am missing something? How is this different than the clients sending "Cookie: somesessionid" in the header and the server looking up the session id in a file or DB? Where as JSON Web tokens decode the token to validate the user without a need to call DB. The token signature is encrypted by the server and can only be decrypted by it.

mtpultz commented Feb 16, 2016

Nice article. How easy would it be to integrate JWT tokens using a custom Guard and Driver? Seems like the JWT-Auth package could really leverage this bit of Laravel's API and make the setup for JWT-Token use a bit simpler.

nicktc commented Mar 10, 2016

@baileylo there is difference between encrypting and hashing. Passwords are hashed (one-way) with the purpose that you are not storing the actual plain password, but only a hashed version of it. Which is good in case your db is compromised for example. In the API token case you can better compare it with the session ID, which is similar like a token. But it's a secure token; only your browser and the server knows about it. Losing the session ID will give someone access to the application you are logged into. I think the same is true for the API token. Only the application and the client knows about this. I would only send it over HTTPS, so it's being send encrypted.

pay2all commented Mar 11, 2016

its redirect to login page when using get method, any solution to display error message??

the wantsJson() part saved my day!! Thank you!

Does the column have to be called api_token? What if you want to call it something else?

malhal commented May 9, 2016

Unless you use middleware 'api' I don't think you will get any throttling at all. To me it doesn't appear to be set up out of the box. I believe you have to create a mapApiRoutes method similar to mapWebRoutes to RouteServiceProvider and call it from map() if Request::is('api/*');

Nice article, but....

I need (I am sure I am not the only one) multiple tokens by user, so the user might create token for each device, and can revoke token for particular device.

I have some problem with Policies. $this->authorize() method throw exception

I have two database tables in MySQL.

User Table
Class Table
I have a working code in Laravel 5.2 on localhost, where I can see Class screen only after authentication. So far everything is working fine.

Now, I have to access the list of class, Add/Update Class functions from Android App.

I am thinking to use Token Based authentication. I saw the User table that Laravel provides when we create a new project. We have remember_token in User Table. I was thinking to use same token for Android and for Website.

Here the problem is : Token will be expired if you logout from the website and if I use same token, then expired token can not be used in android requests even if Android has done the authentication already.

Please suggest the correct way to use Token based authentication

or I should create a new column called api_token in User Table. and then after authentication, I can use same token to send request from Android to Server and for responding from server to Android. Here I have one problem.

Can the token be stolen?

In that case should I update the api_token in db every time I get the request from Android and updated api_token should be passed from server to Android?

raidenz commented May 26, 2016

maybe i can add some point Route::post('/short', 'UrlMapperController@store'); post will need csrf token, if you want to disable it on your link set protected $except = ['api/v1/*'] in VerifyCsrfToken.php

jonwhittlestone commented Jun 9, 2016

Thanks, this is well-written but I am unable to get a response other than 'Unauthorized'

  • I have created the column, api_token in the users table
  • Made sure I'm using the correct api guard and have the middlewareGroup api in App\Http\Kernel
  • Set up a dummy route:
Route::group(['middleware' => 'auth:api'], function () {
    Route::get('data', function () {
        return ['here is some data'];
    });
});
  • removing the middleware parameter, I can see the output ['here is some data']

I must be missing something ....

This is my request

curl -X GET -H "Content-Type: application/json;charset=UTF-8" -H "Accept: application/json, text/plain, */*" -H "Cache-Control: no-cache" -H "Postman-Token: a6d4f21c-67db-4e48-0a66-f2ff7eb461b6" "http://cc.ivxs.uk/data?api_token=f926f2aeebd8b7fe1e8723b47ab741d4"

Hi @JacobBennett, where do we put this code exactly?
Auth::guard('api')->user();

Hejafe commented Jun 16, 2016

How can i inform the username and password to take the token? after take token i get access to other pages?

Added this as a new middleware option to support both API tokens and user logins:

public function handle($request, Closure $next)
{

    $api_token = Input::get('api_token');
    $user = User::where('api_token', $api_token)->first();

    if (!is_null($user)) {
        $this->auth->login($user);
    }

    if ((is_null($user)) || (is_null($api_token))) {
        if ($request->ajax()) {
            return response('Unauthorized.', 401);
        } else {
            return redirect()->guest('auth/login');
        }
    }

    return $next($request);
}

emoran commented Jul 21, 2016

Hi, how can I reset the password of an api user, in this case I would like to provide a reset password for a mobile application. Best

This is awesome. Thank you.
How could I use this approach if the API were remote? I need to pass the remote server the user credentials and then I would be given an access_token.

@jonwhittlestone
Did you add the api_token to the user table?
Are you sure that the token in the request matches the token in the user table?

oliwin commented Sep 4, 2016

Is it reliable default method to use token? Or better to install library? Also, how can I pass token without GET parameter? For example with HEAD field in request?

oliwin commented Sep 7, 2016

How to register a new user using api?

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.

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 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 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 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 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.

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,

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

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 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 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 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 commented Jan 29, 2017

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

samjadps commented Mar 8, 2017

Thank you sir the concept is clear

laravel 5.4 remove authenticate.php :|

Thanks!!!!

thank you so much! simple and well explained!

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

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.

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

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

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.

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.

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??

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

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

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