Skip to content
HN On Hacker News ↗

GitHub - go-dev-auth/go-dev-auth: go-dev-auth The comprehensive authentication library for Go. One config struct. Batteries included. Own your users.

▲ 13 points • 6 comments • by BisratMelak • 4w ago • HN discussion ↗

Pangram verdict · v3.3

We believe this text is mainly AI, with some human-written content.

97 %

AI likelihood · overall

AI
2% human-written 98% AI-generated
SEGMENTS · HUMAN 0 of 1
SEGMENTS · AI 1 of 1
WORD COUNT 1,330
PEAK AI % 98% · §1
Analyzed
Oct 11
backend: pangram/v3.3
Segments scanned
1 windows
avg 1330 words each
Distribution
2 / 98%
human / AI fraction
Verdict
AI
Pangram v3.3

Article text · 1,330 words · 1 segments analyzed

Human AI-generated
§1 AI · 98%

The CI badge covers the whole matrix on every push: build and tests on the oldest supported and current Go, the race detector, lint, a fuzz pass over the attacker-facing parsers, and the storage conformance suite against real SQLite, PostgreSQL and MySQL servers. A comprehensive, framework-agnostic authentication library for Go, modeled after better-auth. Email & password, social sign-on, sessions, account linking, two-factor auth, passkeys, magic links, organizations, SSO, admin tooling, API keys and JWT — with zero external dependencies (pure standard library). go get github.com/go-dev-auth/go-dev-auth Status: pre-1.0, in production use, heading to v1.0. The library runs in production in a real automation project on PostgreSQL, using the full feature set. Releases follow SemVer: while pre-1.0 the public API may still change between minor versions (pin an exact version); v1.0 freezes it, and from then on breaking changes require a major version. The remaining v1.0 gates are runnable examples, field mileage on the newly automated MySQL leg, and a third-party security review. See storage adapters for exactly which backends are verified where, and docs/security-model.md for the threat model. Features Core Email & password authentication (scrypt hashing, compatible with better-auth's hash format) Social sign-on via OAuth 2.0 / OIDC with PKCE — Google, GitHub, Discord, Facebook, Microsoft, Apple, GitLab, LinkedIn, Spotify, Twitch, X built in, plus a declarative oauth2.Spec for any custom provider Database-backed sessions with sliding expiration, signed cookies, optional cookie caching, list/revoke endpoints Email verification, password reset, change email/password, delete user flows Account linking & unlinking (trusted providers, token refresh, account info) CSRF origin checking, trusted origins with wildcard subdomains, IP-based rate limiting Storage adapter interface with built-in database/sql (Postgres, MySQL, SQLite) and in-memory adapters, plus schema/migration SQL generation Request hooks, database hooks, custom user/session fields Plugins (mirroring better-auth's plugin system) twofactor — TOTP, email OTP and backup codes passkey — WebAuthn passkeys, CBOR/COSE parsing included (no external dependency) magiclink — passwordless email links organization — orgs, members, roles, invitations, teams sso — bring-your-own OIDC identity provider, matched by email domain admin — user management, bans, roles, impersonation apikey — hashed API keys with scopes that authenticate like sessions jwt — EdDSA-signed JWTs + JWKS endpoint bearer — Authorization header auth for non-browser clients Quick start package main import ( "context" "net/http" "os" "time" godevauth "github.com/go-dev-auth/go-dev-auth" "github.com/go-dev-auth/go-dev-auth/storage/memory" ) func main() { auth, err := godevauth.New(godevauth.Config{ BaseURL: "http://localhost:8080", // required Secret: os.Getenv("AUTH_SECRET"), // required, 32+ random chars Database: memory.New(), // required EmailAndPassword: godevauth.EmailPasswordConfig{Enabled: true}, // Required in production if anything sits in front of this // process (load balancer, ingress, CDN). See below. Advanced: godevauth.AdvancedConfig{ TrustProxyHeaders: true, TrustedProxies: []string{"10.0.0.0/8"}, }, }) if err != nil { panic(err) } // sweep expired one-time tokens and sessions defer auth.StartCleanup(context.Background(), time.Hour)() mux := http.NewServeMux() mux.Handle("/api/auth/", auth.Handler()) http.ListenAndServe(":8080", mux) } The handler is a plain http.Handler, so it mounts on chi, echo, gorilla, or gin (via gin.WrapH) the same way. If your frontend runs on a different origin, wrap it: auth.CORS(auth.Handler()) and list that origin in TrustedOrigins. New validates the configuration and returns an error rather than starting up in an unsafe state: a missing/short Secret, a missing or relative BaseURL (it decides cookie security, trusted origins and redirect targets), SameSite=none without secure cookies, or TrustProxyHeaders without TrustedProxies are all rejected. Deployment requirement: tell it about your proxy Rate limiting and every recorded session IP depend on resolving the client's address. If anything terminates the connection in front of this process — an AWS ALB, nginx, Cloudflare, a Kubernetes ingress — you must say so, because both defaults are wrong in a different direction: Configuration What happens Nothing set, but running behind a proxy Every request resolves to the proxy's address, so all clients share one bucket. The documented strict rule of 3 sign-ins per 10 seconds becomes 3 per 10 seconds for your whole fleet, and the limiter fails closed. This is a self-inflicted outage. TrustProxyHeaders with no TrustedProxies X-Forwarded-For is client-supplied. Any caller picks its own bucket and the limit stops limiting anything. TrustProxyHeaders + TrustedProxies Correct. Forwarded headers are read only from a listed peer, and the header chain is walked right-to-left past trusted hops, so addresses a client prepended are ignored. So: Advanced: godevauth.AdvancedConfig{ TrustProxyHeaders: true, // IPs or CIDRs of your load balancers / ingress pods. TrustedProxies: []string{"10.0.0.0/8", "192.168.0.0/16"}, // Or, for a provider-specific header: // IPAddressHeaders: []string{"CF-Connecting-IP"}, }, New refuses to start if TrustProxyHeaders (or IPAddressHeaders) is set without TrustedProxies. Use TrustedProxies: []string{"*"} to trust any peer — that is the old, spoofable behaviour, and it is only safe when the network guarantees the service is unreachable except through a proxy that overwrites the header. The opposite mistake cannot be caught at startup, because it depends on the traffic. The first request that arrives with a forwarded header while proxy headers are untrusted logs an error naming the header and the peer. If you see it in production, you are in row one of that table. Direct-to-internet deployments need none of this: leave all three unset. Storage adapters Adapter Import Notes In-memory storage/memory Tests, examples, single-process. Enforces unique constraints and indexes unique fields. SQL storage/sqlstore PostgreSQL, MySQL, SQLite via database/sql. Bring your own driver. MongoDB storage/mongostore Official mongo-go-driver. Separate Go module. All adapters are written against the same conformance suite (storage/storagetest), which pins the semantics the auth core depends on: unique-violation reporting, NULL vs zero, chronological sorting, clause folding, literal case-insensitive substring matching and compare-and-set update counts. What has actually been executed, as of this revision: SQLite — the full conformance suite and a complete HTTP auth flow run against a real SQLite database in CI on every push. Verified. PostgreSQL — running in production (a real automation project uses the whole library on live PostgreSQL), and the conformance suite, migration idempotence and the full HTTP auth flow now run against a real PostgreSQL server in CI on every push. Field-proven and regression-guarded. MySQL — the same CI job runs the conformance suite, migrations and the full auth flow against a real MySQL 8 server on every push. CI-verified; no field mileage yet, so report anything you hit. MongoDB — exercised against an in-house wire-protocol test double, not a live mongod. Beta. The CI legs cannot pass vacuously: the environment sets REQUIRE_DSN=1, which turns a missing database into a test failure rather than a skip. If you write your own adapter, run that suite against it: func TestConformance(t *testing.T) { storagetest.Run(t, func(t *testing.T) (storage.Adapter, func()) { return myAdapter(storagetest.Schema()), func() {} }) } MongoDB import ( "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "github.com/go-dev-auth/go-dev-auth/storage/mongostore" ) client, err := mongo.Connect(options.Client().ApplyURI(os.Getenv("MONGODB_URI"))) store := mongostore.New(client.Database("myapp")) auth, err := godevauth.New(godevauth.Config{Database: store, /* ... */}) New creates the indexes the schema implies, including the unique ones. That step is not cosmetic on MongoDB: collections are created lazily, so an instance without them looks healthy while every "does this email already exist?" check degrades into a race. Use Advanced.DisableAutoMigrate only if you create them in a separate deploy step. The adapter maps the id field onto Mongo's _id, rejects composite values where MongoDB would read them as operators (the NoSQL-injection shape), escapes regex metacharacters in substring searches, and reports duplicate keys as storage.ErrUniqueViolation. Transaction needs a replica set or mongos, as MongoDB itself does. Using a real database import ( "database/sql" "github.com/go-dev-auth/go-dev-auth/storage/sqlstore" _ "github.com/jackc/pgx/v5/stdlib" // bring your own driver ) db, _ := sql.Open("pgx", os.Getenv("DATABASE_URL")) store := sqlstore.New(db, sqlstore.Postgres, nil) auth, _ := godevauth.New(godevauth.Config{ Secret: os.Getenv("AUTH_SECRET"), Database: store, // ... }) // pick up the plugin tables *and columns*, then apply them store.SetSchema(auth.Schema()) if err := store.Migrate(context.Background()); err != nil { ... } // or hand the DDL to your own migration tool: fmt.Println(store.MigrationSQL()) // the whole schema, from nothing pending, _ := store.PendingMigrationSQL(ctx) // only what this database lacks Migrate is idempotent and does two things: it creates missing tables and indexes, and it adds missing columns to tables that already exist. The second matters as soon as the database has data in it — see Migrations. Social sign-on import ( "github.com/go-dev-auth/go-dev-auth/oauth2" "github.com/go-dev-auth/go-dev-auth/providers" ) godevauth.Config{ SocialProviders: []oauth2.Provider{ providers.Google(providers.Credentials{ ClientID: os.Getenv("GOOGLE_CLIENT_ID"),