Per-Application Access Control#
Tinyauth supports granular access control at the individual application level. Each proxied application can have its own rules for domain binding, user/group allowlists, IP filtering, path-based auth bypass, and response header injection. These rules are evaluated by a pluggable policy engine on every incoming proxy request.
The per-app configuration is modeled by the App struct, which contains seven control sub-structs:
| Sub-struct | Purpose |
|---|---|
AppConfig | Domain binding |
AppUsers | Username allow/block lists |
AppOAuth | OAuth email whitelist and group requirements |
AppLDAP | LDAP group requirements |
AppIP | IP/CIDR allow, block, and bypass lists |
AppPath | Regex-based path allow/block (auth bypass) |
AppResponse | Custom response headers and upstream basic auth injection |
App configs are loaded from two sources β static YAML (Config.Apps, a map[string]App) and dynamic label providers (Docker container labels or Kubernetes ingress annotations). Static config takes priority; the label provider is the fallback .
Domain Mapping#
Each app entry is matched to an incoming request host using AccessControlsService.GetAccessControls(domain). The lookup uses a two-tier strategy inside lookupStaticACLs:
- Exact domain match β compares the incoming host against
AppConfig.Domainusing aDomainValidatorthat normalizes both sides (lowercase, IDNA, trailing-dot removal). - Subdomain name match (fallback) β if no exact match, checks whether the incoming host starts with
<app-name>.(e.g.,myapp.example.commatches an app key namedmyapp).
AppConfig.Domain is optional. If omitted, only the subdomain name-prefix rule applies. When a label provider is active and no static match is found, the provider's GetLabels(domain) is called instead.
Label format (Docker/Kubernetes): tinyauth.apps.<app-name>.config.domain: "example.com". All other ACL fields follow the same prefix pattern: tinyauth.apps.<app-name>.<field>.<subfield>.
Policy Engine and Rule Evaluation#
The PolicyEngine runs registered rules against an ACLContext that carries the matched App, authenticated user, client IP, and request path. Each rule returns EffectAllow, EffectDeny, or EffectAbstain; abstain falls back to the global policy (allow by default, configurable via Auth.ACLs.Policy) .
proxyHandler evaluates rules in this order:
RuleIPBypassedβ if the client IP is in the bypass list, skip auth entirely and respond 200.RuleAuthEnabledβ if the request path matches an allow/block path pattern that bypasses auth, respond 200 without authentication.RuleIPAllowedβ if the client IP is blocked or not in an allow list, redirect to/unauthorized.RuleUserAllowedβ if the authenticated user is blocked or not in an allow list, redirect to/unauthorized.RuleOAuthGroup/RuleLDAPGroupβ if the user belongs to a required group, allow; otherwise redirect.
Note: Group rules (
RuleOAuthGroup,RuleLDAPGroup) and IP rules always default toEffectAllowwhen no groups/IPs are configured on the app, to avoid breaking deny-policy setups .
The rule implementations live in access_controls_rules.go.
User and Group Restrictions#
Local users β AppUsers accepts comma-separated Allow and Block lists. Block is checked first: a match immediately denies. If no block list is set, the allow list is checked; an empty allow list abstains (falls back to global policy) .
OAuth users β AppOAuth.Whitelist filters by email address. AppOAuth.Groups requires group membership (evaluated by RuleOAuthGroup) . If no groups are configured, the rule allows by default.
LDAP users β AppLDAP.Groups requires membership in one of the listed LDAP groups (evaluated by RuleLDAPGroup) . If no groups are configured, the rule allows by default.
All list fields are comma-separated strings. Label equivalents: tinyauth.apps.<name>.users.allow, tinyauth.apps.<name>.oauth.groups, tinyauth.apps.<name>.ldap.groups.
IP Filtering#
AppIP provides three lists, each accepting IPs or CIDR ranges:
Allowβ only listed IPs may pass the IP check.Blockβ listed IPs are denied before allow evaluation.Bypassβ listed IPs skip authentication entirely (no session required).
Per-app lists are merged with the global Auth.IP lists at evaluation time , so a global block applies to all apps even if the app has its own allow list.
Important: IP filtering is only enforced when
Auth.TrustedProxiesis configured. Without trusted proxies,RuleIPAllowedabstains (allows all) andRuleIPBypasseddenies (never bypasses), since Tinyauth cannot reliably determine the real client IP behind a Docker bridge .
Label equivalent: tinyauth.apps.<name>.ip.allow, tinyauth.apps.<name>.ip.block, tinyauth.apps.<name>.ip.bypass.
Path-Based Controls#
AppPath allows certain request paths to bypass authentication entirely, via Go regular expressions:
Blockβ paths matching this regex are not bypassed (auth proceeds normally).Allowβ paths matching this regex bypass authentication.
AuthEnabledRule evaluates block before allow: if Block is set and the path does not match, auth is skipped. If Allow is set and the path matches, auth is skipped. Otherwise authentication is required.
Common use case: exposing a public health-check endpoint (/health) while protecting all other routes.
Label equivalent: tinyauth.apps.<name>.path.allow, tinyauth.apps.<name>.path.block.
Response Customization#
AppResponse lets you inject data into the response that Tinyauth sends back to the reverse proxy (which then forwards it upstream):
Headersβ a list of"Key=Value"strings. Each is parsed byutils.ParseHeaders, sanitized to printable ASCII, and set on the response.BasicAuthβ injects anAuthorization: Basic <base64>header upstream, effectively acting as a credential translator. Supports an inlinePasswordor aPasswordFilepath for secret management .
setHeaders() is called only on successful authorization β after IP bypass, path bypass, or full authentication + ACL pass. It is not called on denied requests.
Upstream services also receive standard identity headers set by Tinyauth on successful auth: Remote-User, Remote-Name, Remote-Email, Remote-Groups, and Remote-Sub .
Label equivalent: tinyauth.apps.<name>.response.headers, tinyauth.apps.<name>.response.basicauth.username, tinyauth.apps.<name>.response.basicauth.password.