From 9c44d65f9453bb4a3185eec70c3322e6856ce13c Mon Sep 17 00:00:00 2001 From: = Date: Tue, 16 Jun 2026 12:15:56 +0000 Subject: [PATCH] Deps: Add admin9/laravel-oidc-server --- .claude/skills/passport-development/SKILL.md | 205 +++++++++++++++++++ app/Models/User.php | 6 +- composer.json | 1 + composer.lock | 66 +++++- 4 files changed, 275 insertions(+), 3 deletions(-) create mode 100644 .claude/skills/passport-development/SKILL.md diff --git a/.claude/skills/passport-development/SKILL.md b/.claude/skills/passport-development/SKILL.md new file mode 100644 index 0000000..e5dce35 --- /dev/null +++ b/.claude/skills/passport-development/SKILL.md @@ -0,0 +1,205 @@ +--- +name: passport-development +description: "Develops OAuth2 API authentication with Laravel Passport. Activates when installing or configuring Passport; setting up OAuth2 grants (authorization code, client credentials, personal access tokens, device authorization); managing OAuth clients; protecting API routes with token authentication; defining or checking token scopes; configuring SPA cookie authentication; handling token lifetimes and refresh tokens; or when the user mentions Passport, OAuth2, API tokens, bearer tokens, or API authentication. Make sure to use this skill whenever the user works with OAuth2, API tokens, or third-party API access, even if they don't explicitly mention Passport." +license: MIT +metadata: + author: laravel +--- + +# Passport OAuth2 Authentication + +## Documentation First + +**Always use `search-docs` before writing Passport code.** The documentation covers every grant type, configuration option, and edge case in detail. This skill teaches you how to navigate Passport — the docs have the implementation specifics. + +``` +search-docs(queries: ["Passport installation"], packages: ["laravel/framework@12.x"]) +``` + +The Passport docs live under the `laravel/framework` package — not `laravel/passport`. + +## When to Apply + +Activate this skill when: + +- Installing or configuring Passport +- Setting up OAuth2 authorization grants +- Creating or managing OAuth clients +- Protecting API routes with token authentication +- Defining or checking token scopes +- Configuring SPA cookie-based authentication +- Choosing between Passport and Sanctum + +## Passport vs. Sanctum + +**Passport** is a full OAuth2 server — use it when third-party applications need to consume your API and when you need OAuth2 authorization code grants, client credentials for machine-to-machine auth, or device authorization flow. + +**Sanctum** is simpler — use it when first-party SPAs, third parties, or mobile apps consume the API but you don't need the full OAuth2 grant flows. + +## Installation + +Three steps are always required: + +### 1. Install Passport + +```bash +php artisan install:api --passport +``` + +This publishes migrations, generates encryption keys, and registers routes. + +### 2. Configure the User model + +The User model needs both the `HasApiTokens` trait AND the `OAuthenticatable` interface. Missing the interface is the most common Passport setup mistake — it causes runtime errors that can be confusing to debug. + +```php +use Laravel\Passport\Contracts\OAuthenticatable; +use Laravel\Passport\HasApiTokens; + +class User extends Authenticatable implements OAuthenticatable +{ + use HasApiTokens; +} +``` + +### 3. Configure the auth guard + +The `api` guard must use the `passport` driver in `config/auth.php`. Using `token` or `sanctum` here silently breaks Passport authentication. + +```php +'guards' => [ + 'api' => [ + 'driver' => 'passport', + 'provider' => 'users', + ], +], +``` + +## Choosing a Grant Type + +Matching the right grant to the use case is the most important Passport decision. Use `search-docs` for implementation details of any grant. + +| Use Case | Grant Type | Client Flag | +|----------|-----------|-------------| +| Third-party app accessing user data | Authorization Code | (default) | +| Mobile/SPA without client secret | Authorization Code + PKCE | `--public` | +| Machine-to-machine, no user context | Client Credentials | `--client` | +| User-generated API keys | Personal Access Tokens | `--personal` | +| Smart TV, CLI, IoT devices | Device Authorization | `--device` | + +**Legacy grants** (Password, Implicit) are disabled by default and not recommended. They must be explicitly enabled with `Passport::enablePasswordGrant()` or `Passport::enableImplicitGrant()`. + +## Client Management + +Create clients with the appropriate flag for the grant type: + +```bash +php artisan passport:client # Authorization code + +php artisan passport:client --public # PKCE (no secret) + +php artisan passport:client --client # Client credentials + +php artisan passport:client --personal # Personal access tokens + +php artisan passport:client --device # Device authorization + +``` + +Additional flags: `--name=`, `--redirect_uri=`, `--provider=`. + +Client secrets are hashed by default — the plain-text secret is only shown at creation time and cannot be retrieved later. + +## Protecting Routes + +Apply `auth:api` middleware. Clients send tokens via the `Authorization: Bearer ` header. + +```php +Route::get('/user', function (Request $request) { + return $request->user(); +})->middleware('auth:api'); +``` + +### Scope Enforcement + +Scope middleware must come alongside `auth:api`: + +- `CheckToken::using('scope1', 'scope2')` — requires ALL listed scopes +- `CheckTokenForAnyScope::using('scope1', 'scope2')` — requires ANY listed scope +- `EnsureClientIsResourceOwner::using('scope1')` — restricts to client credential tokens + +```php +use Laravel\Passport\Http\Middleware\CheckToken; + +Route::get('/orders', function () { + // ... +})->middleware(['auth:api', CheckToken::using('orders:read')]); +``` + +### Programmatic scope checking + +```php +if ($request->user()->tokenCan('place-orders')) { + // ... +} +``` + +Use `search-docs` for full scope middleware registration and usage patterns. + +## Key Configuration + +Configure in `AppServiceProvider::boot()`. Use `search-docs` for the full list of options. + +```php +// Token lifetimes (each is independent) +Passport::tokensExpireIn(now()->addDays(15)); +Passport::refreshTokensExpireIn(now()->addDays(30)); +Passport::personalAccessTokensExpireIn(now()->addMonths(6)); + +// Define scopes +Passport::tokensCan([ + 'place-orders' => 'Place orders', + 'check-status' => 'Check order status', +]); +``` + +## SPA Cookie Authentication + +For first-party SPAs, the `CreateFreshApiToken` middleware issues a `laravel_token` cookie containing an encrypted JWT. The SPA must include CSRF tokens — missing the `X-CSRF-TOKEN` or `X-XSRF-TOKEN` header causes 419 errors. + +Use `search-docs` for setup details — this feature has specific CSRF and cookie configuration requirements. + +## Testing + +Passport provides helpers to bypass full OAuth flows in tests: + +```php +Passport::actingAs($user, ['scope1', 'scope2']); +Passport::actingAsClient($client, ['scope1']); +``` + +## Token Maintenance + +```bash +php artisan passport:purge # Purge revoked & expired + +php artisan passport:purge --revoked # Only revoked + +php artisan passport:purge --expired # Only expired + +``` + +Schedule `passport:purge` for regular expired token clean-up. + +## Events + +All in `Laravel\Passport\Events`: `AccessTokenCreated`, `AccessTokenRevoked`, `RefreshTokenCreated`. + +## Common Pitfalls + +- **Missing `OAuthenticatable` interface** — both the `HasApiTokens` trait and the `OAuthenticatable` interface are required on the User model. Missing the interface causes runtime errors. +- **Wrong guard driver** — the `api` guard must use `passport`, not `token` or `sanctum`. This fails silently. +- **Token lifetime confusion** — access token, refresh token, and personal access token lifetimes are all independent settings. +- **Missing CSRF for SPA cookie auth** — `CreateFreshApiToken` requires CSRF tokens. Use `Passport::ignoreCsrfToken()` only if you understand the security implications. +- **Client secrets are hashed** — the plain-text secret is only available at creation time. +- **Legacy grants are disabled** — Password and Implicit grants must be explicitly enabled and are not recommended. diff --git a/app/Models/User.php b/app/Models/User.php index 3a9505a..90ec5ee 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -5,6 +5,8 @@ namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; +use Admin9\OidcServer\Concerns\HasOidcClaims; +use Admin9\OidcServer\Contracts\OidcUserInterface; use Carbon\CarbonImmutable; use Database\Factories\UserFactory; use DateTime; @@ -24,10 +26,10 @@ */ #[Fillable(['name', 'email', 'password', 'immutable_id'])] #[Hidden(['password', 'two_factor_secret', 'two_factor_recovery_codes', 'remember_token'])] -final class User extends Authenticatable +final class User extends Authenticatable implements OidcUserInterface { /** @use HasFactory */ - use HasFactory, SoftDeletes; + use HasFactory, HasOidcClaims, SoftDeletes; use HasRoles; use Notifiable; diff --git a/composer.json b/composer.json index f6c6508..2d5263f 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "ext-libxml": "*", "ext-openssl": "*", "ext-zlib": "*", + "admin9/laravel-oidc-server": "^1.1", "gehrisandro/tailwind-merge-laravel": "^1.4", "laravel/fortify": "^1.34", "laravel/framework": "^13.7", diff --git a/composer.lock b/composer.lock index 1d4fc3b..0b3a585 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,72 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b3aa0d920261f901b2242b3f9b1afb8c", + "content-hash": "666b1735e7d483cade400d22019b7296", "packages": [ + { + "name": "admin9/laravel-oidc-server", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/admin9-labs/laravel-oidc-server.git", + "reference": "4e8fb23dc96b17af555dbd9508e62f4e013395cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/admin9-labs/laravel-oidc-server/zipball/4e8fb23dc96b17af555dbd9508e62f4e013395cd", + "reference": "4e8fb23dc96b17af555dbd9508e62f4e013395cd", + "shasum": "" + }, + "require": { + "defuse/php-encryption": "^2.4", + "laravel/passport": "^12.0|^13.0", + "lcobucci/jwt": "^5.0", + "php": "^8.2", + "spatie/laravel-package-tools": "^1.16" + }, + "require-dev": { + "orchestra/testbench": "^9.0|^10.0", + "pestphp/pest": "^3.0", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Admin9\\OidcServer\\OidcServerServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Admin9\\OidcServer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "ADMIN9 Labs", + "homepage": "https://github.com/admin9-labs" + } + ], + "description": "OpenID Connect Server for Laravel Passport — adds OIDC Discovery, JWKS, UserInfo, Token Introspection, Token Revocation, and RP-Initiated Logout.", + "homepage": "https://github.com/admin9-labs/laravel-oidc-server", + "keywords": [ + "laravel", + "oauth2", + "oidc", + "openid-connect", + "passport" + ], + "support": { + "issues": "https://github.com/admin9-labs/laravel-oidc-server/issues", + "source": "https://github.com/admin9-labs/laravel-oidc-server" + }, + "time": "2026-03-13T14:18:08+00:00" + }, { "name": "bacon/bacon-qr-code", "version": "v3.1.1",