- 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
SessionStore
interface to work with any database/ORM. - Simplified Setup: Concrete
User
andSession
structs 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)
}