sessions table, a signed cookie, or a gorilla/scs style store). It replaces the auth plumbing with theauth-go in steps you can ship one at a time. Nothing here needs a flag day.
What you get, and what changes
Sessions are server side and opaque. If you currently issue JWTs or signed cookies, existing sessions cannot be honored by theauth-go, so plan for a one-time re-login (step 5).
1. Pick a storage backend
For a single host, usestorage/sqlite (Go 1.26). For several instances or organizations, use Postgres. Check Capability interfaces for the features you need, then create the new tables. theauth-go owns tables prefixed theauth_ by default, so they cannot collide with yours:
sqlitestore.Migrations() or RenderMigrations() and fold the SQL into your numbered history instead.
2. Import your users
Create a theauth user per existing row, keeping the email and verified state, then attach the password hash. Use the storage adapter directly, since theauth-go has no public bulk-import API:u.ID (a ULID) and migrate the foreign keys in your own tables.
3. Keep old password hashes working
Hashes in Argon2id PHC form ($argon2id$...) verify as is. bcrypt hashes ($2a$, $2b$, $2x$) verify only while the migration flag is on, and are upgraded to Argon2id on each successful login:
4. Swap the middleware
Mount the auth routes and replace your session lookup:theauth.UserFromContext(r.Context()) returns the user. If your handlers read a user from your own context key, write one small adapter middleware that calls UserFromContext and sets your key, so handler code does not change. Route by route is fine: RequireAuth and your old middleware can coexist while you move routes over.
Replace your own login, logout and signup handlers with /auth/email-password/signup, /auth/email-password/signin and DELETE /auth/sessions/current. Point your form at the new routes. Set CookieName if you want a name other than theauth_session.
5. Cut over sessions
- Keep your old session table read-only for a short window if you want a grace period: a middleware that accepts either cookie, and for an old-cookie request calls
a.IssueSessionByUserID(ctx, userID, userAgent, ip), which returns a raw session token, and sets it as the cookie value yourself (cookie nameCookieName,HttpOnly,SameSite=Lax,Secureover https). Delete that code after your longest old session lifetime. - Or force a re-login: delete the old cookie, drop the table, and let users sign in once.
SessionTTL (absolute, default 24h) and, if you had idle expiry, SessionIdleTimeout (needs SessionManagementStorage, which memory and SQLite have).
6. Replace hand-written checks
- “Re-enter your password for this action”:
a.RequireRecentAuth(5*time.Minute)on the route, and have the client callPOST /auth/step-upfirst. - “Log out my other devices”:
POST /auth/sessions/revoke-others.GET /auth/sessionslists live sessions with masked IPs. - “Disable this user”: call
RevokeUserSessions(ctx, userID). For API tokens,RevokeOwnerAPITokens. - “Admin only”: there is no built-in admin without RBAC. Return roles from
APITokensConfig.UserAbilitiesand guard routes withRequireAbility, or enable RBAC where the backend supports it.
7. Lock down signup and add what you were missing
AddConfig.Bootstrap so a fresh deployment cannot be claimed by whoever finds it first. Add Config.TOTP and Config.WebAuthn for second factors. Add Config.APITokens with Device if you have or want a CLI. The single-binary guide shows all of these wired together.
Checklist
- Storage chosen and migrated.
- Users and password hashes imported, ID mapping recorded.
AllowLegacyBcrypton, with a plan and date to turn it off.- Routes moved to
RequireAuthorRequireAbility. - Old session mechanism removed after the grace window.
- Signup closed with
Bootstrapor an explicit policy.