Update API and proxy docs

This commit is contained in:
2026-08-10 21:35:46 +00:00
parent 6f5287e070
commit 00ef0ae2e7
15 changed files with 1107 additions and 123 deletions
+70
View File
@@ -0,0 +1,70 @@
# GET /auth/api/forward
[API overview](../API.md) · [Proxy guides](../proxy/index.md)
Forward-auth validation for reverse proxies. The proxy calls this endpoint for every incoming request; Paskia validates the session and either authorizes the request by returning 204 with `Remote-*` headers, or rejects it with 401/403 error responses with an HTML login page if `text/html` was requested (i.e. it's a browser viewing the page), or otherwise JSON with details on the error and a URL to initiate API authentication flow.
The proxy server follows the 204 response with the original request to protected service, adding those remote headers to original user request, sent to the service. Any error response is sent directly back to client, never connecting to the protected service.
See [Forward-Auth Proxy Guides](../proxy/index.md) for Caddy, Nginx, Traefik, Apache APISIX, Envoy and HAProxy configuration examples. Caddy users can also start from the dedicated [Caddy configuration guide](../proxy/caddy.md).
## Query parameters
| Parameter | Description |
|-----------|-------------|
| perm | Required permissions. See the [perm argument](perm.md). |
| max_age | Require recent passkey use. See the [max_age argument](max-age.md). |
## Request headers
| Header | Expected value / note | How Paskia uses it |
|---|---|---|
| Host | Forwarded directly from the client | Verifying the session's bound host |
| Cookie | Forwarded directly or just cookie `__Host-paskia` | Session ID |
| X-Forwarded-Method | The HTTP method, e.g. POST | Logging of original request |
| X-Forwarded-Uri | Request path and query, e.g. /reports?foo=bar | Logging of original request |
| Accept | text/html or anything else | Determines whether failures return an HTML page or JSON |
Connection hop-by-hop headers (Connection, Upgrade, Transfer-Encoding, etc.) must not be forwarded.
## Response
### Success (204)
No response body. Only [Remote headers](../Headers.md) are set on the response and your proxy should forward them to the backend request. The headers, not a response body, are the whole point of this endpoint: they are how the authenticated identity reaches the protected service, which can trust them because it is only reachable through the proxy.
### Failure (401 / 403)
| Status | Meaning |
|---|---|
| 401 | Session missing or expired, or max_age not satisfied — the user needs to (re)authenticate. |
| 403 | Requested permissions are missing — the forbidden flow allows signing in with another account. |
Failure responses come in two flavors, chosen by the Accept header, because the audience differs:
- A browser asking for a page (Accept includes text/html) gets a full authentication page it can show directly — the proxy simply passes the response through and the user can sign in without any application involvement.
- Any other request (fetch, img, ...) gets JSON intended for programmatic handling. Besides the error detail, it carries an auth section whose iframe URL points to a ready-made authentication dialog your frontend can embed, so the user can sign in without leaving your app:
```json
{
"detail": "Additional authentication required",
"auth": {
"mode": "reauth",
"iframe": "/auth/restricted/iframe#mode=reauth&theme=dark"
}
}
```
The mode field:
- login: no valid session, need to sign in
- reauth: additional authentication required (using same passkey)
- forbidden: lacking required permissions
Extra metadata such as the user's theme override may appear as additional fields and iframe fragment parameters.
Note: we provide a JavaScript package [paskia](https://www.npmjs.com/package/paskia) with helpers for the embedding, fetch 401/403 handling, session renewals and more.
## Session renewal
The forward endpoint **does not renew** the session because in the forward-auth mechanism it could not send the client a renewed session cookie. It only validates the current cookie and returns the trusted headers. Use [/auth/api/validate](validate.md) when you need to refresh the session lifetime. Otherwise the user will have to sign in again every 24h even if they are actively using the service.
+47
View File
@@ -0,0 +1,47 @@
# The max_age argument
[API overview](../API.md) · [`/auth/api/validate`](validate.md) · [`/auth/api/forward`](../forward.md)
The max_age argument is used by endpoints that validate sessions to require a recent passkey use. It is supported by:
- [POST /auth/api/validate](validate.md)
- [GET /auth/api/forward](forward.md)
## What it checks
max_age limits how long ago the user last authenticated with their passkey. It is intended for high-risk actions where you want to be sure the user recently proved possession of their credential, not just that they still have a valid session cookie.
The check compares the elapsed time since the credential was last used against the given limit:
- If the credential has a last_used timestamp, that time is used.
- Otherwise, the session's validated timestamp is used as a fallback.
If the authentication is older than max_age, the endpoint returns 401 with mode reauth, prompting the user to re-authenticate.
## Time units
max_age accepts a number followed by one of these units:
| Unit | Meaning |
|---|---|
| s | seconds |
| m / min | minutes |
| h | hours |
| d | days |
Examples:
```text
?max_age=30s
?max_age=5m
?max_age=5min
?max_age=1h
?max_age=1d
```
An invalid format is logged as a warning but does not cause the request to fail; the requirement is simply ignored in that case.
## Important notes
- Session renewal by /auth/api/validate does **not** count as fresh authentication. The check is based on the credential's last_used time, not on how recently the session cookie was renewed.
- max_age is independent of perm. You can use either or both at the same time.
+70
View File
@@ -0,0 +1,70 @@
# The perm argument
[API overview](../API.md) · [`/auth/api/validate`](validate.md) · [`/auth/api/forward`](../forward.md)
The perm argument is used by endpoints that validate sessions to require one or more permission scopes. It is supported by:
- [POST /auth/api/validate](validate.md)
- [GET /auth/api/forward](forward.md)
## Passing the argument
Repeat the query parameter for each required scope:
```text
?perm=myapp:read&perm=myapp:write
```
You can also pass multiple scopes in one parameter by separating them with whitespace:
```text
?perm=myapp:read%20myapp:write
```
Both forms produce the same result.
## Semantics
The perm argument uses **AND** semantics: **every** listed scope must be present in the effective permissions for the request to succeed. If any required scope is missing, the endpoint returns 403.
There is **no OR** support inside a single call. If you need to check "scope A or scope B", make separate calls or check the returned permission list in your own backend.
## Wildcards
A required scope may contain the * wildcard, which matches any sequence of characters:
```text
?perm=myapp:*
```
This matches myapp:read, myapp:write, and any other scope starting with myapp:.
## Effective permissions
The permissions available to a session are determined as follows:
1. **Role permissions** — the role assigned to the user contains a set of permission UUIDs.
2. **Org grantable permissions** — only permissions that the user's organization is allowed to grant are effective.
3. **Domain filtering** — a permission can be restricted to a specific domain via its domain field. If the request's Host header does not match that domain, the permission is excluded.
The result is the set of effective permission scopes used for the perm check. Domain-restricted permissions let you grant a scope only for a specific site or subdomain without making it global.
## Examples
Require a single permission:
```text
?perm=myapp:login
```
Require two permissions:
```text
?perm=myapp:login&perm=myapp:api
```
Require any scope under myapp:
```text
?perm=myapp:*
```
+65
View File
@@ -0,0 +1,65 @@
# POST /auth/api/validate
[API overview](../API.md) · [`/auth/api/forward`](../forward.md) · [max_age](max-age.md) · [perm](perm.md)
Validate a session and renew its lifetime when needed. This endpoint is normally called by the browser/session validator, but backends may also call it directly when not behind a forward-auth proxy.
See also the [API overview](../API.md) and the [integration guide](../Integration.md).
## Query parameters
| Parameter | Description |
|-----------|-------------|
| perm | Required permissions. See the [perm argument](perm.md). |
| max_age | Require recent passkey use. See the [max_age argument](max-age.md). |
## Request headers
| Header | Expected value / note | How Paskia uses it |
|---|---|---|
| Host | Forwarded directly from the client | Verifying the session's bound host |
| Cookie | Forwarded directly or just cookie `__Host-paskia` | Session ID |
| X-Forwarded-For | Real client IP | Recorded in session data instead of the backend/proxy IP; requires FORWARDED_ALLOW_IPS to trust the immediate peer |
| User-Agent | Forward the original client UA if available; do not let your backend client add its own default | Recorded in session data only when the header is present; omitting it preserves the existing value |
See [integration documentation for backend validate requests](../Integration.md) for more detailed instructions, in particular for forwarding of client-provided headers.
## Response
The endpoint always responds with JSON.
### Success (200)
```json
{
"valid": true,
"renewed": false,
"ctx": {
"user": {
"uuid": "...",
"display_name": "John Smith",
"theme": "dark"
},
"org": {
"uuid": "...",
"display_name": "The Company Ltd."
},
"role": {
"uuid": "...",
"display_name": "Employee"
},
"permissions": ["auth:admin", "myapp:login"]
}
}
```
If the response includes a Set-Cookie header, the session has been renewed (renewed is true) and you should forward that cookie to the client so the browser updates its expiry. Not forwarding it means the session lifetime is not extended, so the user may need to re-authenticate sooner. Renewals are throttled, so frequent calls usually return renewed: false with no Set-Cookie.
### Failure (401 / 403)
| Status | Meaning |
|---|---|
| 401 | Session missing, expired, or max_age not satisfied. The response body includes auth metadata for the login/reauth iframe. |
| 403 | Session is valid but one or more requested permissions are missing. |
A failure response never contains a refreshed Set-Cookie.