Cookie Domain Resolution#
Overview#
Tinyauth determines the cookie domain once at startup and stores it in RuntimeConfig.CookieDomain . The domain controls the scope of session cookies and directly affects whether a single login works across subdomains. The behavior is governed by a single config flag: auth.subdomainsEnabled (default: true) .
Core Logic: GetCookieDomain#
All resolution is handled by GetCookieDomain in internal/utils/app_utils.go. Given the AppURL and the subdomainsEnabled flag, the function:
- Parses and lowercases the hostname from the app URL.
- Rejects IP addresses β cookies cannot be scoped to an IP .
- Requires at least two domain labels β single-label hostnames like
localhostare rejected . - Strips the leftmost label when
subdomainsEnabled=trueand the hostname has 3+ labels β e.g.,auth.example.comβ cookie domainexample.com, enabling shared cookies across all subdomains . - Validates against the Public Suffix List (via
github.com/weppos/publicsuffix-go) β prevents cookies from being set on a public suffix likeco.ukorgithub.ioin both modes .
When subdomainsEnabled=false (or the hostname has exactly 2 labels), the full hostname is returned as-is β cookies are scoped to that exact host only .
Bootstrap Wiring#
During startup, app_bootstrap.go calls GetCookieDomain and stores the result in runtime.CookieDomain. If subdomainsEnabled is false, a warning is logged: "Subdomains are disabled, cookies will be set for the current domain only" .
AuthService reads runtime.CookieDomain via the private getCookieDomain() helper, which returns an empty string (no domain attribute on the cookie) when subdomains are disabled, or the pre-computed domain otherwise. This domain is applied to every session cookie: creation, refresh, and deletion .
Modes at a Glance#
subdomainsEnabled | Hostname example | Cookie domain set to |
|---|---|---|
true (default) | auth.example.com | example.com |
true | auth.sub.example.com | sub.example.com |
true | example.com (2 labels) | example.com (no strip) |
false | auth.example.com | "" (host-only, no Domain attribute) |
Setting subdomainsEnabled=false was introduced in PR #710 to support running Tinyauth on a standalone or top-level domain (e.g., as a dedicated OIDC provider). The single GetCookieDomain function handling both modes was consolidated in PR #950.
Public Suffix List Validation#
The PSL check runs in both modes. If the resolved domain (or hostname) is itself a public suffix β e.g., github.io or co.uk β GetCookieDomain returns an error and startup fails .
Private/internal TLDs like .home, .lan, or .internal are not in the Public Suffix List, so they pass validation normally. A known limitation: 2-part private hostnames (e.g., tinyauth.home) are not stripped further even when subdomain mode is enabled, which can prevent cross-subdomain cookie sharing in private DNS environments .
Domain Validator Utility (pkg/validators)#
PR #1000 introduced a separate DomainValidator in pkg/validators/domain_validator.go. This is not used for cookie domain resolution β it is used for OAuth redirect URI validation and ACL domain matching. Key behaviors :
- Normalizes hostnames: lowercases, removes trailing dot, converts to ASCII via IDNA (
golang.org/x/net/idna) - Rejects IP addresses
- Optionally enforces scheme and port matching
- Errors:
ErrSchemeMismatch,ErrPortMismatch,ErrHostnameMismatch,ErrInvalidURL
Key Files and References#
| File | Purpose |
|---|---|
internal/utils/app_utils.go | GetCookieDomain β core resolution logic |
internal/bootstrap/app_bootstrap.go | Startup: computes and stores runtime.CookieDomain |
internal/model/runtime.go | RuntimeConfig.CookieDomain field |
internal/model/config.go | auth.subdomainsEnabled config field (default true) |
internal/service/auth_service.go | getCookieDomain() helper; applies domain to all session cookies |
pkg/validators/domain_validator.go | Domain equality/redirect-URI validation (separate from cookie logic) |