Skip to main content

Session strategies

goauth supports two session strategies, matching Auth.js: JWT (stateless encrypted cookie) and database (adapter-persisted sessions).

Automatic selection

ConditionStrategy
No AdapterJWT
Adapter set, no credentials providerDatabase
Any CredentialsProviderJWT (forced)
Explicit Session.StrategyOverrides 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.