Successfully implement Auth0 in Laravel 8 APIs with a SPA

Successfully implement Auth0 in Laravel 8 APIs with a SPA

When you have to choose how to start a new project you have many choices if you think about languages, frameworks and authentication.

In our case (we = company where I work) we're going with a classic SPA: Vue + Laravel API. Additionally, we're using Auth0 to authenticate our SPA, because it's a company backoffice and with Auth0 we're creating a kind-of-SSO between all our backoffices.
Sounds cool, right? I mean, there are plenty of tutorials about this for Vue. Indeed. But I found a lack of tutorials for Laravel.

Wait wait, does Auth0 have an official quickstart for Laravel? They have two!? One for classic webapp application and one for "API only"? This is awesome! And there's more? Their guide is for Laravel 6? Well, I'm using Laravel 8, I'm sure it wor--. Yes, that's why we're here.

Installation

One of the best things Auth0 did is a provider for Laravel to integrate their service: github.com/auth0/laravel-auth0. Before you go, I have to sadly inform you that when I'm writing this and the provider is at v6.1.0 it still doesn't support Guzzle7, so you have to downgrade it to 6.5.5 by running composer require guzzlehttp/guzzle:^6.5.5. Very sad times. There's already an issue for this, we hope they upgrade it soon.

Let's focus folks and proceed with the tutorial: install the provider running composer require auth0/login:"~6.0" and the magic word of Laravel and Composer will automatically discover and register it.

Usage

Now, following their README, we can set up a Guard and use it as a middleware to protect our routes. Open your config/auth.php file and add a new Guard:

'guards' => [
    ...
    'auth0' => [
        'driver' => 'auth0',
        'provider' => 'auth0',
    ],
],

Now, in the same file, add the provider too:

'providers' => [
    ...
    'auth0' => [
        'driver' => 'auth0',
    ],
],

Given that, we can protect our routes using the guard as a middleware:

Route::get('/private', function () {
    return response()->json(['message' => 'Private endpoint']);
})->middleware('auth:auth0');

From now on, you can only access /private endpoint providing a valid JWT token in the Authorization header like this: Authorization: Bearer <jwt-here>.

Testing

Since Laravel 8 the way Models Factories are made is a bit different, in fact, you have to add HasFactory trait to your model to create a new "fake" model. I think that currently Auth0 still does not support this for the Auth0User model and, because of that, we can't (still) rely on Auth0User::factory() syntax.

To workaround this we're just gonna initialize a new Auth0\Login\Auth0User instance and using that for the actingAs helper.

When creating an Auth0User instance you should provide 2 arguments:

  • array $userInfo , where you can find the sub key (provided by Auth0) or the user_id key;

  • string|null $token - I think this is the JWT, but I don't really know.

Anyway for our test where we don't need to store the user nor to retrieve it from the sub / user_id property, we can just leave them both "empty".

namespace Tests\Feature;

use Tests\TestCase;
use Auth0\Login\Auth0User;
use Illuminate\Http\Response;

class PrivateEndpointTest extends TestCase
{
  public function testAuthorizedWithFakeUser()
  {
    $user = new Auth0User([], null);

    $this->actingAs($user, 'auth0')
      ->getJson('/api/private')
      ->assertStatus(Response::HTTP_OK)
      ->assertExactJson([
        'message' => 'Private endpoint',
      ]);
  }
}