API Authentication¶
Authenticatable model¶
The API authenticatable is App\Models\SystemConnection, not User. A caller authenticates as its own connection — there is no separate API-client entity. Never use a User model for API auth — it does not exist in this project, and Conflux has no App/Sanctum-style API-client model either; that concept was removed (see ADR-0002 and ADR-0006).
The connection guard¶
Authentication runs through a custom guard, not a package:
| Piece | Value |
|---|---|
| Guard name | connection |
| Guard driver | connection-token, registered via Auth::extend() in app/Providers/AppServiceProvider.php |
| Guard implementation | App\Auth\SystemConnectionTokenGuard |
| Config | config/auth.php → guards.connection |
The guard reads the bearer token from the request, hashes it with SHA-256, and looks up a SystemConnectionToken where purpose = access (see System Connection Tokens). If the token is found and isValid() (not expired, not used), the guard resolves to the owning SystemConnection (eager-loaded with tenant and system). No personal_access_tokens table or polymorphic tokenable resolution is involved — the token always belongs to a SystemConnection.
Route protection¶
All API routes must be defined in routes/api.php. Both middleware are applied automatically to every route in that file via appendToGroup('api', ...) in bootstrap/app.php — no per-route or per-group middleware declaration is needed:
// routes/api.php — just bare routes, middleware is applied globally
Route::get('/me', MeController::class)->name('api.me');
auth:connection— resolves and authenticates the bearer token via theconnectionguard.ensure.connection.active— rejects requests from an unhealthy connection or an inactive tenant with a401response. Implemented atapp/Http/Middleware/EnsureSystemConnectionIsActive.php.
Routes that must run before a caller has a token — such as /system-connections/register — opt out with ->withoutMiddleware(['auth:connection', 'ensure.connection.active']). See Connection Flow for why /register is unauthenticated and /connect is not.
If a future endpoint must be public, define it in a separate route file — do not add it to routes/api.php.
Issuing tokens¶
Access tokens are not managed through the admin panel — there is no "generate token" UI action. A SystemConnectionToken with purpose = access is issued the same way any connection token is: via App\Services\SystemConnectionService, as part of the connection flow. The plaintext value is returned to the caller once; only its SHA-256 hash is ever persisted.
For local development, use the conflux-local-dev MCP tool to issue a SystemConnection access token directly, without going through the HTTP handshake.
Testing¶
Authenticate as a SystemConnection in tests by attaching a valid access token as a bearer header — the guard reads the token from the request, not from Laravel's session-based Auth facade:
$connection = SystemConnection::factory()->active()->create(['tenant_id' => $tenant->id]);
$plainToken = 'plain-access-token-'.$connection->id;
SystemConnectionToken::factory()->accessToken()->valid()->create([
'system_connection_id' => $connection->id,
'token' => hash('sha256', $plainToken),
]);
$this->withToken($plainToken)->getJson('/api/me');
The shared tests/Pest.php helpers callerConnection(), activeConnectionFor(), and accessTokenFor() wrap exactly this setup — prefer them over rebuilding it inline. See any test under tests/Feature/Api/ (e.g. MeTest.php) for usage.
Do not use actingAs() for API requests — it authenticates against Laravel's session-based guards (web), not the token-based connection guard used by routes/api.php.
API documentation (Scribe)¶
Scribe is configured to pick up api/* routes and defaults to Bearer token authentication in the generated docs. Annotate controllers with #[Group] and #[Response] attributes to improve the output:
use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\Response;
#[Group('Connections')]
class SystemConnectionsController extends Controller
{
#[Response(['data' => []], status: 200)]
public function __invoke(Request $request): JsonResponse { ... }
}
Regenerate docs after controller changes:
Error Responses¶
Error responses follow RFC 9457 (Problem Details for HTTP APIs) with Content-Type: application/problem+json. See API Errors for the full reference, including the handled exception types and the #[ApiProblem] attribute for documenting errors in Scribe.