Minimal, secure, and composable cookie builder for Go.
A tiny, well‑tested builder for constructing validated net/http cookies with secure defaults.
It helps you set Path, Domain, Expires/MaxAge, Secure/HttpOnly, SameSite, and Partitioned coherently while enforcing RFC‑style validation and common security constraints (e.g., SameSite=None requires Secure=true).
Crum’s CookieBuilder produces an *http.Cookie with safe defaults and consistent behavior:
- Defaults: Path="/", Secure=true, HttpOnly=true, SameSite=Lax, session cookie (no persistence) unless you opt in.
- Validation: strict name token; rejects control chars, semicolons, commas, and backslashes in values; ASCII enforcement unless you pass a pre‑encoded value.
- Coherent persistence:
TTL()orMaxAgeSeconds()set both MaxAge and Expires;Session()clears them;Delete()sets a past Expires and negative MaxAge and clears the value. - Cross‑site safety:
SameSiteNone()also enforcesSecure(true). - Quality of life: normalize Path (empty → "/"), strip leading dot in Domain.
FromCookieto transform existing cookies.
Use standard Go module tooling:
go get github.com/struct0x/crumThen import it:
package main
import "github.com/struct0x/crum"Here are common scenarios distilled from the public API and tests.
- Session cookie with secure defaults:
package main
import (
"github.com/struct0x/crum"
)
func main() {
c := crum.NewCookie("sid", sessionID).MustBuild()
// Name=sid, Path=/, Secure, HttpOnly, SameSite=Lax; no Expires/MaxAge
}- Persistent cookie (30 days), strict same‑site:
package main
import (
"github.com/struct0x/crum"
)
func main() {
c, err := crum.NewCookie("remember", token).
TTL(30 * 24 * time.Hour).
SameSiteStrict().
Build()
if err != nil { /* handle */
}
} - Cross‑site flow: SameSite=None (forces Secure=true) with 24h TTL:
package main
import (
"time"
"github.com/struct0x/crum"
)
func main() {
c, err := crum.NewCookie("cs", v).
SameSiteNone().
TTL(24 * time.Hour).
Build()
}- Set Domain and normalize Path:
package main
import (
"github.com/struct0x/crum"
)
func main() {
c := crum.NewCookie("k", "v").
Path(""). // normalized to "/"
Domain(".example.com"). // leading dot stripped → example.com
MustBuild()
}- Delete a cookie previously set (clear value, expire immediately):
package main
import (
"github.com/struct0x/crum"
)
func main() {
c := crum.NewCookie("sid", "").Delete().MustBuild()
}- Infer persistence from absolute time:
package main
import (
"github.com/struct0x/crum"
)
func main() {
c := crum.NewCookie("k", "v").
Expires(time.Now().Add(2 * time.Hour)).
MustBuild()
// MaxAge inferred if omitted
}- Start from an existing cookie and transform it:
package main
import (
"github.com/struct0x/crum"
)
func main() {
c2 := crum.FromCookie(c1).Delete().MustBuild()
}- Advanced/testing: deterministic clock or pre‑encoded value:
package main
import (
"time"
"github.com/struct0x/crum"
)
func main() {
c := crum.NewCookie("k", "v").WithClock(func() time.Time { return fixed }).TTL(time.Hour).MustBuild()
c := crum.NewCookie("tok", "").UnsafeValue("YWJjMTIzXy0u~").MustBuild()
}- Partitioned cookies:
package main
import (
"github.com/struct0x/crum"
)
func main() {
c := crum.NewCookie("k", "v").Partitioned(true).MustBuild()
}- Construction:
NewCookie(name, value),FromCookie(*http.Cookie) - Persistence:
TTL(d),MaxAgeSeconds(s),Expires(t),Session(),Delete() - Attributes:
Path(p),Domain(d),Secure(on),HttpOnly(on),SameSiteStrict(),SameSiteLax(),SameSiteNone(),Partitioned(on) - Testing/util:
WithClock(fn),UnsafeValue(v) - Finalize:
Build() (*http.Cookie, error),MustBuild() *http.Cookie
Validation highlights enforced by Build():
- Non‑empty name; strictly validated token (ASCII; no spaces or separators).
- Value must be ASCII; no control chars, semicolons, commas, or backslashes. Use your own encoding (e.g., URL/base64) for non‑ASCII payloads.
SameSite=NonerequiresSecure=true.
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.