Opinionated Laravel API starter kit for token-based authentication with strong defaults around API design, documentation, testing, and security.
- PHP
8.5+ Laravel12 - SQLite-first local development (
DB_CONNECTION=sqlite) - Sanctum personal access token authentication
- Versioned API routes with no
/apiprefix (/v1/...) - Invokable controllers only
- Form Request validation + DTO-style request payload objects
- JSON:API resources for entity responses
- API localization via
Accept-Language+Content-Language - Scribe (attribute-based) API docs + OpenAPI generation
- OpenAPI contract tests to keep docs and runtime behavior in sync
- Sunset middleware to deprecate and retire endpoints safely
- GitHub Actions for CI tests and daily dependency update PRs
- Laravel Framework:
^12.0 - PHP:
^8.5 - Auth:
laravel/sanctum - Docs/OpenAPI:
knuckleswtf/scribe(attributes, not docblocks) - Test Runner: Pest + Laravel test tooling
- Static Analysis / Quality: PHPStan (Larastan), Pint, Rector
composer installcp .env.example .env
php artisan key:generate
touch database/database.sqlite
php artisan migrateOr run the bundled setup script:
composer run setupphp artisan serveAPI base path is versioned and has no global /api prefix:
http://127.0.0.1:8000/v1/...
Routing is intentionally split:
routes/api/routes.phpfor top-level API version groupingroutes/api/v1.phpfor V1 endpoint declarations
Framework routing is configured with apiPrefix: '' in bootstrap/app.php, so your URLs stay clean.
| Method | Path | Auth | Purpose |
|---|---|---|---|
| POST | /v1/auth/register |
No | Register and issue token |
| POST | /v1/auth/login |
No | Login and issue token |
| GET | /v1/auth/me |
Bearer | Current authenticated user |
| POST | /v1/auth/logout |
Bearer | Revoke current token |
| POST | /v1/auth/email/verification-notification |
Bearer | Send/resend verification email |
| GET | /v1/auth/email/verify/{id}/{hash} |
Signed URL | Verify email |
| POST | /v1/auth/password/forgot |
No | Request reset email (anti-enumeration response) |
| GET | /v1/auth/password/reset/{token} |
No | Return reset payload for API clients |
| POST | /v1/auth/password/reset |
No | Reset password |
Register:
curl -X POST http://127.0.0.1:8000/v1/auth/register \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"name": "Jane Doe",
"email": "jane@example.com",
"password": "Password123!",
"device_name": "cli"
}'Login:
curl -X POST http://127.0.0.1:8000/v1/auth/login \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"email": "jane@example.com",
"password": "Password123!",
"device_name": "cli"
}'Use token on protected route:
curl http://127.0.0.1:8000/v1/auth/me \
-H "Accept: application/json" \
-H "Authorization: Bearer <TOKEN>"Localized response (Spanish):
curl -X POST http://127.0.0.1:8000/v1/auth/password/forgot \
-H "Accept: application/json" \
-H "Accept-Language: es" \
-H "Content-Type: application/json" \
-d '{"email":"unknown@example.com"}'UserResourceuses Laravel JSON:API resource format (application/vnd.api+json)- Message/error JSON responses use
new JsonResponse([...])for explicitness - Validation and auth errors are normalized in global exception renderers
- API message strings are translated via
lang/en/api.phpandlang/es/api.php
Locale resolution is API-first:
- Middleware reads
Accept-Language - Locale is resolved against
APP_SUPPORTED_LOCALES - Response includes
Content-Language - Unsupported locales fall back to
APP_FALLBACK_LOCALE
Relevant config/env:
APP_LOCALEAPP_FALLBACK_LOCALEAPP_SUPPORTED_LOCALES(default:en,es)SANCTUM_EXPIRATION(default:120minutes)
- Controllers are invokable and do not extend a base controller
- Request validation lives in
app/Http/Requests/Auth - DTO payload objects live in
app/Http/Payloads/V1(final, readonly) - Entity output lives in API resources (
app/Http/Resources)
- ULID primary keys for users
- Password hashing via model casts
- Email verification required model contract (
MustVerifyEmail) - Rate limits configured in
AppServiceProvider:auth-register: 10/minute per IPauth-login: 10/minute per IP + emailauth-password: 5/minute per IP + emailauth-protected: 60/minute per authenticated user
- Verification endpoints use signed URLs and throttling
- Write endpoints enforce JSON payloads (
application/json) - API responses include baseline hardening headers (
nosniff,DENY,no-referrer) - API responses include an
X-Request-Idheader (propagated or generated) - Security-sensitive auth/token actions emit structured
security.auditlog events - Critical write endpoints support
Idempotency-Keyreplay/conflict handling - Configurable transport hardening for HTTPS enforcement, HSTS, trusted proxies/hosts, and strict CORS origins
App\Http\Middleware\Sunset adds deprecation metadata and can enforce retirement.
Usage:
Route::middleware('sunset:2030-01-01,https://api.example.com/v2/auth/login,true')
->post('/v1/auth/login', LoginController::class);Behavior:
- Adds
DeprecationandSunsetheaders - Adds
Link: <...>; rel="successor-version"when successor URL is valid - Can return
410 Goneafter sunset date when enforcement is enabled
Scribe is configured for this no-prefix API shape:
- Route matching uses
v1/*prefixes (config/scribe.php) - Endpoints are documented via PHP attributes
- OpenAPI output is generated to
public/docs/openapi.yaml
Generate docs/spec:
php artisan scribe:generate --no-interactionGenerated artifacts:
public/docs/index.htmlpublic/docs/openapi.yamlpublic/docs/collection.json
Run test suite:
php artisan testOr use composer script:
composer testOther quality commands:
composer lint
composer stanFeature tests include:
- Token/auth flows
- Email verification and password reset workflows
- Security and unhappy-path scenarios
- Localization behavior
- Sunset middleware behavior
- OpenAPI generation and contract verification
GitHub Actions workflows:
.github/workflows/ci-tests.yml- Runs tests on every push and pull request
.github/workflows/dependency-updates.yml- Runs daily at
03:00 UTC - Executes
composer update - Opens/updates PR titled
bot: dependency updates
- Runs daily at
.github/workflows/security-gate.yml- Runs Composer security audit
- Fails on high/critical advisories
- Runs repository secret scan with Gitleaks
app/
Http/
Controllers/Api/V1/Auth/
Middleware/
Payloads/V1/
Requests/Auth/
Resources/
routes/
api/
routes.php
v1.php
tests/
Feature/
config/
sanctum.php
scribe.php
.github/
workflows/
MIT