Developer guides
Authentication and token abilities
Create scoped Sanctum tokens and understand customer ownership, session authentication, and IP restrictions.
On this page
Two authentication modes
Protected customer endpoints use auth:sanctum. A request can authenticate with a personal access token in Authorization: Bearer ..., or through a supported first-party session. Core enables Sanctum's stateful API middleware. Session-based writes still need the appropriate CSRF protection; bearer-token examples do not replace the browser login flow.
Token abilities and staff permissions are different systems. A token with orders.read reads the current customer's orders, not every merchant order. The /api/v1 surface is not an Admin CRUD API. Account resources return the customer ID, which must not be confused with the underlying user ID.
Sources: API routes, middleware setup, and AccountResource.
Create a named token
POST /api/v1/auth/tokens accepts JSON:
| Field | Validation | Meaning |
|---|---|---|
email |
Required email | Existing account email |
password |
Required string | Account password |
name |
Required string, maximum 80 characters | Recognizable integration name |
abilities |
Optional array, maximum 50 entries | Explicit ability selection |
abilities.* |
String from the supported ability catalog | Unknown values fail validation |
Omitting abilities grants ['*']. Supply an explicit least-privilege list. An empty array does not request the default; it creates a token without route abilities. Including * normalizes the selection to full access.
Illustrative token-request.json, not real credentials:
{
"email": "[email protected]",
"password": "<account-password>",
"name": "Customer order reader",
"abilities": ["account.read", "orders.read", "tokens.revoke"]
}
curl --request POST 'https://shop.example.test/api/v1/auth/tokens' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data-binary @token-request.json
The 201 response is not wrapped in data:
{
"token": "<plain-text-token>",
"token_type": "Bearer",
"name": "Customer order reader"
}
Store the returned token securely. Do not commit the request file, response, or a populated shell history. The controller returns the plain-text token only on creation. There is no refresh-token exchange in this route set.
The controller lowercases the email for lookup, checks the password hash, and rejects anonymized users. Invalid credentials produce 422 validation_error, not 401. The endpoint validates email and password only: it does not accept a two-factor code, expires_at, or an IP allowlist. Do not assume the browser's privileged two-factor flow is applied here.
Source: TokenController.
Supported abilities
| Ability | Protected operations |
|---|---|
account.read |
GET /me |
account.update |
PATCH /me |
addresses.read |
GET /addresses |
addresses.create |
POST /addresses |
addresses.update |
PATCH /addresses/{address} |
addresses.delete |
DELETE /addresses/{address} |
orders.read |
Order list and detail |
orders.create |
POST /checkout |
orders.pay |
POST /orders/{order}/pay |
invoices.read |
Invoice list and detail |
credit_notes.read |
Credit-note list and detail |
tickets.read |
Support-ticket list and detail |
subscriptions.read |
Subscription Module API |
services.read |
Provisioning Module API |
downloads.read |
Digital download list and file |
digital_secrets.read |
Digital Delivery API, including readable delivered values |
event_tickets.read |
Events ticket list and detail |
tokens.revoke |
Delete the current personal token |
* |
All supported abilities |
The underscore in credit_notes.read, digital_secrets.read, and event_tickets.read is intentional. It does not match the hyphenated URL spelling.
EnsureApiTokenAbility checks abilities when a bearer token is present. It does not apply personal-token scopes to a session-only request. Missing scope produces 403 insufficient_scope with errors.required_ability. Unknown Module API route-name prefixes fail closed as unmapped_api_route; declaring an arbitrary new ability in a package does not add it to Core's catalog.
Use and revoke a token
With AGOVENA_TOKEN populated securely in your environment:
curl 'https://shop.example.test/api/v1/me' \
--header 'Accept: application/json' \
--header "Authorization: Bearer $AGOVENA_TOKEN"
curl --request DELETE 'https://shop.example.test/api/v1/auth/tokens' \
--header 'Accept: application/json' \
--header "Authorization: Bearer $AGOVENA_TOKEN"
Revocation requires tokens.revoke or *. It returns 200 {"ok":true} and deletes only the current personal access token. It does not delete all tokens or implement a browser logout. With session authentication and no personal token, the controller still returns ok without deleting a token.
IP policy and rate limits
The Admin token editor supports per-token IP policies. An empty policy allows any IP. The password endpoint explicitly creates an empty policy; configure restrictions through the supported token management screen rather than sending an undocumented API field. A disallowed source address receives 403 ip_not_allowed.
Proxy trust affects the source address. Core accepts explicitly configured TRUSTED_PROXIES and does not accept wildcard trust. Check the deployment's proxy chain before relying on an allowlist.
Token creation is limited to five requests per minute per IP/email combination. An additional account-level guard records incorrect credentials and blocks after ten failed attempts within its 600-second window. Protected ordinary requests use 60 per minute per user or IP; checkout and payment initiation use 10. Public catalog requests use 120 per minute per IP.
Integration tests to retain
Cover successful bearer and session authentication separately. Test missing scope, an unknown ability, self-revocation without permission, another customer's object ID, blocked IPs, and omitted abilities. Do not log credential-bearing requests while testing errors.
The source checks are ApiTokenManagementTest, StorefrontApiTest, ApiTokenAbilities, and AppServiceProvider. Continue with the endpoint reference.