January 13, 2016 14:44
Laravel 5.2 has much better support for allowing multiple methods of authentication.
For example, you may want to authorize users with a username and password on the website, but with a random token string on the API.
Setting up tokens is simple enough, assuming you have all of the default config. First you need to add a column called api_token
to your users table. It can be any length but the longer it is, the more secure.
Here is an example migration:
Schema::table('users', function (Blueprint $table) {
$table->char('api_token', 60)->nullable()->after('remember_token');
});
Then you need to make sure that the api guard is specified for any API routes. Unfortunately, Laravel is not able to determine this by itself; it simply defaults to the web guard.
Route::group(['middleware' => ['auth:api']], function()
{
// API routes here
});
Finally you just need to pass this api_token
when sending an API request. There are multiple ways you can do this. Check out the TokenGuard class to see where I'm getting this:
- as a GET parameter named
api_token
- as an Authorization header (Authorization: Bearer [api_token])
- or as an auth password
Here is how you would use each of those methods with Guzzle:
$guzzle = new GuzzleHttp\Client();
$guzzle->request($url, ['api_token' => $api_token]);
$guzzle->request($url, [], ['headers' => ['Authorization' => 'Bearer ' . $api_token]]);
$guzzle->request($url, [], ['auth' => [$username, $api_token]]);
The bearer token may take some additional setup. Apache sometimes strips the Authorization token from requests, so you need to add these two lines to your public/.htaccess
file:
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
Finally, of course, you need to generate an api_token for every user that is using the API. Best to do this when creating the user.
Note about upgrades
If you're upgrading from Laravel 5.1 or earlier, chances are you'll need to update some other files.
If you followed the upgrade instructions, you should have already added an "api" guard with a "token" driver to config/auth.php
:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],
You may also need to update your Authenticate
middleware. In file app/Http/Middleware/Authenticate.php
, make sure your handle
method takes a $guard
as an argument and that that guard is used when checking if the user is logged in. The final method should look like this:
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->guest()) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
} else {
return redirect()->guest('auth/login');
}
}
return $next($request);
}
December 10, 2015 11:17
IPv6 addresses should be converted using MySQL's INET6_ATON
function.
To convert them back to their original value, use INET6_NTOA
.
They should be stored as a VARBINARY(16)
.
This function is only available in MySQL 5.6+
In Laravel, if you're tracking visitors you could do something like this:
$ip = \DB::connection()->getPdo()->quote(request()->ip());
$visitor = Visitor::firstOrCreate([
'longip' => \DB::raw("INET6_ATON({$ip})")
]);
November 15, 2015 23:56
CSRF tokens are great for security but there are rare occurrences where the token is invalid when it shouldn't be.
An invalid token is usually a temporary problem* - it's solved immediately on the next page load when their new session is created and token is refreshed. So it's bad UX to simply throw an exception and display a useless page or worse, a nasty error page.
*One case in which it wouldn't be temporary is if sessions aren't working properly at all; for example, if you're using the file session driver but your server is out of disk space or the folder isn't writeable.
A simple solution to this is to catch those exceptions and tell the user to "try again." Now it's just a slight annoyance to your user and not something that will completely stop them in their tracks. Modifying your handler like this takes care of that:
use Illuminate\Session\TokenMismatchException;
class VerifyCsrfToken extends BaseVerifier {
public function handle($request, Closure $next)
{
try {
return parent::handle($request, $next);
} catch (TokenMismatchException $e) {
if ($request->ajax()) {
return response('CSRF error', 500);
}
return redirect()->back()
->withInput(\Input::except('_token'))
->withErrors(['Something went wrong, please try again']);
}
}
}
October 17, 2015 21:29
Update
Laravel now comes with a Relationship morphMap
which takes care of this all in one place. Simply add this to your AppServiceProvider
use Illuminate\Database\Eloquent\Relations\Relation;
public function boot()
{
Relation::morphMap([
'album' => Album::class,
'photo' => Photo::class,
'user' => User::class,
]);
}
You can now use album
, photo
, etc. in your ENUM
s instead of the full class name.
Original Post
Let's say you have a Comment
class that has a morphTo
relationship with many other models. However, you don't want to store the full class name of the relationship in your database because that's a waste of space, and namespaces might change.
First, every model that has comments should define the morphClass
variable. This is just a shortened version of the class name.
class Album {
protected $morphClass = 'album';
}
class Photo {
protected $morphClass = 'photo';
}
Next, the Comment class should define an $entity_types
array which is basically the inverse of what you just did. It maps the short name to the full class name.
class Comment {
protected $entity_types = [
'album' => \App\Album::class,
'photo' => \App\Photo::class,
];
}
Finally, the Comment class should have an accessor for the entity_type
column. This takes the database value ("photo", "album", etc.) and converts it to the full class name ("\App\Photo", "\App\Album", etc.)
/**
* @param string $type short name
* @return string full class name
*/
public function getEntityTypeAttribute($type)
{
if ($type === null) {
return null;
}
$type = strtolower($type);
return array_get($this->entity_types, $type, $type);
}
Ideally, this should be either on your base model, or on a trait. That way the method can be reused on any class that has a morphTo relationship.
October 15, 2015 15:26
By default, Laravel logs users in with their email and password. In some cases you might want them to use a username instead. This is pretty simple, just set the $username
property on the AuthController
class:
class AuthController extends Controller
{
...
protected $username = 'username';
}
The AuthController
uses the AuthenticatesUsers trait, which checks if the $username property is set, and if not, defaults to 'email'
If you want users to have the choice of logging in with email or username, it's a little more complex. You'll have to override the postLogin
method in your controller.
class AuthController extends Controller
{
use AuthenticatesAndRegistersUsers {
AuthenticatesAndRegistersUsers::postLogin as laravelPostLogin;
}
/**
* Login with email or username
*
* @param Request $request
*/
public function postLogin(Request $request)
{
$field = filter_var($request->input('email'), FILTER_VALIDATE_EMAIL) ? 'email' : 'username';
$request->merge([$field => $request->input('email')]);
$this->username = $field;
return self::laravelPostLogin($request);
}
}
To break that code down, first we use the trait, but we want to rename the original postLogin
method so that we can use it later.
Then inside our postLogin
method, we check if the email
field that we're receiving is an email address or not. This assumes that you have some restrictions on usernames, e.g. that they're alphanumeric, or at the very least, don't allow the @
character.
Then it sets the $username
attribute as either 'email' or 'username'.
Finally, it calls the laravelPostLogin
method, which we had reassigned earlier, so that Laravel continues the login as before. That way we don't have to rewrite any of the logic in that original method.
- 1
- 2