laravel-authentication-made-easy



laravel-authentication-made-easy

0 2


laravel-authentication-made-easy

Slides for "Laravel Authentication and Permissions Made Easy"

On Github stevegrunwell / laravel-authentication-made-easy

Laravel Authentication and Permissions Made Easy

Steve Grunwell@stevegrunwell

Play along at home: stevegrunwell.github.io/laravel-authentication-made-easy

Who am I?

Let's talk login pages…

  • Most apps have 'em
  • They don't change all that much between projects
  • They're slowing you down!

Enter Confide

  • Laravel 4 authentication solution developed by Zizaco (Leroy Merlin)
  • Automatically handles common actions like registration, confirmation, password reminders, user throttling, and login/logout
  • Only takes ~5min to install

Installing Confide

Easiest to do on a fresh installation of Laravel (the User model's usually pretty important)

Full instructions on GitHub:https://github.com/Zizaco/confide

Installing Confide

Register Confide in your composer.json Add Confide to your 'providers' and 'aliases' arrays
  • Adjust relevant config in auth.php and mail.php
Generate migration Update User model Generate default controller + routes Dump Composer's auto-load

Register Confide in your composer.json

   "require": {
     "laravel/framework": "4.2.*",
+    "zizaco/confide": "~4.0@dev"
   },

Then, of course, run:

$ composer update

Add Confide to your 'providers' and 'aliases' arrays

In app/config/app.php:

   'providers' => array(
      ...
      'Illuminate\View\ViewServiceProvider',
      'Illuminate\Workbench\WorkbenchServiceProvider',
+     'Zizaco\Confide\ServiceProvider',
   ),

   'aliases' => array(
     ...
     'Validator'  => 'Illuminate\Support\Facades\Validator',
     'View'       => 'Illuminate\Support\Facades\View',
+    'Confide'    => 'Zizaco\Confide\Facade',
   ),

Adjust relevant config in auth.php and mail.php

  • You'll need to ensure that you've set your desired authentication table names in app/config/auth.php before proceeding
  • Ensure account emails can be sent by configuring app/config/mail.php!

Generate migration

Generate a default users table by running:

# Let Confide generate a migration that creates the users table
$ php artisan confide:migration

# Run any available migrations
$ php artisan migrate

Update User model

use Zizaco\Confide\ConfideUser;
use Zizaco\Confide\ConfideUserInterface;

class User extends Eloquent implements ConfideUserInterface
{
    use ConfideUser;
}

Generate default controller + routes

# Generates UsersController
$ php artisan confide:controller

# Appends default routes to app/routes.php
$ php artisan confide:routes

Dump Composer's auto-load

If you've installed a new package or changed config and things aren't updating, this is usually a good first step:

$ composer dump-autoload

Test it out!

Visit /users/create to see the (very generic) registration page!

Achievement unlocked: authentication

Introducting permissions

Great, so users can register on your app, but what can they do?

Entrust your users

  • Sister package from Zizaco that adds role- and permission-based access control to your application
  • Protect routes, change how data is handled, or determine what should be shown
  • Works with (or without) Confide

Roles v. permissions

Permission A specific action that a user may or may not be able to do in your app (upload photos, edit posts, etc.) Role A collection of permissions (Administrator, Editor, etc.)

If you're familiar with WordPress Roles and Capabilities, this should be familiar!

Installing Entrust

Register Entrust in your composer.json Add Entrust to your 'providers' and 'aliases' arrays Generate migration Create Role and Permission models Update the User model Dump Composer's auto-load

Create Role and Permission models

# app/models/Role.php
<?php

use Zizaco\Entrust\EntrustRole;

class Role extends EntrustRole
{

}

Create Role and Permission models

# app/models/Permission.php
<?php

use Zizaco\Entrust\EntrustPermission;

class Permission extends EntrustPermission
{

}

Update the User model

  <?php

  use Zizaco\Confide\ConfideUser;
  use Zizaco\Confide\ConfideUserInterface;

  class User extends Eloquent implements ConfideUserInterface
  {
-     use ConfideUser;
+     use ConfideUser, HasRole;
  }

Putting them to work

Populating permissions

$assemble = new Permission;
$assemble->name = 'assemble_blocks';
$assemble->display_name = 'Assemble blocks';
$assemble->save();

Populating (more) permissions

$dream = new Permission;
$dream->name = 'dream_big';
$dream->display_name = 'Dream BIG!';
$dream->save();

Creating roles and assigning permissions

$construction_worker = new Role;
$construction_worker->name = 'Average construction worker';
$construction_worker->save();

$construction_worker->perms()->sync([$assemble->id]);

Creating a more privileged role

$master_builder = new Role;
$master_builder->name = 'Master Builder';
$master_builder->save();

$master_builder->perms()->sync([$assemble->id, $dream->id]);

Wait, what?

Average construction worker
  • assemble_blocks
Master Builder
  • assemble_blocks
  • dream_big

Assigning a role to a user

$user = User::where('username', '=', 'emmet')->first();

// Attach a specific ID
$user->roles()->attach($master_builder->id);

// OR...

// attachRole() can take a Role object, array, or ID
$user->attachRole($master_builder);

Remember: a user can have many roles!

Now you have a new Master Builder!

Seeding permissions

  • A database seeder can be versioned and is meant to populate the database
  • Can call seeders directly by class name
    $ php artisan db:seed --class=PermissionSeeder

Permission seeder

class PermissionSeeder extends Seeder
{
  // Track all the permissions you use in your app...
  $permissions = [
    'assemble_blocks' => 'Assemble blocks',
    'dream_big' => 'Dream BIG!'
  ];

  // And the permissions
  $roles = [
    'Average construction worker' => ['assemble_blocks'],
    'Master Builder' => ['assemble_blocks', 'dream_big']
  ];

  ...

}

Permission seeder

class PermissionSeeder extends Seeder
{
  ...

  // Create any new permissions and store their IDs for later
  $permission_ids = Permission::lists('id', 'slug');
  foreach ($permissions as $slug => $name)
  {
    if (! in_array($slug, $permission_ids))
    {
      $perm = new Permission;
      $permission->name = $slug;
      $permission->display_name = $name;
      $permission->save();
      $permission_ids[$slug] = $permission->id;
    }
  }

  ...
}

Permission seeder

class PermissionSeeder extends Seeder
{
  ...

  // Attach permissions to roles and create roles that don't exist yet
  foreach ($roles as $name => $perms)
  {
    $role_permissions = array_intersect_key($perms, $permission_ids);
    $role = Role::where('name', '=', $name)->first();
    if (! $role)
    {
      $role = new Role
      $role->name = $name;
      $role->save();
    }

    $role->perms()->sync($role_permissions);
  }
}

Checking permissions

// Check Roles...
$user->hasRole('Average contruction worker'); // false
$user->hasRole('Master Builder');             // true

// ...or Permissions
$user->can('assemble_blocks');  // true
$user->can('build_spaceship');  // false

Check both with ability()

// Checks if the user has *any* of these
$user->ability(
  ['Master Builder', 'Micro-manager'],  // Roles
  ['assemble_blocks','dream_big']       // Permissions
);
// Use the third argument to check for *all*
// the specified roles + permissions
$user->ability(
  ['Master Builder', 'Micro-manager'],
  ['assemble_blocks','dream_big'],
  ['validate_all' => true]
);

Route filtering

// Restrict the Master Builders section
Entrust::routeNeedsRole('master-builders/*', 'Master Builder');

// Restrict a route to users with a given permission
Entrust::routeNeedsPermission('assembly-line', 'assemble_blocks');

// Only let users who can assemble_blocks AND be_evil in
Entrust::routeNeedsPermission(
  'president-business-tower/*',
  ['assemble_blocks', 'be_evil']
);

Redirect unauthorized users

Instead of just throwing a 403, you can redirect the users:

// Only let users who can assemble_blocks AND be_evil in
Entrust::routeNeedsPermission(
  'president-business-tower/*',
  ['assemble_blocks', 'be_evil'],
  Redirect::to('real-world') // any response object works here
);

Additional tips + advice

Configuring packages

  • Configuration for both Confide and Entrust can be published to app/config/packages/zizaco:
    $ php artisan config:publish zizaco/confide
    $ php artisan config:publish zizaco/entrust
  • Model + table names, views, email notification settings, and more!

Check for permissions instead of roles

  • Rather than assume what permissions each role has, it's usually better to check for permissions in your routes and templates
  • Makes maintenance easier as a new role doesn't automatically mean updating all your permission checks
  • Permissions use slugs ("assemble_blocks") rather than strings that can have spaces ("Assemble blocks")

Role is just another model

  • If you need to, you can totally build CRUD interfaces for managing roles
  • Useful when building CMS-type apps that require flexible permissions

"The Shadow Role"

  • Nice trick for apps that need fine-grained control over access
  • Permissions populated via seeder
  • CRUD interfaces to manage primary roles
  • A hidden, non-public role, unique to each user, automatically created during UsersController@store

"Spiffy Authentication"

Questions?

Steve Grunwellstevegrunwell.com@stevegrunwell

Packages:github.com/Zizaco/confidegithub.com/Zizaco/entrust

Slides:stevegrunwell.github.io/laravel-authentication-made-easy

Meetups

Ohio Laravelwww.meetup.com/Ohio-Laravel/

Columbus PHPwww.meetup.com/phpphp/