Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Close the Gate! Exploring Laravel's Authorization Layer

Close the Gate! Exploring Laravel's Authorization Layer

Authorization (controlling access via permissions) is often confused with Authentication (identifying a user/request). Yet it's not as complicated as it may seem! In this talk we will dig into the many features provided by Laravel's Authorization layer (gates and policies) to control access. Along the way we will identify some tips to make things easier, look at performance and code maintenance, as well as things to consider for more advanced or complex apps. We will also cover several popular packages which offer advanced functionality, and why/when to use them.

Chris Brown

August 29, 2019
Tweet

More Decks by Chris Brown

Other Decks in Programming

Transcript

  1. Close the Gate!

    Using Laravel's
    Authorization Layer
    @DrByteZC
    @drbyte
    drbyte.dev
    Chris Brown
    1
    @drbytezc
    drbyte.dev
    Who Am I?
    • Freelance developer 15+ yrs drbyte.dev
    • Cofounder Zen Cart e-commerce platform
    • Package Maintainer for 

    Spatie Laravel-Permissions package
    • Active contributor to Laravel Valet package
    • Public Speaking Coach: PickyPresenter.com
    2
    @drbytezc
    drbyte.dev
    The Problem: What I Needed
    • Contributors can edit/delete their own content.
    • Super-Admins can do (almost) anything.
    • Nobody can upvote/downvote an article more than once,
    even administrators and super-admins.
    • Users shouldn't be able to message "ignored" users, nor
    message others who have blocked them.
    3
    @drbytezc
    drbyte.dev
    Objectives
    1. Explore the ways Laravel provides
    Authorization capabilities
    2. Uncover some tips / tricks / traps
    3. Discuss related Packages, and
    when to use them
    4
    Laracon EU 2019 - Close the Gate! Laravel Authorization.key - August 29, 2019

    View Slide

  2. @drbytezc
    drbyte.dev
    Let's Talk About Auth
    5
    @drbytezc
    drbyte.dev
    Auth != Auth
    Authorization is not Authentication
    Authentication is: verifying identity and
    allowing connections to the application

    (login, password, token/session/cookie)
    Authorization is: verifying privileges,

    for controlling access to resources within
    the application.

    (roles, permissions, Access Control/ACL)
    6
    @drbytezc
    drbyte.dev
    Terms
    • Permission = permissions, abilities, privileges
    • Role = (often) "groups of permissions"
    7
    @drbytezc
    drbyte.dev
    Authorization in Laravel
    • Gate - define rules for "abilities"/permissions (closure)
    • Policies - define rules regarding model actions
    • Middleware - route/controller rules to allow/deny
    • Validation - check authorization before processing inputs
    (Controller calls to Gate, Policy, Form Request)
    • View - @can, @cannot, @canany, @guest, etc
    8
    Laracon EU 2019 - Close the Gate! Laravel Authorization.key - August 29, 2019

    View Slide

  3. Show Me The Code!
    9
    @drbytezc
    drbyte.dev
    Gate::define
    // AuthServiceProvider
    public function boot()
    {
    Gate::define('edit-settings', function ($user) {
    return $user->isAdmin;
    });
    Gate::define('update-post', function ($user, $post) {
    return $user->id === $post->author_id;
    });
    }
    10
    @drbytezc
    drbyte.dev
    Gate::allows / denies
    class SettingsController extends Controller
    {
    public function edit()
    {
    if (Gate::allows('edit-settings')) {
    return view('settings.edit');
    }
    abort(403, 'Not allowed to edit settings');
    }
    public function update(Request $request, $id)
    {
    if (Gate::denies('edit-settings')) {
    abort(403, 'Not allowed to edit settings');
    }
    // save updates
    }
    }
    11
    @drbytezc
    drbyte.dev
    Gate::allows - forUser()
    class SettingsController extends Controller
    {
    public function edit()
    {
    // $user = User::find(1);
    if (Gate::forUser($user)->allows('edit-settings')) {
    return view('settings.edit');
    }
    abort(403, 'Not allowed to edit settings');
    }
    }
    12
    Laracon EU 2019 - Close the Gate! Laravel Authorization.key - August 29, 2019

    View Slide

  4. @drbytezc
    drbyte.dev
    public function update(Request $request, $id)
    {
    $post == Post::find($id);
    //
    abort_unless($request->user()->can('update-post', $post),
    403, 'Not allowed to update');
    abort_if($request->user()->cannot('update-post', $post),
    403, 'Not allowed to update');
    $post->save();
    }
    $user->can() / cannot()
    13
    @drbytezc
    drbyte.dev
    php artisan make:policy PostPolicy --model=Post
    class PostPolicy
    {
    use HandlesAuthorization;
    public function viewAny(User $user) { }
    public function view(User $user, Post $post) { }
    public function create(User $user) { }
    public function update(User $user, Post $post) { }
    public function delete(User $user, Post $post) { }
    public function restore(User $user, Post $post) { }
    public function forceDelete(User $user, Post $post) { }
    }
    14
    @drbytezc
    drbyte.dev
    Policy Methods (return: boolean)
    class PostPolicy
    {
    use HandlesAuthorization;
    public function view(?User $user, Post $post)
    {
    // everyone can view all visible posts
    return (bool)$post->isVisible;
    }
    public function update(User $user, Post $post)
    {
    // can update their own post
    return $user->id === $post->author_id;
    }
    }
    15
    @drbytezc
    drbyte.dev
    Controller: $this->authorize()
    class PostsController extends Controller
    {
    /**
    * @param $id
    * @throws \Illuminate\Auth\Access\AuthorizationException
    */
    public function edit($id)
    {
    $post = Post::find($id);
    // [email protected]
    $this->authorize('update', $post);
    return view('posts.edit');
    }
    }
    16
    Laracon EU 2019 - Close the Gate! Laravel Authorization.key - August 29, 2019

    View Slide

  5. @drbytezc
    drbyte.dev
    $this->authorize() with extra context
    class PostsController extends Controller
    {
    public function update(Request $request, Post $post)
    {
    $this->authorize('update', [$post, $request->input('category')]);
    // The current user can update the blog post...
    }
    }
    17
    @drbytezc
    drbyte.dev
    Policy Method with extra context
    class PostPolicy
    {
    use HandlesAuthorization;
    public function update(User $user, Post $post, int $category)
    {
    // can update their own post, if it is part of category 3
    return $user->id === $post->author_id
    && $category > 3;
    }
    }
    18
    @drbytezc
    drbyte.dev
    Middleware: 'can:'
    Route::put('/post/{post}', function (Post $post) {
    // The current user may update the post...
    })->middleware('can:update,post');
    19
    @drbytezc
    drbyte.dev
    Blade: @can()

    @can('update-post', $post)
    The edit form could go here.
    @endcan

    20
    Laracon EU 2019 - Close the Gate! Laravel Authorization.key - August 29, 2019

    View Slide

  6. @drbytezc
    drbyte.dev
    Blade: @can family
    @can(ability1)
    @elsecan(ability2)
    @endcan
    @cannot(ability3)
    @elsecannot(ability4)
    @endcannot
    @canany([ability5, ability6])
    @elsecanany([ability7, ability8])
    @endcanany
    21
    @drbytezc
    drbyte.dev
    can() fallback/precedence
    1. Policy Method for the Model
    2. Gate ability by that name
    $user->can('update', $post);
    22
    @drbytezc
    drbyte.dev
    Super-Admins
    23
    @drbytezc
    drbyte.dev
    Gate::before() -- Super-Admins
    // AuthServiceProvider
    public function boot()
    {
    // Super Powers
    // always return true for any Gate 'can' check, if user isSuperAdmin
    Gate::before(function ($user, $ability) {
    return $user->isSuperAdmin ? true : null;
    });
    }
    24
    Laracon EU 2019 - Close the Gate! Laravel Authorization.key - August 29, 2019

    View Slide

  7. @drbytezc
    drbyte.dev
    Policy::before() -- Super-Admins
    class PostPolicy
    {
    use HandlesAuthorization;
    // Super Powers
    public function before($user, $ability)
    {
    if ($user->isSuperAdmin) {
    return true;
    }
    }
    public function update(User $user, Post $post)
    {
    return $user->id === $post->author_id; // eg: update their own post
    }
    }
    25
    @drbytezc
    drbyte.dev
    Super-Admins
    • Policy::before()
    • Gate::before()
    • Gate::after()
    • Impersonation *
    26
    @drbytezc
    drbyte.dev
    Storing Dynamic Permissions in the Database
    class User extends Authenticatable
    {
    public function permissions()
    {
    return $this->belongsToMany(Permission::class);
    }
    public function hasPermissionTo(string $ability)
    {
    $permission = Permission::where('name', $ability);
    if (! $permission) {
    return false;
    }
    return $this->permissions->contains('id', $permission->id);
    }
    }
    27
    @drbytezc
    drbyte.dev
    Registering Permissions from the Database
    // AuthServiceProvider
    public function boot()
    {
    // skip trying to register permissions if not migrated
    if (! Schema::hasTable('permissions')) return;
    // Tip: would probably want to cache this
    $permissions = Permission::all();
    $permissions->map(function ($permission) {
    Gate::define($permission->ability, function ($user) use ($permission) {
    return $user->hasPermissionTo($permission->ability);
    });
    });
    }
    28
    Laracon EU 2019 - Close the Gate! Laravel Authorization.key - August 29, 2019

    View Slide

  8. @drbytezc
    drbyte.dev
    Validation in Form Requests
    class PostRequest extends FormRequest
    {
    public function authorize()
    {
    $post = Post::find($this->route('post'));
    return $post && $this->user()->can('update', $post);
    }
    }
    29
    @drbytezc
    drbyte.dev
    Front-End Clients
    • Authorization and Front-End Security:
    • Usually okay to expose allowed permissions to the front-end.
    • Just don't trust them. :)
    • Always rely on the back-end application to enforce them, 

    particularly for sensitive actions and accessing sensitive info.
    30
    @drbytezc
    drbyte.dev
    Best-Practices
    • Test "abilities", not "roles".

    - ie: use can() everywhere
    • better for code maintenance, gate centralization
    • RESTful Controller Method names
    • policy events tie well with RESTful controller methods,
    which indirectly will make you "think" in terms of
    effective "ability" names in your app
    31
    @drbytezc
    drbyte.dev
    When To Use Packages
    1. First, ensure you've mapped out your permission needs.
    2. Second, build your static rules into your app before using a
    package. (ie: Policies to control model event rules)
    3. Next, do you need Dynamic capability? 

    ie: for users to grant/revoke access to other users?
    4. Consider how many layers of hierarchy you require? 

    Do you need *both* roles and permissions? Or just one?

    Then assess Packages based on your needs.
    5. Less is more. Know what the package does! 

    Does it do too much? Can you understand its API?
    32
    Laracon EU 2019 - Close the Gate! Laravel Authorization.key - August 29, 2019

    View Slide

  9. @drbytezc
    drbyte.dev
    Popular ACL Packages
    Spatie/Laravel-Permission
    Santigarcor/Laratrust
    Zizaco/Entrust
    Silber/Bouncer
    Lab404/Laravel-Impersonate *
    33 34
    @drbytezc
    drbyte.dev
    Review
    • Gate
    • Policy
    • Middleware
    • Validation
    • View
    • Fallbacks
    • Front-End Security
    • Best Practices
    • Super Admins
    • Packages
    https://laravel.com/docs/authorization
    35
    Find me around the conference,
    and let's talk about auth!
    @DrByteZC
    @drbyte
    drbyte.dev
    Chris Brown
    36
    Laracon EU 2019 - Close the Gate! Laravel Authorization.key - August 29, 2019

    View Slide