Authentication
Implement user authentication in Goose applications.
Overview
Authentication verifies user identity. Goose provides patterns for:
- JWT (JSON Web Tokens)
- Session-based authentication
- API keys
- OAuth2
JWT Authentication
Setup
import (
"time"
"github.com/golang-jwt/jwt/v5"
)
type AuthService struct {
userService *UserService `inject:""`
jwtSecret string
}
func NewAuthService() *AuthService {
return &AuthService{
jwtSecret: env.String("JWT_SECRET", ""),
}
}
Generate Token
type Claims struct {
UserID string `json:"user_id"`
Email string `json:"email"`
Role string `json:"role"`
jwt.RegisteredClaims
}
func (s *AuthService) GenerateToken(user *User) (string, error) {
claims := Claims{
UserID: user.ID,
Email: user.Email,
Role: user.Role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: "myapp",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(s.jwtSecret))
}
Validate Token
func (s *AuthService) ValidateToken(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method")
}
return []byte(s.jwtSecret), nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, fmt.Errorf("invalid token")
}
Auth Middleware
type AuthMiddleware struct {
authService *AuthService `inject:""`
}
func (m *AuthMiddleware) Handle(ctx types.Context, next types.Next) any {
// Get token from header
authHeader := ctx.Header("Authorization")
if authHeader == "" {
return ctx.Status(401).JSON(map[string]string{
"error": "Missing authorization header",
})
}
// Parse Bearer token
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
if tokenString == authHeader {
return ctx.Status(401).JSON(map[string]string{
"error": "Invalid authorization format",
})
}
// Validate token
claims, err := m.authService.ValidateToken(tokenString)
if err != nil {
return ctx.Status(401).JSON(map[string]string{
"error": "Invalid token",
})
}
// Set user in context
ctx.Set("user_id", claims.UserID)
ctx.Set("user_email", claims.Email)
ctx.Set("user_role", claims.Role)
return next()
}
Login Endpoint
type AuthController struct {
authService *AuthService `inject:""`
userService *UserService `inject:""`
}
type LoginDTO struct {
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required"`
}
type LoginResponse struct {
Token string `json:"token"`
ExpiresIn int64 `json:"expires_in"`
User *User `json:"user"`
}
func (c *AuthController) Login(ctx types.Context) any {
var dto LoginDTO
if err := ctx.Bind(&dto); err != nil {
return ctx.Status(400).JSON(map[string]string{
"error": "Invalid request",
})
}
// Find user
user, err := c.userService.GetByEmail(dto.Email)
if err != nil {
return ctx.Status(401).JSON(map[string]string{
"error": "Invalid credentials",
})
}
// Check password
if !CheckPassword(dto.Password, user.Password) {
return ctx.Status(401).JSON(map[string]string{
"error": "Invalid credentials",
})
}
// Generate token
token, err := c.authService.GenerateToken(user)
if err != nil {
return ctx.Status(500).JSON(map[string]string{
"error": "Failed to generate token",
})
}
return LoginResponse{
Token: token,
ExpiresIn: 86400, // 24 hours
User: user,
}
}
Registration
type RegisterDTO struct {
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=8"`
Name string `json:"name" validate:"required"`
}
func (c *AuthController) Register(ctx types.Context) any {
var dto RegisterDTO
if err := ctx.Bind(&dto); err != nil {
return ctx.Status(400).JSON(map[string]string{
"error": "Invalid request",
})
}
// Check if user exists
existing, _ := c.userService.GetByEmail(dto.Email)
if existing != nil {
return ctx.Status(409).JSON(map[string]string{
"error": "Email already registered",
})
}
// Hash password
hashedPassword, _ := HashPassword(dto.Password)
// Create user
user, err := c.userService.Create(&User{
Email: dto.Email,
Password: hashedPassword,
Name: dto.Name,
})
if err != nil {
return ctx.Status(500).JSON(map[string]string{
"error": "Failed to create user",
})
}
// Generate token
token, _ := c.authService.GenerateToken(user)
return ctx.Status(201).JSON(LoginResponse{
Token: token,
ExpiresIn: 86400,
User: user,
})
}
Routes
func (c *AuthController) Routes() types.Routes {
return types.Routes{
{Method: "POST", Path: "/auth/login", Handler: c.Login},
{Method: "POST", Path: "/auth/register", Handler: c.Register},
{Method: "POST", Path: "/auth/logout", Handler: c.Logout},
{Method: "GET", Path: "/auth/me", Handler: c.Me, Middlewares: []any{&AuthMiddleware{}}},
}
}
Session Authentication
Setup Session Store
type SessionService struct {
kv *kv.Client `inject:""`
}
func (s *SessionService) Create(userID string) (string, error) {
sessionID := uuid.New().String()
session := map[string]interface{}{
"user_id": userID,
"created_at": time.Now(),
}
data, _ := json.Marshal(session)
err := s.kv.SetEx("session:"+sessionID, string(data), 24*time.Hour)
return sessionID, err
}
func (s *SessionService) Get(sessionID string) (*Session, error) {
data, err := s.kv.Get("session:" + sessionID)
if err != nil {
return nil, err
}
var session Session
json.Unmarshal([]byte(data), &session)
return &session, nil
}
func (s *SessionService) Destroy(sessionID string) error {
return s.kv.Del("session:" + sessionID)
}
Session Middleware
type SessionMiddleware struct {
sessionService *SessionService `inject:""`
}
func (m *SessionMiddleware) Handle(ctx types.Context, next types.Next) any {
// Get session from cookie
sessionID := ctx.Cookie("session_id")
if sessionID == "" {
return ctx.Status(401).JSON(map[string]string{
"error": "Not authenticated",
})
}
// Validate session
session, err := m.sessionService.Get(sessionID)
if err != nil {
return ctx.Status(401).JSON(map[string]string{
"error": "Invalid session",
})
}
// Set user in context
ctx.Set("user_id", session.UserID)
return next()
}
API Key Authentication
type APIKeyMiddleware struct {
apiKeyService *APIKeyService `inject:""`
}
func (m *APIKeyMiddleware) Handle(ctx types.Context, next types.Next) any {
apiKey := ctx.Header("X-API-Key")
if apiKey == "" {
return ctx.Status(401).JSON(map[string]string{
"error": "Missing API key",
})
}
key, err := m.apiKeyService.Validate(apiKey)
if err != nil {
return ctx.Status(401).JSON(map[string]string{
"error": "Invalid API key",
})
}
ctx.Set("api_key", key)
ctx.Set("user_id", key.UserID)
return next()
}
Password Utilities
import "golang.org/x/crypto/bcrypt"
// Hash password
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
return string(bytes), err
}
// Check password
func CheckPassword(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
Refresh Tokens
type RefreshTokenService struct {
kv *kv.Client `inject:""`
authService *AuthService `inject:""`
}
func (s *RefreshTokenService) Generate(userID string) (string, error) {
refreshToken := uuid.New().String()
// Store with longer expiry
err := s.kv.SetEx("refresh:"+refreshToken, userID, 7*24*time.Hour)
return refreshToken, err
}
func (s *RefreshTokenService) Refresh(refreshToken string) (*TokenPair, error) {
// Get user ID from refresh token
userID, err := s.kv.Get("refresh:" + refreshToken)
if err != nil {
return nil, fmt.Errorf("invalid refresh token")
}
// Invalidate old refresh token
s.kv.Del("refresh:" + refreshToken)
// Generate new tokens
user, _ := s.userService.GetByID(userID)
accessToken, _ := s.authService.GenerateToken(user)
newRefreshToken, _ := s.Generate(userID)
return &TokenPair{
AccessToken: accessToken,
RefreshToken: newRefreshToken,
}, nil
}
Best Practices
- Use HTTPS for all authentication endpoints
- Hash passwords with bcrypt or argon2
- Set appropriate token expiry (short for access, longer for refresh)
- Implement rate limiting on login endpoints
- Log authentication events for security monitoring
- Use secure cookies (HttpOnly, Secure, SameSite)
- Validate token on every request
Next Steps
- Authorization - Access control
- CORS - Cross-origin configuration
- Rate Limiting - Prevent brute force