Claude
Skills
Sign in
Back

auth0-laravel-api

Included with Lifetime
$97 forever

Use when securing Laravel API endpoints with JWT Bearer token validation, scope/permission checks, or stateless auth - integrates auth0/login (laravel-auth0) with the AuthorizationGuard for REST APIs receiving access tokens from SPAs, mobile apps, or other clients. Triggers on: Laravel API auth, auth0.authorizer, AuthorizationGuard, Laravel JWT, stateless Bearer.

Backend & APIs

What this skill does


# Auth0 Laravel API Integration

Protect Laravel API endpoints with JWT access token validation using `auth0/login` and the `AuthorizationGuard`.

---

## Prerequisites

- Laravel 11+ application
- PHP 8.2+ with extensions: `mbstring`, `openssl`, `json`
- Composer installed
- Auth0 API resource configured (not an Application - must be an API)
- If you don't have Auth0 set up yet, use the `auth0-quickstart` skill first

## When NOT to Use

| Scenario | Use Instead |
|----------|-------------|
| Laravel web app with login/logout UI | `auth0-laravel` (session-based `AuthenticationGuard`) |
| Plain PHP API (no framework) | `auth0-php-api` |
| Plain PHP web app | `auth0-php` |
| Single Page Applications | `auth0-react`, `auth0-vue`, or `auth0-angular` |
| FastAPI / Python APIs | `auth0-fastapi-api` |
| Express / Node.js APIs | `express-oauth2-jwt-bearer` |
| Issuing tokens | This skill is for *validating* access tokens, not issuing them |

---

## Quick Start Workflow

### 1. Install SDK

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

The `auth0/login` package requires `auth0/auth0-php` (v8.19+) and installs it automatically. It also requires a PSR-18 HTTP client - if you don't already have one:

```bash
composer require guzzlehttp/guzzle guzzlehttp/psr7
```

### 2. Publish Configuration

```bash
php artisan vendor:publish --tag=auth0
```

This creates `config/auth0.php` with guard, middleware, and route configuration.

### 3. Create Auth0 API

You need an **API** (not Application) in Auth0.

> **STOP - ask the user before proceeding.**
>
> Ask exactly this question and wait for their answer before doing anything else:
>
> > "How would you like to create the Auth0 API resource?
> > 1. **Automated** - I'll run Auth0 CLI scripts that create the resource and write the exact values to your `.env` automatically.
> > 2. **Manual** - You create the API yourself in the Auth0 Dashboard (or via `auth0 apis create`) and provide me the Domain and Audience.
> >
> > Which do you prefer? (1 = Automated / 2 = Manual)"
>
> Do NOT proceed to any setup steps until the user has answered. Do NOT default to manual.

**If the user chose Automated**, follow the [Setup Guide](references/setup.md) for complete CLI scripts. The automated path writes `.env` for you - skip Step 4 below and proceed directly to Step 5.

**If the user chose Manual**, follow the [Setup Guide](references/setup.md) (Manual Setup section) for full instructions. Then continue with Step 4 below.

Quick reference for manual API creation:

```bash
auth0 apis create \
  --name "My Laravel API" \
  --identifier https://my-api.example.com \
  --json
```

Or create manually in Auth0 Dashboard -> Applications -> APIs

### 4. Configure Environment

Add to your `.env`:

```bash
AUTH0_DOMAIN=your-tenant.us.auth0.com
AUTH0_AUDIENCE=https://your-api.example.com
```

`AUTH0_DOMAIN` is your Auth0 tenant domain (without `https://`). `AUTH0_AUDIENCE` is the API identifier you set when creating the API resource in Auth0.

### 5. Configure Auth Guard

Update `config/auth.php` to add the API guard:

```php
'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
    'auth0-api' => [
        'driver' => 'auth0.authorizer',
        'provider' => 'auth0-provider',
        'configuration' => 'api',
    ],
],

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\Models\User::class,
    ],
    'auth0-provider' => [
        'driver' => 'auth0.provider',
        'repository' => 'auth0.repository',
    ],
],
```

Key points:
- `driver` must be `auth0.authorizer` (not `auth0.authenticator` which is for web apps)
- `configuration` must be `'api'` which maps to the `api` guard in `config/auth0.php`
- The SDK auto-registers an `auth0-api` guard with this config, but defining it explicitly is clearer

### 6. Verify Auth0 Config

After publishing, verify that `config/auth0.php` contains a `guards.api` key with `strategy` set to `SdkConfiguration::STRATEGY_API` (value: `'api'`). This is already present in the published config — no manual editing needed.

The published file uses class constants for keys (e.g., `Configuration::CONFIG_STRATEGY`), which resolve to the same string values at runtime:

```php
'guards' => [
    'api' => [
        'strategy' => SdkConfiguration::STRATEGY_API,  // value: 'api'
    ],
],
```

The published config also includes `default` and `web` guard sections — these can be ignored for API-only usage. The `STRATEGY_API` strategy disables all session/cookie machinery and enables stateless Bearer token validation.

### 7. Add Protected API Routes

Laravel 11+ does not include `routes/api.php` by default. If the file does not exist, scaffold it:

```bash
php artisan install:api
```

This creates `routes/api.php` and registers it in `bootstrap/app.php` with the `/api` prefix. It also installs Laravel Sanctum, which is unused but harmless alongside Auth0.

In `routes/api.php`:

```php
use Illuminate\Support\Facades\Route;

Route::get('/public', function () {
    return response()->json(['message' => 'Public endpoint - no authentication required']);
});

Route::middleware('auth:auth0-api')->group(function () {
    Route::get('/private', function () {
        $user = auth('auth0-api')->user();
        return response()->json([
            'message' => 'Private endpoint',
            'sub' => $user->getAuthIdentifier(),
        ]);
    });
});
```

The `auth:auth0-api` middleware validates the Bearer token, verifies the signature against the JWKS endpoint, and checks issuer and audience claims. Requests without a valid token receive a 401 response.

### 8. Scope and Permission Checks

Use the guard's `hasScope()` and `hasPermission()` methods:

```php
Route::middleware('auth:auth0-api')->group(function () {
    Route::get('/messages', function () {
        $guard = auth('auth0-api');

        if (!$guard->hasScope('read:messages')) {
            return response()->json(['error' => 'insufficient_scope'], 403);
        }

        return response()->json(['messages' => []]);
    });

    Route::delete('/users/{id}', function (string $id) {
        $guard = auth('auth0-api');

        if (!$guard->hasPermission('delete:users')) {
            return response()->json(['error' => 'insufficient_permissions'], 403);
        }

        return response()->json(['deleted' => $id]);
    });
});
```

- `hasScope()` checks the `scope` claim (space-separated string in the JWT)
- `hasPermission()` checks the `permissions` claim (array, requires RBAC enabled on the API in Auth0 Dashboard)

### 9. Access Token Claims

The authenticated user is a `StatelessUser` instance with dynamic claim access:

```php
Route::middleware('auth:auth0-api')->get('/profile', function () {
    $user = auth('auth0-api')->user();

    return response()->json([
        'sub' => $user->getAuthIdentifier(),
        'email' => $user->email,
        'permissions' => $user->permissions ?? [],
        'all_claims' => $user->jsonSerialize(),
    ]);
});
```

Claims are accessed via:
- `$user->getAuthIdentifier()` - Returns `sub` claim
- `$user->claim_name` - Dynamic property access via `__get`
- `$user->getAttribute('claim_name')` - Explicit access
- `$user->jsonSerialize()` - All claims as array

### 10. Test the API

> **Agent instruction:** Start the Laravel dev server and verify the basic endpoints work without credentials:
> ```bash
> php artisan serve &
> sleep 2
> curl -s -H "Accept: application/json" http://localhost:8000/api/public
> curl -s -o /dev/null -w "%{http_code}" -H "Accept: application/json" http://localhost:8000/api/private
> ```
> - `/api/public` should return `{"message":"Public endpoint - no authentication required"}`
> - `/api/private` should return HTTP 401
>
> **Important:** The `-H "Accept: application/json"` header is required. Without it, Laravel's `Authenticate` middleware returns a 302 redirect instead of 401.
>
> If both pass, the guard is working. Kill the background s

Related in Backend & APIs