API Token Authentication in Laravel 5.2 & 5.3

By JacobBennett

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.

Created 1 year ago | Updated 1 month ago

Comments (30)

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?

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

@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

Good to know! Thanks :)

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

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

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.

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

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

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

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

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.

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

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?

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?

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

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"