docs(16.3-03): ExampleResolveBackend for plugin token auth

- Add compiled ResolveBackend example with acme.blog cover
- Include src= fence in authentication docs (Bearer-only)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-11 02:17:43 +02:00
parent 611f98e33b
commit 60873a3fea
2 changed files with 68 additions and 0 deletions

View File

@@ -109,6 +109,33 @@ A guard that resolves more than a user, such as an API token record, implements
Plugin routes that should accept an admin JWT or a personal API token call `cabana.ResolveBackend`. It reads only the Authorization Bearer header (never the admin cookie), applies the token's method filter, and for a scoped token requires a method-allowed scope that is full or matches every cover code you pass. On success it returns the request with `bouncer.WithUser` (and `bouncer.WithCredential` for a token). Failures are `cabana.ErrUnauthenticated` or `cabana.ErrTokenScope`. Routes registered before `cabana.Activate` must call it per request, because the resolver is published at activation.
```go src=modules/cabana/example_tokens_test.go#ExampleResolveBackend
handler := func(app *backpack.App, w http.ResponseWriter, r *http.Request) {
_, _, err := cabana.ResolveBackend(app, r, "acme.blog.*")
if err != nil {
if errors.Is(err, cabana.ErrTokenScope) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusForbidden)
fmt.Fprint(w, `{"error":"Insufficient permissions"}`)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprint(w, `{"error":"Authentication required"}`)
return
}
w.WriteHeader(http.StatusOK)
}
rec := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodPost, "/_blog/api/v1/posts", nil)
r.Header.Set("Authorization", "Bearer unused")
handler(nil, rec, r)
fmt.Println(rec.Code, rec.Body.String())
// Output:
// 401 {"error":"Authentication required"}
```
## Passwords
`bouncer.HashPassword` hashes with bcrypt at the cost you give it, `bouncer.CheckPassword` compares in constant time, and `bouncer.NeedsRehash` reports a hash made below your configured cost. WinterCMS stores bcrypt hashes too, so existing passwords keep working: