---
title: "Build and Secure a Laravel API"
description: "Learn how to build and secure an API with Laravel."
authors:
  - name: "Holly Guevara"
    url: "https://auth0.com/blog/authors/holly-guevara/"
date: "May 13, 2021"
category: "Developers,Tutorial,Laravel"
tags: ["laravel", "authorization", "tutorial"]
url: "https://auth0.com/blog/build-and-secure-laravel-api/"
---

# Build and Secure a Laravel API

## Getting Started

In this tutorial, you'll learn how to create a simple Laravel API and add authorization to it using [Auth0](https://auth0.com). You can find the final code in this [GitHub repository](https://github.com/auth0-blog/laravel-api-auth).

> 👋 If you already have a Laravel API that you want to secure, you can go ahead and skip to the ["Secure your Laravel API" section](#Secure-your-Laravel-API).

### Prerequisites

This tutorial uses the latest version of Laravel at the time of writing (`v8`). I will assume you have some basic knowledge of Laravel. If you're new to Laravel, [Build a Laravel CRUD Application with Authentication](https://auth0.com/blog/build-a-laravel-6-app-with-authentication/) may be a better primer for you!

You'll also need the following:

- [Composer](https://getcomposer.org/)
- PHP >= `7.3`
- A [free Auth0 account](https://a0.to/blog_signup)

### What you'll build

You'll be building a simple API with a single `/comment` resource. The API should allow **anyone** to view comments. However, **only authorized users** should be able to create, update, or delete a comment.

**Public endpoints:**

- `GET /comments` &mdash; Return all comments
- `GET /comments/{id}` &mdash; Return the comment with the specified `id`

**Private endpoints:**

- `POST /comments` &mdash; Add a new comment
- `PUT /comments/{id}` &mdash; Update the comment with the specified `id`
- `DELETE /comments/{id}` &mdash; Delete the comment with the specified `id`

## Setting Up Your Laravel Application

### Installation

First, start by creating your new Laravel application. Make sure you have [Composer](https://getcomposer.org/) installed, and then run the following:

```bash
composer create-project laravel/laravel laravel-api-auth
cd laravel-api-auth
php artisan serve
```

You can now view your starter Laravel application at [http://localhost:8000](http://localhost:8000)!

![Laravel starter app](https://images.ctfassets.net/23aumh6u8s0i/2fZ2LNHDCQEb1xcLNzzIyC/8eb2861df002204c3893b0b33db392ab/01_laravel-starter-app.jpg)

> 👩‍💻 Tip: There are several other options for [starting a new Laravel project](https://laravel.com/docs/8.x/installation#your-first-laravel-project). You can now even run your Laravel project with Docker using the brand new [Laravel Sail](https://laravel.com/docs/8.x/sail).

### Sign up for Auth0

Next, you need to <a href="https://a0.to/blog_signup" data-amp-replace="CLIENT_ID" data-amp-addparams="anonId=CLIENT_ID(cid-scope-cookie-fallback-name)">sign up for a free Auth0 account</a> if you don't already have one.

Your free account allows you to easily add authentication and authorization to your applications. You'll also have access to:

- 7,000 free active users and unlimited logins
- [Auth0 Universal Login](https://auth0.com/universal-login) for Web, iOS & Android
- Up to 2 [social identity providers](https://auth0.com/docs/identityproviders) like Google, GitHub, and Twitter
- Unlimited [Serverless Rules](https://auth0.com/docs/rules/current) to customize and extend Auth0's capabilities

You'll go through a short sign-up process where you'll [create your Auth0 tenant](https://auth0.com/docs/get-started/learn-the-basics#account-and-tenants). Once you've finished, leave the dashboard open, as you'll be revisiting it soon.

## Create the Database

For this tutorial, you'll be using [SQLite](https://laravel.com/docs/8.x/database#sqlite-configuration) as the database. In your terminal, run:

```bash
touch database/database.sqlite
```

This will create the `.sqlite` file at `database/database.sqlite`. Next, open up `.env` and find the section that specifies the database. Update it as follows:

```bash
DB_CONNECTION=sqlite
DB_DATABASE=/absolute/path/to/database.sqlite
```

> Note: You need to use the absolute path to the `database.sqlite` file you just created as the value for `DB_DATABASE`. You can usually get this by right-clicking the file in your code editor and clicking "Copy path".

## Models, Migrations, Factories, and Seeding

Next, you need to set up the Comment model, migration, factory, and seeder. You can do this all with one command, but let's go over each individually.

> 👩‍💻 Tip: To create a model, factory, migration, seeder, and controller all with a single Artisan command: `php artisan make:model Comment -mfsc`

### Create Comment model

In your terminal, run the following:

```bash
php artisan make:model Comment
```

Open up the model file that was created at `app/Models/Comment.php` and replace it with the following:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
  use HasFactory;

  protected $fillable = [
    'name',
    'text'
  ];
}
```

In Laravel, the model name is written in singular camel-case. This way, it can be automatically matched up to its corresponding database table, which is assumed to be singular snake-case.

In other words, the `Comment` model corresponds to the `comments` table in the database.

A comment in this API is very simple. It will have the following attributes:

- `id` &mdash; Primary key
- `created_at` &mdash; Comment creation date
- `updated_at` &mdash; Comment updated date
- `name` &mdash; Name of the commenter
- `text` &mdash; Comment text

You'll notice that only `name` and `text` are listed as fillable, with no mention of the other attributes. This is because Laravel assumes that `id`, `created_at`, and `updated_at` exist in your table and will automatically update them when a model is created.

> 👩‍💻 Tip: If you don't want an auto-incrementing primary key, set `public $incrementing = false;`. If you don't want automatic timestamps, set `public $timestamps = false;`.

### Create Comment migration

Next, it's time to create the migration. In your terminal, run the following:

```bash
php artisan make:migration create_comments_table
```

This will create a new file in the `database/migrations` directory. The filename will start with the timestamp of when the file was created, followed by `create_comments_table.php`. When you run your migrations, they'll be run based on the timestamp from earliest to most recent.

Open the file up and replace it with the following:

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateCommentsTable extends Migration
{
  /**
    * Run the migrations.
    *
    * @return void
    */
  public function up()
  {
    Schema::create('comments', function (Blueprint $table) {
      $table->id();
      $table->timestamps(); // created_at and updated_at
      $table->string('name');
      $table->string('text');
    });
  }

  /**
    * Reverse the migrations.
    *
    * @return void
    */
  public function down()
  {
    Schema::dropIfExists('comments');
  }
}
```

Here, you're using the `Schema` facade with the `create` method to create the `comments` table in the database. You're then able to define the table columns using the schema builder's [column methods](https://laravel.com/docs/8.x/migrations#creating-columns). In this case, you're creating the `id`, `created_at`, `updated_at`, `name`, and `text` columns. The last two will get a type of `string`.

Now, run the migration in your terminal with:

```bash
php artisan migrate
```

### Comment seeder

When you're testing your application, it's helpful to have some mock data in your database. This is where the seeder comes into play. By creating a seeder, you can easily add mock data to your database with a single command.

In a large application, you can create separate seeder files for every model by running `php artisan make:seeder CommentSeeder`. For our purposes, putting the seeder in the main `DatabaseSeeder.php` file is fine.

Open up `/database/seeders/DatabaseSeeder.php` and update it as follows:

```php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use App\Models\Comment;

class DatabaseSeeder extends Seeder
{
  /**
    * Seed the application's database.
    *
    * @return void
    */
  public function run()
  {
    Comment::factory()
      ->times(3)
      ->create();
  }
}
```

When you run the seeder, the `run` method will execute, which is where the mock data is created. Instead of manually creating mock data for every table, you can use Eloquent model factories to define the guidelines for this mock data. Let's define the factory now.

### Create Comment factory

Create your Comment factory by running the following:

```php
php artisan make:factory CommentFactory
```

Open up the newly generated factory file in `database/factories/CommentFactory.php` and replace it with the following:

```php
<?php

namespace Database\Factories;

use App\Models\Comment;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

class CommentFactory extends Factory
{
  /**
    * The name of the factory's corresponding model.
    *
    * @var string
    */
  protected $model = Comment::class;

  /**
    * Define the model's default state.
    *
    * @return array
    */
  public function definition()
  {
    return [
      'name' => $this->faker->name,
      'text' => $this->faker->text()
    ];
  }
}
```

In the `definition` method, you're returning the two attributes, `name` and `text`, with their mock data. The mock data is generated using the [PHP Faker library](https://github.com/FakerPHP/Faker).

Back in the `DatabaseSeeder.php` file, you have this `run` method:

```php
public function run()
{
  Comment::factory()
    ->times(3)
    ->create();
}
```

Now, when you run the seeder, it will run this Comment model factory three times, thus creating three entries in the `comments` table.

Let's test it out now by running the following:

```bash
php artisan db:seed
```

To confirm that it was created, you can use [Tinker](https://laravel.com/docs/8.x/artisan#tinker), which allows you to interact with your Laravel application from the command line.

In your terminal, run:

```bash
php artisan tinker
```

This will open up Tinker. Next, enter:

```bash
App\Models\Comment::all()
```

This will return the Eloquent collection with existing comments:

```php
Illuminate\Database\Eloquent\Collection {
  all: [
    App\Models\Comment {#4262
      id: "1",
      created_at: "2020-12-08 19:57:33",
      updated_at: "2020-12-08 19:57:33",
      name: "Eleonore Kohler",
      text: "Dolor rerum saepe rerum pariatur. Corporis eos unde eveniet itaque aut omnis voluptas.",
    },
    App\Models\Comment {#4263
      id: "2",
      created_at: "2020-12-08 20:01:32",
      updated_at: "2020-12-08 20:01:32",
      name: "Mr. Alfonso Schmidt",
      text: "Iste dolores reiciendis eius dolorem dolorem qui et mollitia. Vel deserunt ea deleniti ipsam fugiat. Velit assumenda odio ipsum qui nisi voluptatem molestiae.",
    },
    App\Models\Comment {#4264
         id: "3",
         created_at: "2020-12-08 21:56:52",
         updated_at: "2020-12-08 21:56:52",
         name: "Ms. Destany Kozey DDS",
         text: "Rem reprehenderit voluptas quasi est ea. Quibusdam accusamus et dolores porro veritatis quo eos. Et beatae et voluptatem voluptatem aperiam dolores fugit.",
       },
     ],
  ]
}
```

You can exit Tinker by typing in `exit`.

Now that all the heavy lifting is done, let's set up the API!

## Create the Comment Controller

First, you need to create the Comment controller. In your terminal, run the following:

```bash
php artisan make:controller API/CommentController --resource
```

This will create a new file at `app/Http/Controllers/API/CommentController.php`, complete with all of the methods you'll need.

Open this up now and update it as follows:

```php
<?php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\Models\Comment;
use Illuminate\Http\Request;

class CommentController extends Controller
{
  /**
    * Display a listing of the resource.
    *
    * @return \Illuminate\Http\Response
    */
  public function index()
  {
    $comments = Comment::all();
    return response()->json($comments);
  }

  /**
    * Store a newly created resource in storage.
    *
    * @param  \Illuminate\Http\Request  $request
    * @return \Illuminate\Http\Response
    */
  public function store(Request $request)
  {
    $request->validate([
      'name' => 'required|max:255',
      'text' => 'required'
    ]);

    $newComment = new Comment([
      'name' => $request->get('name'),
      'text' => $request->get('text')
    ]);

    $newComment->save();

    return response()->json($newComment);
  }

  /**
    * Display the specified resource.
    *
    * @param  int  $id
    * @return \Illuminate\Http\Response
    */
  public function show($id)
  {
    $comment = Comment::findOrFail($id);
    return response()->json($comment);
  }

  /**
    * Update the specified resource in storage.
    *
    * @param  \Illuminate\Http\Request  $request
    * @param  int  $id
    * @return \Illuminate\Http\Response
    */
  public function update(Request $request, $id)
  {
    $comment = Comment::findOrFail($id);

    $request->validate([
      'name' => 'required|max:255',
      'text' => 'required'
    ]);

    $comment->name = $request->get('name');
    $comment->text = $request->get('text');

    $comment->save();

    return response()->json($comment);
  }

  /**
    * Remove the specified resource from storage.
    *
    * @param  int  $id
    * @return \Illuminate\Http\Response
    */
  public function destroy($id)
  {
    $comment = Comment::findOrFail($id);
    $comment->delete();

    return response()->json($comment::all());
  }
}
```

Let's go over each method in this controller.

**`index()` method:**

```php
public function index()
{
  $comments = Comment::all();
  return response()->json($comments);
}
```

This will be used to get all comments. You're using the Eloquent `all()` method on the Comment model, which will return all results in the `comments` table. Then, you're returning the comments as a JSON response.

**`store()` method:**

```php
public function store(Request $request)
{
  $request->validate([
    'name' => 'required|max:255',
    'text' => 'required'
  ]);

  $newComment = new Comment([
    'name' => $request->get('name'),
    'text' => $request->get('text')
  ]);

  $newComment->save();

  return response()->json($newComment);
}
```

The `store()` method is used to create a new comment. You can use the `validate()` method to set validation rules for incoming requests. For this example, you're just setting `name` and `text` as required and limiting the `name` field to 255 characters. If the validation fails, the error will be automatically [sent back to the view](https://laravel.com/docs/8.x/validation#quick-displaying-the-validation-errors).

If the validation passes, a new comment is created and saved to the database. The new comment is then returned as a JSON response.

**`show(id)` method:**

```php
public function show($id)
{
  $comment = Comment::findOrFail($id);
  return response()->json($comment);
}
```

The `show()` method is used to get a single comment. It accepts the `id` of the requested comment and then uses Eloquent's `findOrFail()` method to find the comment in the database.

**`update(id)` method:**

```php
public function update(Request $request, $id)
{
  $comment = Comment::findOrFail($id);

  $request->validate([
    'name' => 'required|max:255',
    'text' => 'required'
  ]);

  $comment->name = $request->get('name');
  $comment->text = $request->get('text');

  $comment->save();

  return response()->json($comment);
}
```

The `update()` method is similar to `store()`, but instead of creating a new comment, you first grab the existing requested comment with `findOrFail($id)`. You then validate the new request, update the existing comment if the request is valid, save it in the database, and return the updated comment.

**`destroy(id)` method:**

```php
public function destroy($id)
{
  $comment = Comment::findOrFail($id);
  $comment->delete();

  return response()->json($Comment::all());
}
```

Finally, you have the `destroy()` method. This takes the `id` of the comment that is being deleted, finds the comment, deletes it, and returns all comments so that you can verify that the deleted comment no longer exists.

## Create the API Routes

Finally, let's set up the API routes.

Open up `routes/api.php` and replace everything with the following:

```php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\API\CommentController;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::apiResource('comments', CommentController::class);
```

Using `Route::apiResource()` here creates all of the routes needed to create, show, update, and delete comments.

You can see all of the routes in your application by running:

```bash
php artisan route:list
```

![PHP route list](https://images.ctfassets.net/23aumh6u8s0i/1sBoyK5xwMYeEUXGCOL7Tq/2483d1132baa2b2a0625ed89571c6273/02_laravel-route-list.jpg)

You'll also notice that everything is going in the `api.php` file instead of the commonly used `web.php` file. By placing the API routes in the `api.php` file, you get the following:

- An `/api` prefix, e.g., `yourdomain.com/api/comments`
- Throttle middleware is automatically applied
- No automatic session state and CSRF protection, like you would get with `routes/web.php`

Next, let's test out the API to make sure everything works.

## Testing your API

You can confirm that your API works correctly by using a tool such as [Postman](https://www.postman.com/) or testing from your terminal with cURL. I'll show the cURL commands first and then the Postman instructions at the end of the section if you prefer using that approach.

As a reminder, here are the API endpoints that were created:

- `GET https://localhost:8000/api/comments` &mdash; Return all comments
- `GET https://localhost:8000/api/comments/{id}` &mdash; Return the comment with the specified `id`
- `POST https://localhost:8000/api/comments` &mdash; Add a new comment
- `PUT https://localhost:8000/api/comments/{id}` &mdash; Update the comment with the specified `id`
- `DELETE https://localhost:8000/api/comments/{id}` &mdash; Delete the comment with the specified `id`

### cURL commands

**Get all comments**:

```bash
curl http://localhost:8000/api/comments -i
```

**Response:**

```bash
[
    {
        "id": 1,
        "created_at": "2021-03-03T21:31:36.000000Z",
        "updated_at": "2021-03-03T21:31:36.000000Z",
        "name": "Dee Sporer",
        "text": "Quod nam qui modi sunt. Illum ut delectus minima nisi corporis assumenda voluptate. Quaerat voluptate omnis vitae ex."
    },
    {
        "id": 2,
        "created_at": "2021-03-03T21:31:36.000000Z",
        "updated_at": "2021-03-03T21:31:36.000000Z",
        "name": "Lurline Schinner",
        "text": "Culpa blanditiis nihil quisquam minima nam. Rerum placeat vero corrupti nobis reiciendis accusantium necessitatibus. Maiores vel nesciunt voluptatum ex. Eveniet quidem aut neque."
    },
    {
        "id": 3,
        "created_at": "2021-03-03T21:31:36.000000Z",
        "updated_at": "2021-03-03T21:31:36.000000Z",
        "name": "Pete McClure MD",
        "text": "Amet impedit mollitia consectetur eveniet natus. Cumque perspiciatis debitis ratione est. Dolor expedita vitae recusandae ut quos earum."
    }
]
```

**Get a single comment**:

```bash
curl http://localhost:8000/api/comments/1 -i
```

**Response:**

```bash
{
    "id": 1,
    "created_at": "2021-03-03T21:31:36.000000Z",
    "updated_at": "2021-03-03T21:31:36.000000Z",
    "name": "Dee Sporer",
    "text": "Quod nam qui modi sunt. Illum ut delectus minima nisi corporis assumenda voluptate. Quaerat voluptate omnis vitae ex."
}
```

**Create a comment**:

```bash
curl -X POST -H 'Content-Type: application/json' -d '{
    "name": "Holly",
    "text": "You are the best!"
}' http://localhost:8000/api/comments -i
```

**Response:**

```bash
{
  "name": "Holly",
  "text": "You are the best!",
  "updated_at": "2021-03-03T23:16:31.000000Z",
  "created_at": "2021-03-03T23:16:31.000000Z",
  "id": 4
}
```

**Update a comment**:

```bash
curl -X PUT -H 'Content-Type: application/json' -d '{
  "name": "Holly",
  "text": "My updated comment"
}' http://localhost:8000/api/comments/4 -i
```

**Response:**

```bash
{
  "id": 4,
  "created_at": "2021-03-05T13:41:17.000000Z",
  "updated_at": "2021-03-05T13:51:40.000000Z",
  "name": "Holly",
  "text": "My updated comment"
}
```

**Delete a comment**:

```bash
curl -X DELETE http://localhost:8000/api/comments/4 -i
```

**Response:**

```bash
[
  {
    "id": 1,
    "created_at": "2021-03-05T14:27:49.000000Z",
    "updated_at": "2021-03-05T14:27:49.000000Z",
    "name": "Burdette Medhurst",
    "text": "Hic dolores minus illum modi consectetur. Qui est officia distinctio voluptatem at aut non. Sapiente perspiciatis nesciunt ea eos. Perferendis dolorem harum rerum magnam totam."
  },
  {
    "id": 2,
    "created_at": "2021-03-05T14:27:49.000000Z",
    "updated_at": "2021-03-05T14:27:49.000000Z",
    "name": "Keaton Wuckert",
    "text": "Id distinctio rerum tenetur et. Molestias excepturi aut labore enim. Vitae aperiam aut odit sed. Qui est sit cupiditate ut placeat sint nam."
  },
  {
    "id": 3,
    "created_at": "2021-03-05T14:27:49.000000Z",
    "updated_at": "2021-03-05T14:27:49.000000Z",
    "name": "Violet Kulas",
    "text": "Nostrum adipisci tempora tempore ad et. Quia minus odit rem. Non nihil maiores quidem eum molestiae voluptatem cum."
  }
]
```

### Postman instructions

Open up Postman and create a new `GET` request at `https://localhost:8000/api/comments`. Click "Send", and you should see the JSON response of all the comments in your application.

![Postman GET comments request](https://images.ctfassets.net/23aumh6u8s0i/4pGlP02vWRoNzG7CPG0Yqx/495905de5bf4c7f928c01a5b1afb8ff5/03_postman-get-comments.jpg)

You can test getting a single comment with `https://localhost:8000/api/comments/1`.

To test creating a new comment, change the request to a `POST` request and type in `http://localhost:8000/api/comments/` as the request URL. Next, click on "Body" and enter in `name` and `text` as the keys with any string as the value. Click "Send" to create the new comment, and you'll see the new comment returned below.

![Postman create comment](https://images.ctfassets.net/23aumh6u8s0i/TWtKcLl5lRK6SL97iaRVj/f392019215bddeff3157608c0440469a/04_postman-create-comment.jpg)

Just to confirm, switch back to a `GET` request and get all the comments again. You should see your new comment now listed.

You can also easily update and delete comments using this same method, but make sure you change the HTTP verb to `PUT` or `DELETE`, respectively.

## Secure your Laravel API

💃 You finally have a fully functional API!

However, we can't celebrate _just yet_. If you recall, one of the application constraints was that some of these API endpoints should be **private**. Currently, anyone can perform any operation on your API. Let's fix that.
![](https://images.ctfassets.net/23aumh6u8s0i/9V1ogdM5qf7fKZE9xt0G6/396f24da326e4ef25bdef1023dff1134/05_create-auth0-api.jpg)

### Configure Auth0

You're going to be using Auth0 to protect the private endpoints. If you haven't already, <a href="https://a0.to/blog_signup" data-amp-replace="CLIENT_ID" data-amp-addparams="anonId=CLIENT_ID(cid-scope-cookie-fallback-name)">sign up for a free account</a> now.

Once in your dashboard, you need to register your Laravel API with Auth0.

- Click on "Applications" > "APIs" in the left sidebar.
- Click the "Create API" button.
- Enter a "Name" and "Identifier" for your API. You can name it anything you want.

> Note: For Identifier, we recommend using a URL. This doesn't have to be a publicly available URL, and Auth0 will never call it.

- Leave Signing Algorithm as is.
- Click "Create".

![Laravel Auth0 API](https://images.ctfassets.net/23aumh6u8s0i/9V1ogdM5qf7fKZE9xt0G6/396f24da326e4ef25bdef1023dff1134/05_create-auth0-api.jpg)

Keep this tab open, as you'll need to grab some values from it soon.

### Install dependencies

Once you have your API set up, switch back to your terminal and run:

```bash
composer require auth0/login
```

This will install the [Auth0 Laravel plugin](https://github.com/auth0/laravel-auth0).

Next, you need to generate the configuration file. To do this, run:

```bash
php artisan vendor:publish --provider "Auth0\Login\LoginServiceProvider"
```

This will generate a configuration file at `config/laravel-auth0.php`. Open it up now, and you should see these values interspersed throughout:

```php
return [
  // ...
  'domain'        => env('AUTH0_DOMAIN'),
  'client_id'     => env('AUTH0_CLIENT_ID'),
  'client_secret' => env('AUTH0_CLIENT_SECRET'),
  'redirect_uri'  => env( 'APP_URL' ).'/auth0/callback',
  'authorized_issuers' => [ 'https://'.env('AUTH0_DOMAIN').'/' ],
  // 'api_identifier' => '',
  'supported_algs' => [ 'RS256' ],
  // ...
];
```

Find `api_identifier`, uncomment it, and replace it with:

```php
'api_identifier' => env('API_IDENTIFIER'),
```

Finally, you need to update your `.env` file to include some of these values. Paste the following into `.env`.

```php
AUTH0_DOMAIN=your-domain.auth0.com
API_IDENTIFIER=https://your-api.com
AUTH0_CLIENT_ID=
```

Now let's fill them in. Back in the Auth0 dashboard:

- Go to the Laravel API you just registered
- Click on the "Quick Start" tab
- Click on "PHP"
- You'll see two values there: `valid_audiences` and `authorized_iss`
- Copy the value for `valid_audiences` and paste it in as the value for `API_IDENTIFIER`
- Copy the value for `authorized_iss` and paste it in as the value for `AUTH0_DOMAIN`.
- Click on the "Machine to machine applications" tab and find the Test Application that was created for your API. Copy the Client ID and paste it in as the value for `AUTH0_CLIENT_ID`.

> ❗ Important: Omit the `https://` portion of for `AUTH0_DOMAIN`. For example, you'll paste in `your-domain.auth0.com` instead of `https://your-domain.auth0.com`.

Here is some clarification about what each of these `.env` values mapped to the `config/laravel-auth0.php` values do:

- `authorized_issuers` &mdash; An array of allowed token issuers, which is just your tenant URL
- `api_identifier` &mdash; The identifier for your API registered with Auth0
- `supported_algs` &mdash; The signing algorithm used by the API

### Create the middleware

Next, you need to create a middleware that will check for the existence and validity of the bearer token when making a request to a private endpoint.

To create the middleware, run the following:

```bash
php artisan make:middleware CheckJWT
```

This will create a new file at `app/Http/Middleware/CheckJWT.php`. Open it up and replace it with the following:

```php
<?php
// app/Http/Middleware/CheckJWT.php

namespace App\Http\Middleware;

use Auth0\Login\Contract\Auth0UserRepository;
use Auth0\SDK\Exception\CoreException;
use Auth0\SDK\Exception\InvalidTokenException;
use Closure;

class CheckJWT
{
  protected $userRepository;

  /**
    * CheckJWT constructor.
    *
    * @param Auth0UserRepository $userRepository
    */
  public function __construct(Auth0UserRepository $userRepository)
  {
    $this->userRepository = $userRepository;
  }

  /**
    * Handle an incoming request.
    *
    * @param  \Illuminate\Http\Request  $request
    * @param  \Closure  $next
    * @return mixed
    */
  public function handle($request, Closure $next)
  {
    $auth0 = app()->make('auth0');

    $accessToken = $request->bearerToken();
    try {
      $tokenInfo = $auth0->decodeJWT($accessToken);
      $user = $this->userRepository->getUserByDecodedJWT($tokenInfo);
      if (!$user) {
        return response()->json(["message" => "Unauthorized user"], 401);
      }

    } catch (InvalidTokenException $e) {
      return response()->json(["message" => $e->getMessage()], 401);
    } catch (CoreException $e) {
      return response()->json(["message" => $e->getMessage()], 401);
    }

    return $next($request);
  }
}
```

When a request is made to a private endpoint, the `handle()` method of this middleware will run. It grabs the bearer token from the request and passes it to the `decodeJWT()` function.

The `decodeJWT()` function will then attempt to decode and verify the token. If the token is valid, the `Auth0JWTUser` will be retrieved. Otherwise, an error is thrown.

Next, you need to register this middleware. Open up `app/Http/Kernel.php` and scroll down to the `$routeMiddleware` array. Add in the following to register the middleware:

```php
protected $routeMiddleware = [
  // ...
  'jwt' => \App\Http\Middleware\CheckJWT::class,
  // ...
];
```

### Apply middleware to routes

Finally, let's use this middleware to protect the API endpoints!

As a reminder, the goal is that the endpoints to get all comments and get a single comment remain public, while the rest require a token to access.

Open up `routes/api.php`. Currently, you have a single `apiResource` route doing everything, but you don't want to apply the middleware to every resource. To fix this, you can break them apart and then only apply middleware to the `store`, `update`, and `destroy` actions:

```php
Route::resource('comments', CommentController::class)->only([
  'index', 'show'
]);

Route::resource('comments', CommentController::class)->only([
  'store', 'update', 'destroy'
])->middleware('jwt');
```

### Testing your protected routes

Let's test it one more time to make sure that comments can't be created, updated, or destroyed without an access token.

But first, try to make a new comment from your command line without a token:

```bash
curl -X POST -H 'Content-Type: application/json' -d '{
  "name": "Lucy",
  "text": "An authorized comment"
}' http://localhost:8000/api/comments -i
```

This should return a `401` Unauthorized status code.

Now, let's try it again, but this time with an access token.

Back in the [Auth0 dashboard](https://manage.auth0.com/dashboard), click on the Laravel API that you created earlier, and then click on the "Test" tab. Under "Response", you'll see an access token that has been generated for you to test your API.

Click the copy symbol to copy that token.

Modify the previous cURL command with the following, but make sure you first replace `YOUR_ACCESS_TOKEN_HERE` with the test token from your dashboard:

```bash
curl -X POST -H 'Authorization: Bearer YOUR_ACCESS_TOKEN_HERE' -H 'Content-Type: application/json' -d '{
    "name": "Lucy",
    "text": "An authorized comment"
}' http://localhost:8000/api/comments -i
```

The comment creation should now work! Feel free to test this with updating and deleting comments as well.

Now, if you want to protect any other routes in your API, all you need to do is add the `jwt` middleware!

## Conclusion

And that's it! Just to recap, you've learned how to set up a Laravel API complete with the following:

- SQLite database
- Easy-to-run migrations
- A seeder that uses Faker to create mock data
- Authorization using Auth0

Please let me know if you have any questions in the comments below. Thanks for reading!