# Authentication design
xui-rs supports the two authentication mechanisms implemented by 3x-ui
v3.8.5. They deliberately have different lifecycles.
## API tokens
Bearer tokens are the default recommendation for programmatic clients. 3x-ui
accepts them on every `/panel/api/*` endpoint and skips CSRF validation after
the token has been verified. The token is held in `SecretString`, omitted from
`Debug`, and never written to tracing fields.
## Cookie sessions
Cookie authentication follows the same sequence as the 3x-ui web client:
1. `GET /csrf-token` creates a pre-auth session and returns its CSRF token.
2. `POST /login` sends that token in `X-CSRF-Token`; the shared cookie jar sends the
matching signed `3x-ui` cookie automatically.
3. Successful login updates the same session cookie. Unsafe session requests
continue to send the CSRF token.
4. `POST /logout` clears the server-side session and the local cached CSRF
token.
The SDK does not parse `Max-Age`, copy a complete `Set-Cookie` value into the
`Cookie` request header, retain passwords, or perform hidden automatic login.
Those behaviours are both unnecessary with a cookie jar and surprising for
applications that control credential access.
CSRF initialization is synchronized across cloned clients. If an unsafe
session request receives HTTP 403, the client discards its cached CSRF value,
fetches a fresh CSRF token through the cookie jar, and retries once. This
applies to every 403 on that request path: the SDK does not determine whether
the response actually came from CSRF validation. Bearer-authenticated panel
API calls do not enter this recovery path.
For the pinned v3.8.5 `bulkAdjust` route, the built-in CSRF, domain-validation,
and token-scope middleware reject requests before the mutation handler runs.
The handler reports service failures through API envelopes, not HTTP 403.
This ordering supports replay after those middleware rejections; it does not
prove that a 403 generated by a reverse proxy or modified server preceded all
mutations. See the tagged [CSRF middleware](https://github.com/MHSanaei/3x-ui/blob/v3.8.5/internal/web/middleware/security.go),
[domain validator](https://github.com/MHSanaei/3x-ui/blob/v3.8.5/internal/web/middleware/domainValidator.go),
[API middleware registration](https://github.com/MHSanaei/3x-ui/blob/v3.8.5/internal/web/controller/api.go),
and [client controller](https://github.com/MHSanaei/3x-ui/blob/v3.8.5/internal/web/controller/client.go).
Transport, response-reading, decoding, and API-envelope failures do not trigger
another SDK-dispatcher attempt. They can still leave the mutation outcome
unknown. Follow the [bulk adjustment recovery guidance](clients.md#bulk-adjustment-retries-and-partial-results)
before repeating additive operations; HTTP status alone is not a general
idempotency guarantee.
The jar is stored explicitly and shared by all cloned HTTP clients and
WebSocket handshakes. This is required because `/ws` accepts only an
authenticated session cookie; 3x-ui does not apply API-token authentication to
that route. Cookie selection still belongs entirely to `reqwest::cookie::Jar`,
including domain/path/secure/expiry rules. See [the event-stream guide](events.md).
## Upstream references
This design is based on the v3.8.5 implementations in:
- `internal/web/controller/index.go`
- `internal/web/controller/api.go`
- `internal/web/middleware/security.go`
- `internal/web/session/csrf.go`
- `internal/web/session/session.go`
- `internal/web/controller/websocket.go`