- Installation
- Features
- Quick Start
- Core Components
- Implementation Guide
- Usage Patterns
- Database Schema
- Security Considerations
- Samples
A flexible, database-agnostic authentication package for Go web applications. This package provides session-based authentication with configurable cookie settings, session renewal, and pluggable action handlers.
go get github.com/dector/authie
- Database Agnostic: Uses a
SessionStoreinterface to work with any database/ORM. - Simplified Setup: Concrete
UserandSessionstructs eliminate the need for interface boilerplate. - Configurable: Cookie names, expiration times, and security settings.
- Session Renewal: Automatic session renewal before expiration.
- Flexible Actions: Pluggable handlers for auth failures (redirects, API responses, etc.).
- Security: Secure token generation, HTTP-only cookies, configurable SameSite.
package main import "github.com/dector/authie" func main() { // 1. Create configuration config := authie.NewConfig("my_session") // 2. Implement the SessionStore interface for your database store := NewMySessionStore(db) // Your database-specific implementation // 3. Create AuthController authController := authie.NewAuthController(config, store) // 4. Use in HTTP middleware actionHandler := authie.NewDefaultAuthActionHandler("/login") // For frameworks like gorilla/mux, chi, etc: // r.Use(authController.Middleware(actionHandler)) // For standard library: // http.Handle("/protected", authController.Middleware(actionHandler)(yourHandler)) }
Configuration for session management:
type Config struct { CookieName string // Required: name of session cookie SessionTokenLength int // Length of generated session tokens ValidityTime time.Duration // How long session is active RenewabilityTime time.Duration // How long session can be renewed SameSite http.SameSite // Cookie SameSite setting }
Default Values:
SessionTokenLength: 40 charactersValidityTime: 3 daysRenewabilityTime: 7 daysSameSite:http.SameSiteLaxMode
The package provides concrete User and Session structs, removing the need for you to implement these interfaces.
// User represents the authenticated user. type User struct { ID int Login string } // Session represents a user session. type Session struct { ID int UserID int Token string ActiveUntil time.Time RenewableUntil time.Time Revoked bool User *User // Associated user data }
You must implement this interface to provide the bridge between the authie package and your database.
type SessionStore interface { CreateSession(ctx context.Context, params CreateSessionParams) (*authie.Session, error) GetSessionByToken(ctx context.Context, params GetSessionByTokenParams) (*authie.Session, error) UpdateSession(ctx context.Context, params UpdateSessionParams) error RevokeSession(ctx context.Context, params RevokeSessionParams) error }
Parameter Structs:
type CreateSessionParams struct { UserID int Token string ActiveUntil time.Time RenewableUntil time.Time } // ... other param structs (GetSessionByTokenParams, etc.)
Handles authentication failures, allowing for custom logic (e.g., API responses vs. redirects).
type AuthActionHandler interface { HandleUnauthenticated(w http.ResponseWriter, r *http.Request) HandleAuthError(w http.ResponseWriter, r *http.Request, err error) }
The main controller for session management.
// Create session for a user CreateSession(ctx context.Context, userID int, r *http.Request) (*authie.Session, error) // Verify and optionally renew a session from a token VerifySession(ctx context.Context, token string) (*authie.Session, bool, error) // Close/revoke a session CloseSession(ctx context.Context, sessionID int) error // Set the session cookie on the response SetSessionCookie(w http.ResponseWriter, token string) // Clear the session cookie ClearSessionCookie(w http.ResponseWriter) // Get the session from an HTTP request GetSessionFromRequest(r *http.Request) (*authie.Session, bool) // HTTP middleware to protect routes Middleware(actionHandler AuthActionHandler) func(http.Handler) http.Handler
The only implementation required is the SessionStore interface. Create an adapter for your database that maps your data models to the authie.Session and authie.User structs.
import ( "github.com/dector/authie" "your-project/db/models" // Your database models ) type MySessionStore struct { db *sql.DB // or your ORM client } func (s *MySessionStore) CreateSession(ctx context.Context, params authie.CreateSessionParams) (*authie.Session, error) { // 1. Insert session into your database dbSession, err := models.CreateDBSession(ctx, s.db, params) if err != nil { return nil, err } // 2. Map your DB model to authie.Session and return return &authie.Session{ ID: dbSession.ID, UserID: dbSession.UserID, Token: dbSession.Token, ActiveUntil: dbSession.ActiveUntil, RenewableUntil: dbSession.RenewableUntil, Revoked: dbSession.RevokedAt != nil, }, nil } func (s *MySessionStore) GetSessionByToken(ctx context.Context, params authie.GetSessionByTokenParams) (*authie.Session, error) { // 1. Query session and user data from your database dbSession, err := models.GetDBSessionWithUser(ctx, s.db, params.Token) if err != nil { return nil, err } // 2. Map your DB models to authie.Session and authie.User return &authie.Session{ ID: dbSession.ID, UserID: dbSession.UserID, Token: dbSession.Token, ActiveUntil: dbSession.ActiveUntil, RenewableUntil: dbSession.RenewableUntil, Revoked: dbSession.RevokedAt != nil, User: &authie.User{ ID: dbSession.User.ID, Login: dbSession.User.Login, }, }, nil } func (s *MySessionStore) UpdateSession(ctx context.Context, params authie.UpdateSessionParams) error { // Update session token and timestamps in your database } func (s *MySessionStore) RevokeSession(ctx context.Context, params authie.RevokeSessionParams) error { // Mark session as revoked in your database }
For API responses instead of redirects, you can implement a custom handler.
type APIAuthActionHandler struct{} func (h *APIAuthActionHandler) HandleUnauthenticated(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) json.NewEncoder(w).Encode(map[string]string{"error": "Authentication required"}) } func (h *APIAuthActionHandler) HandleAuthError(w http.ResponseWriter, r *http.Request, err error) { // ... handle error }
func LoginHandler(authController *authie.AuthController) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // ... validate credentials for 'user' ... // Create session session, err := authController.CreateSession(r.Context(), user.ID, r) if err != nil { http.Error(w, "Failed to create session", http.StatusInternalServerError) return } // Set cookie using the session token authController.SetSessionCookie(w, session.Token) http.Redirect(w, r, "/dashboard", http.StatusSeeOther) } }
func LogoutHandler(authController *authie.AuthController) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Optional: revoke session in database if session, ok := authController.GetSessionFromRequest(r); ok { authController.CloseSession(r.Context(), session.ID) } // Clear cookie authController.ClearSessionCookie(w) http.Redirect(w, r, "/login", http.StatusSeeOther) } }
func SomeHandler(w http.ResponseWriter, r *http.Request) { // User is available in context after middleware user, ok := r.Context().Value("user").(*authie.User) if !ok { http.Error(w, "User not found", http.StatusInternalServerError) return } fmt.Fprintf(w, "Hello, %s!", user.Login) }
A compatible session table structure:
CREATE TABLE user_sessions ( id INTEGER PRIMARY KEY, user_id INTEGER NOT NULL, token VARCHAR(40) NOT NULL UNIQUE, active_until TIMESTAMP NOT NULL, renewable_until TIMESTAMP NOT NULL, revoked_at TIMESTAMP NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ); CREATE INDEX idx_user_sessions_token ON user_sessions(token); CREATE INDEX idx_user_sessions_user_id ON user_sessions(user_id);
- Tokens: Configurable length hex tokens (default 40 characters = 160 bits of entropy)
- Cookies: HTTP-only, Secure, configurable SameSite
- Session Renewal: Automatic renewal prevents session fixation
- Revocation: Explicit session revocation support
A basic setup using an in-memory store.
package main import ( "context" "fmt" "net/http" "time" "github.com/dector/authie" ) // In-memory representations of our data var mockUsers = map[int]struct{ ID int; Login string }{ 1: {ID: 1, Login: "testuser"}, } var mockSessions = make(map[string]*authie.Session) // Minimal SessionStore implementation type SimpleSessionStore struct{} func (s *SimpleSessionStore) CreateSession(ctx context.Context, params authie.CreateSessionParams) (*authie.Session, error) { user, ok := mockUsers[params.UserID] if !ok { return nil, fmt.Errorf("user not found") } session := &authie.Session{ ID: len(mockSessions) + 1, UserID: params.UserID, Token: params.Token, ActiveUntil: params.ActiveUntil, RenewableUntil: params.RenewableUntil, User: &authie.User{ID: user.ID, Login: user.Login}, } mockSessions[params.Token] = session return session, nil } func (s *SimpleSessionStore) GetSessionByToken(ctx context.Context, params authie.GetSessionByTokenParams) (*authie.Session, error) { session, exists := mockSessions[params.Token] if !exists { return nil, fmt.Errorf("session not found") } return session, nil } func (s *SimpleSessionStore) UpdateSession(ctx context.Context, params authie.UpdateSessionParams) error { // Find and update session return nil // Simplified for example } func (s *SimpleSessionStore) RevokeSession(ctx context.Context, params authie.RevokeSessionParams) error { // Find and mark session as revoked return nil // Simplified for example } func main() { // Setup config := authie.NewConfig("simple_session") store := &SimpleSessionStore{} authController := authie.NewAuthController(config, store) authActionHandler := authie.NewDefaultAuthActionHandler("/login") mux := http.NewServeMux() // Login page mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { session, err := authController.CreateSession(r.Context(), 1, r) // Login user 1 if err != nil { http.Error(w, "Login failed", http.StatusInternalServerError) return } authController.SetSessionCookie(w, session.Token) http.Redirect(w, r, "/dashboard", http.StatusSeeOther) } else { fmt.Fprint(w, `<form method="post"><input type="submit" value="Login"></form>`) } }) // Protected dashboard dashboardHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user := r.Context().Value("user").(*authie.User) fmt.Fprintf(w, "Welcome, %s!", user.Login) }) mux.Handle("/dashboard", authController.Middleware(authActionHandler)(dashboardHandler)) fmt.Println("Server starting on :8080") http.ListenAndServe(":8080", mux) }