Session strategies
goauth supports two session strategies, matching Auth.js: JWT (stateless encrypted cookie) and database (adapter-persisted sessions).
Automatic selection
| Condition | Strategy |
|---|---|
No Adapter | JWT |
Adapter set, no credentials provider | Database |
Any CredentialsProvider | JWT (forced) |
Explicit Session.Strategy | Overrides the above |
// Force database even with credentials (invalid — New() will error)
// Credentials always require JWT per Auth.js compatibility
:::caution Credentials + database
goauth.New returns an error if you configure credentials with database strategy. Password sign-in always uses encrypted JWT cookies (or bearer tokens).
:::
JWT strategy
- Session payload is a JWE in the session cookie (
goauth/jwt,dir+A256CBC-HS512). - No server-side session row — revocation is limited to cookie expiry unless you implement a denylist in callbacks.
- Ideal for: serverless, minimal infra, credentials-only apps.
auth, _ := goauth.New(goauth.Config{
Secret: []string{secret},
Providers: []goauth.Provider{credentials.New(...)},
// No adapter → JWT
})
JWT callback enrichment
Callbacks: goauth.Callbacks{
JWT: func(ctx context.Context, p goauth.JWTCallbackParams) (goauth.JWT, error) {
if p.User != nil {
p.Token["roles"] = []string{"member"}
}
return p.Token, nil
},
},
Claims appear on session.Roles(), session.Claim(), etc.
Database strategy
- Adapter stores a session row with a random
session_token(UUID v4 by default). - Cookie holds the opaque token; server loads user on each request.
- Required for: email magic links, OTP, passkeys (with adapter), session list/revoke.
adapter := postgres.New(db)
auth, _ := goauth.New(goauth.Config{
Secret: []string{secret},
Adapter: adapter,
Providers: []goauth.Provider{github.New(id, secret)},
})
Session ID (UUID v4)
Token responses include a stable sessionId claim (UUID v4) for client-side correlation — distinct from user.id.
{
"sessionId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"user": { "id": "42", "email": "you@example.com" }
}
Simple example: read session
session, err := auth.GetSession(w, r)
if session == nil {
http.Error(w, "login required", http.StatusUnauthorized)
return
}
fmt.Println(session.User.Email)
Advanced: strategy override + custom session token
Session: goauth.SessionConfig{
Strategy: goauth.StrategyDatabase,
MaxAge: 7 * 24 * time.Hour,
GenerateSessionToken: func() string {
return mySecureRandomToken()
},
},
MFA and sessions
MFA delays session creation until POST /auth/mfa/verify. The challenge is a short-lived JWE embedding sign-in params — not a full session yet.
Passkeys and sessions
Passkey sign-in uses the same completeSignIn path as OAuth. With an adapter, strategy is database; session row is created after successful WebAuthn verification.