Passport and OAuth
The API uses Laravel Passport as its OAuth2 server. Enabled grants: personal access tokens, authorization code, refresh, and client credentials. The password grant is off — do not call Passport::enablePasswordGrant(). The Filament panel login stays an email/password session under the web guard; Passport only protects API routes.
Personal access tokens
Mint a PAT from the panel: open the user menu → API tokens. PassportClientSeeder (run by the installer or migrate) creates the personal-access client that page needs. A PAT is the fastest way to call the API as a logged-in panel user.
curl -H "Authorization: Bearer <token>" https://edge.qcentic.com/api/user
GET /api/user (or /api/me) with a Bearer token is the API proof endpoint — the one you curl to confirm a token works. It is not /api/media.
Authorization code flow
For third-party apps acting on behalf of a user, use the authorization code grant.
sequenceDiagram
participant RO as Resource Owner (browser)
participant Client as OAuth Client
participant Auth as /oauth/authorize
participant Token as /oauth/token
participant API as /api/user
RO->>Client: Click "Connect"
Client->>Auth: Redirect to authorize
Auth->>RO: Login + consent (panel session)
RO->>Auth: Approve scopes
Auth->>Client: Redirect with ?code=
Client->>Token: POST code + client secret
Token->>Client: access_token + refresh_token
Client->>API: Bearer access_token
API->>Client: 200 user JSON
Client credentials is the machine-to-machine grant (no user). Refresh tokens rotate expired access tokens without re-prompting the user.
Signing keys
Passport signs access tokens with an RSA keypair. Generate it once:
php artisan passport:keys
This writes storage/oauth-private.key and storage/oauth-public.key. On Magic Containers the disk is ephemeral — those files vanish on recycle, and tokens stop validating. Put the PEMs in env instead:
# One-line \n-escaped PEM (keep BEGIN/END lines)
php -r 'echo str_replace(["\r\n","\n","\r"], "\\n", file_get_contents("storage/oauth-private.key"));'
php -r 'echo str_replace(["\r\n","\n","\r"], "\\n", file_get_contents("storage/oauth-public.key"));'
# Paste into PASSPORT_PRIVATE_KEY / PASSPORT_PUBLIC_KEY, then:
rm storage/oauth-private.key storage/oauth-public.key
PASSPORT_PRIVATE_KEY / PASSPORT_PUBLIC_KEY are the same class of secret as APP_KEY. See Secrets and keys.
Content API vs Passport
The Content API (/api/v1) is the JSON write surface for panel Filament resources. It accepts a Bearer hashed key (agk_…, minted on the panel's API settings page) or a Passport token. Passport tokens remain valid on the Content API — they are unscoped at the key layer and pass through to the same resource policies the panel enforces.
Sanctum is not the template API default. Avoid reaching for it.