Abstract
Better-Goth is a better-auth alternative, written purely in Go. It provides a complete OAuth 2.1 and OpenID Connect implementation as an embeddable library, giving you full control over your authentication flow without external dependencies or managed services.
Purely exists because golang cool and js ugly and we wanted to learn about OAuth.
Useful if you want a simple plug-and-play library for your web-app for both custom OAuth Providers and self-deployable OAuth server. Can be easily incorporated as it uses built-in net/http
Status of This Memo
This library is production ready.
1. Design Decisions
Better-Goth was made as a library so the user keeps complete control over what the application needs, and so wiring it in stays easy.
A few choices follow from that:
- It embeds into your app instead of running next to it. You mount an
http.Handleron your own mux, so there is no second service to deploy, patch, or keep alive and is also compatible with all backend frameworks. dev_mode: truedowngrades cookies for plain-HTTP localhost work.
2. How It Works
Three public files carry most of the weight:
| File | Job |
|---|---|
auth.go |
Social/OIDC login. Builds the redirect with PKCE, checks state and nonce via short-lived cookies, verifies the upstream ID token, mints your session JWT |
embed.go |
The embedded authorization server. Users, password reset over SMTP, access/device/recovery tokens, refresh rotation |
verify.go |
Bearer verification and the RequireAuth middleware that drops a VerifiedUser into the request context |
Under internal/ sit the oauth server handlers, key manager, storage, and mailer. The endpoints are:
GET /authorize start the OAuth flow
POST /oauth/token code exchange & refresh
POST /oauth/token/revocation RFC 7009 revocation
POST /oauth/token/introspection RFC 7662 introspection
GET /.well-known/openid-configuration OIDC discovery
GET /.well-known/jwks.json signing keys
GET /userinfo authenticated claims
GET /admin/rotate key rotation
Social login runs on two routes:
GET /login/{provider} redirect to provider with PKCE + state + nonce
GET /callback/{provider} validate, exchange, verify, issue session
Flow cookies are HttpOnly, SameSite=Lax, and short lived. The code verifier gets five minutes, and each cookie clears after one use.
What you can override: SetUserHandler runs after every successful login (sync the user into your own DB there), SetAuthResultHandler decides what happens after auth (the built-in session handler sets a cookie and redirects), and storage.type in config swaps the backing store.
3. Quickstart
go get github.com/Protofarm/better-goth
package main
import (
"log"
"net/http"
bettergoth "github.com/Protofarm/better-goth"
)
func main() {
rt, err := bettergoth.Setup("config.yaml")
if err != nil {
log.Fatal(err)
}
defer rt.Close()
mux := http.NewServeMux()
// oauth/oidc endpoints: authorize, token, jwks, userinfo...
mux.Handle("/", rt.Handler())
// social login: GET /login/{provider}, GET /callback/{provider}
bettergoth.RegisterRoutes(mux, rt.Auth)
// anything behind RequireAuth gets a VerifiedUser in context
protected := rt.Auth.RequireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, _ := bettergoth.UserFromContext(r.Context())
bettergoth.WriteJSON(w, http.StatusOK, map[string]string{
"hello": user.Subject,
})
}))
mux.Handle("/api/me", protected)
log.Fatal(http.ListenAndServe(rt.ListenAddr, mux))
}
And a config to setup both the oauthserver and google oauth.
app:
port: "3000"
dev_mode: true
providers:
oauthserver:
enabled: true
issuer_url: "http://localhost:8080"
client_id: "my-client"
client_secret: "my-secret"
redirect_uris:
- "http://localhost:3000/callback/oauthserver"
google:
enabled: true
auth_url: "https://accounts.google.com"
client_id: "${GOOGLE_CLIENT_ID}"
client_secret: "${GOOGLE_CLIENT_SECRET}"
redirect_uri: "http://localhost:3000/callback/google"
jwt:
secret: "replace-with-at-least-32-bytes-secret"
storage:
type: "memory"
examples/ shows just this.
4. Standards & Compliance Notes
The RFC list:
RFC 6749: the core framework. Authorization code flow, token exchange, refresh grants.OAuth 2.1(draft): PKCE everywhere, nothing implicit. We target this over 2.0 so the secure path is the only path.RFC 7636: PKCE withS256challenges. The verifier never leaves a short-lived cookie.RFC 7009: token revocation at/oauth/token/revocation.RFC 7662: introspection at/oauth/token/introspection, useful for resource servers.RFC 7517: JWKS at/.well-known/jwks.jsonso clients can verify signatures themselves.RFC 7523: JWT bearer profile, shipped asprivate_key_jwtclient authentication.RFC 7591: dynamic client registration.RFC 9700: the security BCP.
5. Roadmap & Limitations
- Caching uses only default in-memory maps and redis implementation is pending (Check out Rsdis)
6. See Also
- Repository: https://github.com/Protofarm/better-goth
- Go reference: https://pkg.go.dev/github.com/Protofarm/better-goth
- Example app: https://github.com/Protofarm/better-goth/tree/main/examples