Moonlight security model
Who this is for
You are securing Pionia Shop and want to understand where auth runs — at the switch, in middleware, or inside an action.
What you will learn
- Why Moonlight action payloads use POST JSON bodies
- How switch-level authentication protects groups of actions
- The difference between HTTP 401 and envelope
returnCode
Before you start
- Moonlight overview — POST dispatch model
- Pionia Shop tutorial Step 1 — Pionia Shop on port 8000
How it works
Credentials and business fields travel in the POST JSON body. Authentication backends run before your action — so a missing Bearer token fails early on order.place, not halfway through a wallet debit.
POST bodies and access logs
Moonlight sends credentials and business fields in the request body, not query strings:
{
"service": "customer",
"action": "login",
"email": "ada@pionia.shop",
"password": "your-secret"
}Query strings often appear in proxy and CDN access logs. POST bodies typically do not — still use HTTPS in production and never log raw passwords in application code.
Health checks like GET /api/v1/ping remain ordinary GET requests.
Switch-level authentication
Pionia v3 evaluates authentication before dispatching to your action — at the switch layer. Pionia Shop can require JWT for all product.create calls while leaving product.list public, configured on MainSwitch.
See Authentication & authorization for JWT setup with customer.login.
Action-level rules
Prefer attributes on the service or method (#[Authenticated], #[Can], #[CanAny]) so authorization runs before the action body. Use $this->can() inside an action only when the rule depends on the payload or a row (for example “only the assignee may close this task”).
Full guide: Protecting actions with attributes.
Real HTTP status codes
| Situation | HTTP status | Envelope |
|---|---|---|
| Success | 200 | returnCode: 0 |
| Validation error | 422 | returnCode non-zero |
| Not authenticated | 401 | error message in envelope |
| Forbidden | 403 | error message in envelope |
Do not assume HTTP 200 for every error — clients must read both status and JSON.
Common mistakes
- Putting passwords in query strings — use POST JSON for
customer.login; never?password=in URLs. - Checking auth only inside actions — configure switch-level rules so unauthenticated requests fail before business logic runs.
- Logging raw request bodies in production — redact passwords; use
[logging] HIDE_IN_LOGSinsettings.ini. - Ignoring HTTP status because
returnCodeexists — mobile clients must handle 401, 403, and 422 explicitly.
What’s next
Authentication
Implement customer.login with JWT.
Security utilities
Password hashing for customer.login.
Middleware
Request IDs for support tickets.