★ How to handle front-end authorization using Laravel, Inertia and TypeScript
Tags: laravel, php, javascript, inertia
Published: 2020-01-02 23:30:31

---
title: ★ How to handle front-end authorization using Laravel, Inertia and TypeScript
published: true
date: 2019-07-01 09:35:03 UTC
tags: laravel, php, javascript, inertia
canonical_url: https://freek.dev/1391-how-to-handle-front-end-authorization-using-laravel-inertia-and-typescript
---

Recently [Jeffrey Way](https://twitter.com/jeffrey_way) published [a video titled "Frontend Authorization Brainstorming"](https://laracasts.com/series/whatcha-working-on/episodes/36) on [Laracasts](https://laracasts.com). In that video, he shows three ways of passing authorization results to the front-end.

Currently I'm working on a big project that uses [Inertia](https://reinink.ca/articles/introducing-inertia-js), [React](https://reactjs.org/) and [TypeScript](https://www.typescriptlang.org/). In this blog post, I won't cover those things in detail, but I'd like to show you have we, using those technologies, pass authorization (and routes) to the front-end.

## Using policies #

In the app I'm working on the are teams and projects. A team owns a project. A project can also be accessible by guests.

In our app, all of our authorization checks are made using [Laravel Policies](https://laravel.com/docs/master/eloquent-resources). Here's the policy for projects.

```php
namespace App\Domain\Project\Policies;

use App\Domain\Project\Models\Project;
use App\Domain\Team\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class ProjectPolicy
{
    public const ADMINISTER = 'administer';
    public const LEAVE = 'leave';

    use HandlesAuthorization;

    public function administer(User $user, Project $project)
    {
        if (! $user->hasTeamWithProject($project)) {
            return false;
        }

        return $user->isAdminOrOwner($project->team);
    }

    public function leave(User $user, Project $project)
    {
        return $user->isGuestOnProject($project);
    }
}
```

Don't mind the logic in those methods, that's not the focus. Do notice that for every method in our policy there is a constant with the same name. In a small project, you won't do this. But this code is taken from quite a sizeable app, with many policies each having different methods on it. By having a constant on your can do a gate check like this:

```php
Gate::allows(ProjectPolicy::ADMINISTER, $project);
```

Why starting to type `ProjectPolicy` any decent IDE will show you the constants you have on your policy.

 ![](https://freek.dev/uploads/media/auth-frontend/1.png)

Using a constant also has a benefit that changing the name of a policy method becomes easy. Just change the method name and using your IDE perform a rename refactor on the constant. A decent IDE can update all usages of the constant.

## Using resources #

In our app, we're using [Inertia](https://github.com/inertiajs). It's a very cool collection of packages that [Jonathan Reinink](https://twitter.com/reinink) currently is building. If you want to know more about the project, read [this blog post](https://reinink.ca/articles/introducing-inertia-js).

Using Inertia, each page is its own React (or Vue component). So in our app Blade isn't used to render anything. So we can't use serverside logic when rendering our React powered views.

This is what our `ProjectsIndexController` looks like:

```php
namespace App\Http\App\Controllers\Projects;

use App\Http\App\Resources\Project\ProjectResource;
use Inertia\Inertia;

class ProjectsIndexController
{
    public function __invoke()
    {
        $projects = $this->getProjectsForCurrentUser();

        return Inertia::render('projects.index', [
            'projects' => ProjectResource::collection($projects),
        ]);
    }
}
```

The important bit here is that a collection of projects is passed to the `ProjectResource`, which is an [API Resource](https://laravel.com/docs/master/eloquent-resources). An API resource in Laravel is a dedicated class to transform an Eloquent model into an API response. Let's take a look at that `ProjectResource`.

```php
namespace App\Http\App\Resources\Project;

use App\Domain\Project\Policies\ProjectPolicy;
use App\Http\App\Controllers\Projects\Settings\DeleteProjectController;
use App\Http\App\Controllers\Projects\Settings\LeaveProjectController;
use Illuminate\Http\Resources\Json\JsonResource;

class ProjectResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            // ... other attributes redacted for brevity
            'can' => [
                ProjectPolicy::ADMINISTER => auth()->user()->can(ProjectPolicy::ADMINISTER, $this->resource),
                ProjectPolicy::LEAVE => auth()->user()->can(ProjectPolicy::LEAVE, $this->resource)
            ],
            'links' => [
                'edit' => action(EditProjectController::class, $this->resource),
                'delete' => action([DeleteProjectController::class, 'show'], $this->resource),
                'leave' => action([LeaveProjectController::class, 'show'], $this->resource),
            ],
        ];
    }
}
```

In all resources we use the `can` key to put authorization results that are of interest to the front-end. The key of each entry in that array is the name of the policy method, the value is the result of the check: `true` or `false`.

Routes that are of interest to the front-end are put in the `links` key. The front-end can use the routes to render links to the detail screen, the delete endpoint and so on. We can use the `action` helper and the fully qualified class name because we've [removed the default controller namespace](https://freek.dev/1324-simplifying-controllers#controllers-are-better-off-without-a-default-namespace).

## At the front-end #

In this project, we use TypeScript to define custom types. Each API resource has a matching type. Here's the type definition for `Project`:

```ts
declare type Project = {
    id: number;
    name: string;
    can: {
        administer: boolean;
        leave: boolean;
    };
    links: {
        show: string;
        delete: string;
        leave: string;
    };
};
```

Here's the `project.index` React component that renders the list of projects.

```react
import React from 'react';
import Layout from 'app/views/layouts/Layout';

import {can} from "app/util";

type Props = {
    projects: Array<Project>;
};

export default function index({projects}: Props) {
    return (
        <Layout title="Projects">
            <table>
                <th>
                    <td>Name</td>
                    <td> </td>
                </th>
                {projects.map(project => (
                    <tr>
                        <td>{project.name}</td>
                        <td>{can('administer', project) && <a href={project.links.edit}>Edit</a>}</td>
                        <td>{can('leave', project) && <a href={project.links.leave}>Leave</a>}</td>
                    </tr>

                ))}
            </table>
        </Layout>
    );
}
```

Let's take a look at the things that are happening here. Remember those `projects` we passed to `Inertia::render`? Behind the scenes, Inertia will take care that those projects are being passed to the React component above as a `projects` prop. Using TypeScript, we explicitly say that the `projects` prop is an array of `Project` objects.

```ts
type Props = {
    projects: Array<Project>;
};

export default function index({projects}: Props) {

// ...
```

IDEs that support TypeScript can now autocomplete the properties on a `Project` object. So when typing `project.links` the IDE can show us the available links:

 ![](https://freek.dev/uploads/media/auth-frontend/2.png)

Let's turn our attention to the `can` method. It was created by my colleague [Seb](https://sebastiandedeyne.com/). This is its definition:

```ts
export function can<T extends Authorizable>(ability: keyof T['can'] & string, authorizable: T) {
    return authorizable.can[ability];
}
```

This function will check if the `can` property of the object being passed as the second argument contains a key that's being passed as the first argument. With this in place, `can('administer', project)` will return a boolean (the result of the authorization check). If we try to use a non-existing check, the IDE will warn us.

 ![](https://freek.dev/uploads/media/auth-frontend/3.png)
## Closing thoughts #

I hope you enjoyed this walkthrough of how we pass authorization checks (and routes) to the front end. In essence, we add a `can` and `links` entry to the API resource. On the front-end, we use TypeScript to enable autocompletion and error detection.