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.

See also

Built by Qcentic