IIIF Authorization¶
iiiris implements the IIIF Authorization Flow API 2.0 and
advertises the older IIIF Authentication API 1.0 service tree
side-by-side for migration compatibility — matching the project's
existing dual-version stance on the Image API itself. The subsystem
lives under internal/auth/ and is wired into the IIIF image and
info.json handlers via an Authorizer seam. When no auth: block is
configured, the zero-config AllowAll authorizer is installed and the
request path is unchanged — nothing is gated and no auth services are
advertised in info.json.
This doc is the operator reference. Code orientation lives in
architecture.md; the YAML schema lives in
configuration.md; copy-pasteable sample configs
for every pattern × backend combination live in
examples/. The durable implementation contract — what
the subsystem guarantees, how its components relate — lives in
specs/auth.md.
Start here¶
This is the most detailed doc in the set; jump to what you need:
- First-time setup → The four services →
patterns → access-service backends,
then copy a ready-made config from
examples/. - Choosing how users authenticate → The four patterns (clickthrough / active / kiosk / external).
- Wiring to your own identity system →
oidc/header/external, or Hook integration for per-request logic. - Which sessions survive restarts or multiple replicas → Session storage (decision table at the top).
- Gating only some images → Rules + Substitute behavior on deny.
- A viewer isn't prompting for login → Reference-viewer matrix + Migration from Auth 1.0.
The four services¶
Per the spec, an Auth 2.0 deployment exposes four cooperating services.
iiiris mounts all four under /iiif/auth/:
| Service | Route | Type | Purpose |
|---|---|---|---|
| Probe | GET /iiif/auth/probe/{identifier...} |
AuthProbeService2 |
Tells a viewer whether the current request has access (200/401/302), and what to do if not. |
| Access | GET, POST /iiif/auth/login/{profile} |
AuthAccessService2 |
The login flow — built-in UI, redirect to SSO, or kiosk check. |
| Access Token | GET /iiif/auth/token |
AuthAccessTokenService2 |
Issues a short-lived Bearer token to the viewer via postMessage (cross-origin friendly). |
| Logout | GET, POST /iiif/auth/logout |
AuthLogoutService2 |
Tears down the session and any active tokens. |
All four routes are public — they are not under /admin. CORS is
applied per route; see CORS + cookies below.
IIIF Auth 1.0 routes¶
For migration compatibility, iiiris also mounts the smaller IIIF Auth
1.0 surface under /iiif/auth/v1/. Auth 1.0 has no probe service; the
access service is itself the entry point.
| Service | Route | 1.0 profile URI | Notes |
|---|---|---|---|
| Login (access) | GET, POST /iiif/auth/v1/login/{profile} |
http://iiif.io/api/auth/1/{clickthrough,login,kiosk,external} |
Shares the v2 login handler — the session cookie it mints is identical regardless of which version's advertisement led the viewer here. |
| Token | GET /iiif/auth/v1/token |
http://iiif.io/api/auth/1/token |
postMessage iframe flow, same input contract as v2. Response payload differs: no @context / type, success keys are {accessToken, expiresIn, messageId}, failure keys are {error, description, messageId}. |
| Logout | GET, POST /iiif/auth/v1/logout |
http://iiif.io/api/auth/1/logout |
Shares the v2 logout handler. |
The 1.0 spec uses Auth-1-specific naming for the active pattern — what
iiiris config (and Auth 2.0) calls active is advertised with the v1
profile URI http://iiif.io/api/auth/1/login. The other three pattern
names are identical between versions.
The four patterns¶
Each profile names one of the four IIIF Auth 2.0 interaction patterns:
| Pattern | Viewer experience | iiiris behavior |
|---|---|---|
clickthrough |
Single "I agree" button. | Built-in HTML page; POST mints session with no credentials captured. |
active |
Username + password form. | Built-in HTML page; POST verifies against an inline users: map or an htpasswd_file:. |
kiosk |
No UI. | Login URL is loaded by the viewer in an iframe; if the originating IP is on the profile allowlist a session is minted automatically, otherwise 401. |
external |
Viewer is redirected away from iiiris. | iiiris 303s to the operator's auth service (or OIDC issuer) and accepts a signed callback. |
The pattern name is advertised verbatim as the profile field of the
AuthAccessService2 block in info.json, so existing IIIF Auth 2.0
viewers know how to render it.
The four access-service backends¶
The access_service.backend field on each profile selects how the user's
identity actually arrives. Backends compose with patterns: clickthrough
+ builtin is the canonical click-through deployment; external +
oidc is a full OIDC login; active + header is rejected by Build
(the patterns are mutually exclusive — interactive vs. ambient — so the
combination is treated as a config mistake).
| Backend | Credentials live where | Required config |
|---|---|---|
builtin |
Inline users: map, or htpasswd_file: on disk. |
One of users: or htpasswd_file: for the active pattern. Empty for clickthrough / kiosk. |
header |
A reverse-proxy header (e.g. oauth2-proxy, Pomerium, Tailscale). | header: name (defaults to X-Forwarded-User). |
external |
A separate operator-run auth service that redirects back with an HMAC-signed query string. | url: of the auth page; callback_secret_env: naming an env var that holds the shared HMAC-SHA256 secret. |
oidc |
An OpenID Connect issuer. | issuer:, client_id:, client_secret_env:; optional scopes: (defaults to [openid, profile, email]) and user_claim: (defaults to sub). |
builtin¶
iiiris hosts the login UI. For clickthrough the page is a single
button ("Continue", or the profile's confirm_label). For active it is
a username/password form. The built-in template is minimal HTML with
system fonts; supply a path to your own template via
access_service.template to override it.
The override is parsed at startup using Go's html/template. A
missing or unparseable file is a hard build error so misconfiguration
surfaces immediately rather than on the first denied request. The
template receives a struct with the following fields:
| Field | Type | Source |
|---|---|---|
.Title |
string |
First value of profile.label (or "Access"). |
.LogoURL |
string |
profile.logo_url. |
.Heading |
string |
First value of profile.heading, or falls back to .Title. |
.Description |
string |
First value of profile.description (may be empty). |
.ConfirmLabel |
string |
profile.confirm_label, or "Sign in" (active pattern) / "Continue" (other). |
.ReturnTo |
string |
Echoed from the request (set as a hidden form input). |
.Active |
bool |
True when the profile pattern is active — render the username/password inputs in that case. |
.Username |
string |
On a re-render after a failed POST, the previously submitted username. |
.Error |
string |
On a re-render, the error message to display (empty on the first GET). |
The override is restart-required; iiiris does not watch the file for
changes (unlike htpasswd_file:, which is mtime-watched). The
built-in template in internal/auth/service/login.go is a useful
starting point if you want to clone-and-tweak.
header¶
iiiris does no UI of its own — the reverse proxy in front of iiiris has
already authenticated the user and is forwarding a header. The
Authorizer reads that header on the image and info.json paths; the
configured value is treated as the user identity and the request is
allowed. The access-service block is omitted from info.json for
header-backend profiles (there is no interactive flow for the viewer to
initiate), but the probe service is still advertised so a viewer can
discover access status.
Important: the reverse proxy must strip the configured header from untrusted clients before forwarding. iiiris trusts the header unconditionally when present.
external¶
GET /iiif/auth/login/{profile} redirects (303) to the operator's
auth URL with two query parameters:
callback— the absolute URL the operator must redirect back to.return_to— passed through verbatim from the viewer.
The operator's auth service authenticates the user and redirects to:
signature is HMAC-SHA256(secret, user + "|" + expires) — hex or
base64url encoding both accepted. iiiris constant-time-compares the
signature, rejects callbacks past the expires timestamp, and mints a
session for user.
oidc¶
GET /iiif/auth/login/{profile} redirects to the issuer's authorization
endpoint (discovered via {issuer}/.well-known/openid-configuration).
The callback exchanges the code for tokens, validates the ID token, and
extracts the user identity from the configured user_claim (default
sub).
Rules¶
The rules: list maps identifier patterns to profile names. Rules are
evaluated in order; first match wins. An identifier matching no rule is
treated as public — the request is allowed regardless of session state
and no auth service block is added to info.json.
The match shape is intentionally small:
| Form | Example | Meaning |
|---|---|---|
| Exact | restricted |
Identifier equals restricted. |
| Trailing prefix | private/* |
Identifier is private or begins with private/. |
| Catch-all | * |
Every identifier matches. |
Anything richer (regex, content-type checks, time-of-day) belongs in the
Hook.Authorize engine — see Hook integration.
Substitute behavior on deny¶
When a request is denied, the profile's substitute.max_size (an IIIF
size string like !400,400 or ^max) controls what happens next:
- Empty
substitute.max_size— hard deny. The handler writes the HTTP status from theDecision(defaults to 401) and the configuredHeading+Noteare surfaced via the probe service. - Non-empty
substitute.max_size— the IIIF request's size parameter is rewritten to the configured value and the pipeline runs normally against the source. The viewer receives a degraded version of the same image at the request's URL — no redirect. The result is cached under thesubstitutecache tier (see Cache-tier keying).
Hook.Authorize can additionally inject a per-request alternate
identifier via AuthDecision.Substitute. That identifier is
substituted into the request and the pipeline runs against that source
instead — useful for swapping a high-res master for a watermarked
proxy. Hook-supplied identifier substitution takes precedence over the
profile's max_size.
Cache-tier keying¶
The render and info caches are keyed with an auth tier so a denied request never serves bytes that were cached for an allowed user (or vice versa). Three tiers, capped vocabulary:
| Tier | When | Key shape (RenderCache) |
|---|---|---|
public |
Identifier has no matching rule. | {r.URL.Path} — unchanged from pre-auth iiiris. |
full |
Identifier is gated, request is authorized. | {r.URL.Path}\|v1\|full |
substitute |
Identifier is gated, request denied, substitute served. | {r.URL.Path}\|v1\|substitute |
InfoCache follows the same pattern with r.Host + r.URL.Path as the
base. The v1 segment is cacheKeyVersion — bumping it (when the tier
vocabulary changes) cleanly invalidates the cache rather than silently
colliding with old-shape keys.
The public-tier key is byte-identical to the pre-auth key shape, so introducing auth into an existing deployment doesn't churn unrelated cache entries.
Admin dashboard¶
The /admin/auth page surfaces the subsystem read-only: configured
profiles + rules, active session count (with per-profile breakdown
when the session backend supports it), and a bounded ring buffer of
the most-recent 256 allow/deny decisions. The 24h allow/deny tally and
the recent-decisions table are drawn from auth.DecisionLog —
RuleAuthorizer emits one event per Authorize call when the
recorder is wired (which cmd/iiirisd/main.go does whenever an
auth: block is configured). See admin.md.
Token and session lifetimes¶
| Knob | Default | Notes |
|---|---|---|
token_ttl |
5m | Bearer-token lifetime. Set per-profile or globally. The viewer requests a fresh token via /iiif/auth/token whenever its existing one nears expiry. |
session_ttl |
24h | Server-side session lifetime. The session cookie's Expires matches. |
Tokens are opaque random strings, not JWTs — the spec doesn't require JWTs and opaque tokens avoid key-rotation surface area. Sessions and tokens are stored separately so one session can vend many tokens.
Session storage¶
The session store is selected via auth.session.backend. Pick by your
deployment shape:
| You have… | Use | Trade-off |
|---|---|---|
| One replica; losing sessions on restart is fine | heap |
Zero-config, no dependencies. Sessions vanish on every deploy. |
| One replica; sessions must survive restarts, no external deps | filesystem |
Simplest durable store. Single-host only. |
| One replica in an ephemeral/container host (e.g. K8s) | s3 |
Survives pod restarts. Cleanup is by bucket lifecycle policy, not automatic. |
| Multiple replicas | redis |
The only multi-replica-safe backend (atomic session↔token index). Adds a Redis dependency. |
Details for each follow.
heap (default)¶
An in-memory map of sessions and tokens. Zero-config; sessions vanish on restart. The right choice for a single iiirisd process where session loss across deploys is acceptable.
filesystem¶
Sessions and tokens persist as JSON files under a configured root, so
they survive an iiirisd restart. Files are sha-sharded
(<root>/sessions/<aa>/<bb>/<full-hash>.json,
<root>/tokens/<aa>/<bb>/<full-hash>.json) so a directory listing
never has to enumerate the full set. Writes are atomic (temp + rename)
so a concurrent reader never observes a partial record. Expired
entries are deleted lazily on Get and proactively on Sweep.
path: is required; missing or unwritable is a build error so
misconfiguration fails at startup.
Running multiple iiirisd replicas against the same filesystem root is unsupported — each process owns its own in-memory mutex, and concurrent writes to the same file from different processes are not coordinated. Use a single-replica deployment, or wait for a future shared backend (Redis / DB) if you need multi-replica.
s3¶
Sessions and tokens persist as JSON objects under a configured S3 prefix. Suitable for single-instance iiirisd deployments that want persistent sessions without managing local disk (containers in ephemeral filesystems, autoscaling groups whose instance host can change). Strongly read-after-write consistent (S3's posture for new keys), so callers can't observe a torn write.
auth:
session:
backend: s3
s3:
bucket: iiiris-sessions
region: us-east-1
prefix: prod/sessions
endpoint: "" # for MinIO and other S3-compatibles
Object layout: <prefix>/sessions/<sha256>.json and
<prefix>/tokens/<sha256>.json. Object content is the JSON-encoded
record. AWS credentials follow the standard SDK chain (env vars,
shared config, IMDS).
Long-term cleanup is the operator's job — set an S3 lifecycle
policy on the configured prefix (Expiration { Days: N } where N
exceeds your longest session_ttl). iiiris's Sweep is a no-op for
the S3 backend; expired records are still dropped on read by
GetSession / GetToken, but accumulated tombstoned objects need
lifecycle to evict.
Cascade on DeleteSession (logout) lists the tokens prefix and
deletes any token whose SessionID matches the deleted session — one
ListObjectsV2 + one GetObject per token + one DeleteObject per
match. This is bounded by your active-token cardinality (typically
dozens) but it is not free; logout latency is proportional to the
token-prefix size. If the token tree grows to thousands of active
entries, consider a session-indexed sub-prefix or a separate by-session
listing object — the current shape was chosen for symmetry with the
filesystem backend.
redis¶
Sessions and tokens persist as Redis strings (JSON-encoded) under a
configurable prefix. The only backend that supports multi-replica
iiirisd deployments today — Redis's atomic single-key semantics
guarantee concurrent CreateToken from one replica can't race with
DeleteSession's cascade from another.
auth:
session:
backend: redis
redis:
addr: redis.internal:6379
password_env: REDIS_PASSWORD # env-var-resolved
db: 0
tls: true
prefix: iiiris # namespace for shared-Redis deployments
Key layout (with the optional prefix):
{prefix}:sessions:<sha256(id)>— string, JSON Session record{prefix}:tokens:<sha256(value)>— string, JSON Token record{prefix}:session-tokens:<sha256(id)>— set; members are the sha-keys of bound tokens
Native Redis TTL handles expiry — records evict on their own clock,
no iiiris-side sweep needed. A secondary ExpiresAt guard on read
catches the rare clock-drift case where the JSON-encoded expiry is
past but Redis hasn't yet evicted.
DeleteSession (logout) cascades atomically: one SMEMBERS of the
session-tokens set, then a single pipelined DEL removing the
session record, the index, and every bound token. One round trip vs.
the linear scan needed by the filesystem and S3 backends.
CountActive returns the total only (via SCAN). A per-profile
breakdown would require reading every record (same constraint as the
S3 backend) and is intentionally not implemented; deployments that
need it can maintain a side-channel counter via HINCRBY on a
profile-counts hash in their own integration.
The connection is verified with a PING at startup so
misconfiguration fails fast. Connect failures, auth failures, and
TLS handshake errors all surface at boot rather than at the first
session write.
Multi-instance posture¶
Use the redis backend for any deployment with more than one
iiirisd replica. The other backends (heap, filesystem, s3) are
all single-instance only — concurrent processes writing the same
backing store from multiple iiirisd replicas race in ways that can
leak tokens past their session's logout. A single-replica
deployment can use any backend.
htpasswd¶
The active pattern verifies credentials against an htpasswd-format
file or an inline map. Supported hash schemes:
- bcrypt —
$2a$,$2b$,$2y$prefixes. Generated byhtpasswd -B. Recommended. - Apache APR1-MD5 —
$apr1$<salt>$<hash>. Generated byhtpasswd -m. Legacy but ubiquitous.
Plaintext, crypt(3), and SHA-1 entries are explicitly rejected.
When both users: and htpasswd_file: are configured on the same
profile, htpasswd_file: wins (the file is treated as the authoritative
source). The file is mtime-watched: editing it on disk reloads the
user list at the next verification call — no restart required.
Hook integration¶
When to reach for this: use Hook.Authorize when an access decision
needs per-request logic the static Rules globs can't express. A
lua hook sees the identifier and request_path (so it can do regex /
computed matching richer than the rule prefixes); a webhook hook delegates
to your own service, which can factor in anything it knows (time, client,
user claims). Either way it runs after the rule engine and can only
tighten a rule-driven allow (refuse it), never lift a deny.
Example — embargo a sub-collection the prefix rules can't target. Rules allow
archive/*, but anything under anembargoed/segment must be refused:
internal/hook.Hook.Authorize runs after the rule-engine verdict. The hook
can:
- Refuse access (
Allow: false) even when the rules allowed it. - Supply a per-request
Substituteidentifier (overridesprofile.Substitute.MaxSize). - Supply a
RedirectURL (the handler issues a 303). - Override
Profile(informational; used for logging).
The hook cannot lift a rule-driven deny to an allow. This is
deliberate: the common no-opinion hook shape — Noop, an empty Lua
script, a webhook returning {} — defaults to Allow: true per
hooks.md, and we don't want a default-allow hook to
accidentally lift an auth gate set in YAML.
CORS + cookies¶
The session cookie is set with Secure, HttpOnly, and
SameSite=None (the last is required so a viewer on a different origin
can attach the cookie to its iframe-driven probe + token requests).
Path is auto-derived from X-Forwarded-Prefix when iiiris is mounted
under a sub-path behind a proxy; set auth.cookie.path explicitly to
override.
CORS is split by service:
- Probe defaults to wildcard (
Access-Control-Allow-Origin: *) because the probe payload is informational. Narrow it viaauth.cors.probe_origins. - Token is intentionally strict — the postMessage payload contains
a credential.
auth.cors.token_originsmust list the viewer origins that may request tokens. Empty list means no origin is permitted.
info.json advertisement¶
The auth service trees are inserted into the service array of every
gated identifier's info.json (both v2 and v3). Both Auth 2.0 and
Auth 1.0 trees are emitted side-by-side, in that order. Modern
viewers follow the 2.0 tree they prefer; older viewers that only speak
1.0 follow the 1.0 entry.
service[0]: AuthProbeService2 (IIIF Auth 2.0)
└── AuthAccessService2 (omitted for header backends)
├── AuthAccessTokenService2
└── AuthLogoutService2
service[1]: { @context, @id, profile: (IIIF Auth 1.0)
"http://iiif.io/api/auth/1/{pattern}",
label, header, description,
confirmLabel,
service: [ token, logout ] }
Pattern name, label, heading, description, and confirmLabel are
shared across both trees (the v2 entry uses the IIIF JSON-LD language
map shape; the v1 entry uses scalar strings — iiiris collapses the
language map by preferring English, then falling back to any value).
Header-backend profiles advertise the v2 probe service only — no v2 access service (there is no interactive flow for the viewer to initiate) and no v1 entry at all (1.0 has no probe-only mode, so a v1 advertisement without an interactive access service would be ambiguous).
IP allowlist (kiosk) and trusted proxies¶
The kiosk pattern matches the client's IP against
profile.kiosk.allowed_ips (single IPs or CIDRs, v4 or v6). The client
IP is extracted via RealIP, which:
- Reads
RemoteAddrfrom the TCP connection. - If that address is in
auth.trusted_proxies, walksX-Forwarded-Forright-to-left and returns the rightmost hop that is not itself a trusted proxy — i.e. the real client. - Otherwise returns
RemoteAddrdirectly.
This prevents a client that doesn't go through a trusted proxy from
forging X-Forwarded-For. Configure auth.trusted_proxies whenever
iiiris sits behind anything (a load balancer, a CDN, a Kubernetes
ingress) — without it, kiosk allowlists match the proxy's IP, not the
client's.
Validator posture¶
No published upstream validator covers IIIF Auth 2.0 today. The spec
site lists none; the historical github.com/IIIF/auth-validator
repo is 404. In place of an upstream tool, iiiris ships a
project-local conformance harness — tools/iiif-auth-conformance —
that drives the spec-defined endpoints and asserts the response
shapes (@context, type, status, payload fields per
§3–§7 of the spec). The harness is
wired into CI as the iiif-auth-validate stage in .gitlab-ci.yml
and is load-bearing: a regression in any of the four interaction
patterns fails the pipeline.
The CI job builds iiirisd, starts it with the sample multi-profile
config in tools/iiif-auth-conformance/config.yaml, and runs the
harness once per interaction pattern — clickthrough, active,
kiosk, header — capturing the combined report as the
iiif-auth-validation.log artifact.
Each interactive pattern (clickthrough, active, kiosk) runs the same
nine-check sequence (info.json shape, unauth probe, public probe,
login → cookie, authed probe, token postMessage, Bearer-grants-probe,
token error without cookie, logout invalidates session). The login
mechanism varies per pattern: clickthrough = empty POST,
active = POST username+password, kiosk = GET (the client IP must
be in the profile's allowed_ips).
The header backend has no interactive flow and emits a probe-only
advertisement, so it runs a shorter four-check sequence
(advertisement shape, unauth probe, public probe, header-authorized
probe).
external (HMAC-signed callback) and oidc backends are out of
scope for the harness — they require an operator-hosted upstream to
complete the flow. iiiris's side of those contracts is covered by
the unit + integration tests
(internal/auth/service/external_test.go, the OIDC client tests).
When community mocks of the operator side become available, those
flows can be folded back into the harness.
See tools/iiif-auth-conformance/README.md
for the full check list, the spec section references, and how to
run the harness locally.
Posture decision. Strict (no allow_failure). The harness is
project-local rather than community-blessed, so "passing"
ultimately means "passing against iiiris's own definition of
conformance" — but in practice the harness encodes the spec's
response shapes verbatim, the four patterns it covers are stable,
and we'd rather catch a regression at merge time than chase it
later. When a community-blessed Auth 2.0 validator emerges, swap
the binary out of the job; the posture stays the same.
Reference-viewer matrix¶
Smoke-tested against the most-deployed viewers. "✓" means the pattern works end-to-end with iiiris's stock advertisement; missing entries mean we haven't verified, not that they fail.
| Viewer | clickthrough | active | kiosk | external (OIDC) |
|---|---|---|---|---|
| Mirador 3.x | ✓ | ✓ | ✓ | ✓ |
| Universal Viewer 4.x | ✓ | ✓ | — | ✓ |
| Clover | ✓ | — | ✓ | — |
When you verify a new combination, please extend this table in the same PR — the matrix is the operator's quickest signal for what's expected to work.
Migration from Auth 1.0¶
iiiris emits both v1 and v2 service trees, so a viewer that speaks
either version finds a working path. Same cookie, same session store,
same login handler under the hood — the access service at
/iiif/auth/login/{profile} is identical whether the viewer found it
via the 2.0 advertisement or via the 1.0 advertisement at
/iiif/auth/v1/login/{profile}. The split lives only in the advertised
service tree and in the token service's response shape.
For migrating an existing 1.0 deployment:
- Pick the closest Auth 2.0 pattern for each existing 1.0 flow —
clickthrough →
clickthrough, login →active, kiosk →kiosk, external →external. iiiris advertises both versions automatically; you only configure once. - Update operator-side login pages if any; the redirect and callback
shapes for
externaldiffer between 1.0 and 2.0 deployments. The iiiris-side handlers (/iiif/auth/login/...and/iiif/auth/v1/login/...) are the same — the difference is on the operator's auth service. - Re-test viewers. Modern viewers (recent Mirador, Universal Viewer 4.x, Clover) prefer the 2.0 tree; older 1.0-only viewers follow the 1.0 tree.
- The session cookie name is
iiiris_sessionby default — pick a different name viaauth.cookie.nameif your existing reverse-proxy stack already uses it.
Things deferred¶
- Distributed session storage (filesystem / S3 / Redis). Heap-only today; multi-replica deployments are out of scope.
- Per-prefix profile routing as a built-in. The Lua engine already
supports this via
Hook.Authorize; the built-inrules:list is intentionally small. - Built-in login template hot-reload. Override is parsed once at startup; restart after editing.
- JWT tokens. Tokens are opaque random strings. No
token_strategy:knob is reserved.
Tests¶
| File | Covers |
|---|---|
internal/auth/rules_test.go |
Rule matching (exact / prefix / catch-all). |
internal/auth/authorizer_test.go |
RuleAuthorizer.Authorize verdict matrix; hook interaction. |
internal/auth/build_test.go |
YAML → built profile/rule/session construction; backend validation. |
internal/auth/advertisement_test.go |
Service-tree shape (both v2 and v1, including header-backend omission and pattern → v1-profile-URI mapping). |
internal/auth/cachekey_test.go |
Tier-keyed cache key shapes; public-tier byte-identity. |
internal/auth/realip_test.go |
X-Forwarded-For parsing under trusted/untrusted proxies. |
internal/auth/htpasswd/* |
bcrypt and APR1 verification; file mtime watch; rejection of plaintext/SHA-1/crypt. |
internal/auth/service/* |
Probe envelope; login flow per backend; v2 token postMessage; v1 token postMessage (v1-shape payload + error keys); logout; external HMAC verification; kiosk IP match; mux registration covering all v2 + v1 routes. |
internal/auth/session/heap_test.go |
LRU eviction; expiry sweep; concurrent access. |
internal/server/auth_backends_test.go |
End-to-end backend flows behind the real server. |
internal/server/auth_integration_test.go |
Full request lifecycle (image + info.json + probe + token) per pattern. |