Skip to main content
โšก Calmops

Authentication and Authorization in Web Apps

Authentication and Authorization in Web Apps

Introduction

Authentication (who you are) and authorization (what you can do) are critical for web application security. This guide covers implementing both in Go web applications.

Core Concepts

Authentication vs Authorization

  • Authentication: Verifying user identity (login)
  • Authorization: Checking user permissions (access control)

Common Methods

  1. Session-based: Server stores session data
  2. Token-based: Client stores token (JWT)
  3. OAuth2: Third-party authentication
  4. API Keys: Simple token authentication

Good: JWT Authentication

Implementing JWT

package main

import (
	"github.com/golang-jwt/jwt/v4"
	"time"
)

// โœ… GOOD: JWT claims
type Claims struct {
	UserID string `json:"user_id"`
	Email  string `json:"email"`
	Role   string `json:"role"`
	jwt.RegisteredClaims
}

// โœ… GOOD: Generate JWT token
func generateToken(userID, email, role string) (string, error) {
	claims := &Claims{
		UserID: userID,
		Email:  email,
		Role:   role,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString([]byte("secret-key"))
}

// โœ… GOOD: Verify JWT token
func verifyToken(tokenString string) (*Claims, error) {
	claims := &Claims{}

	token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
		return []byte("secret-key"), nil
	})

	if err != nil {
		return nil, err
	}

	if !token.Valid {
		return nil, jwt.ErrSignatureInvalid
	}

	return claims, nil
}

JWT Middleware

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
	"strings"
)

// โœ… GOOD: JWT middleware
func jwtMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		authHeader := c.GetHeader("Authorization")
		if authHeader == "" {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Missing token"})
			c.Abort()
			return
		}

		// Extract token from "Bearer <token>"
		parts := strings.Split(authHeader, " ")
		if len(parts) != 2 || parts[0] != "Bearer" {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token format"})
			c.Abort()
			return
		}

		claims, err := verifyToken(parts[1])
		if err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
			c.Abort()
			return
		}

		// Store claims in context
		c.Set("user_id", claims.UserID)
		c.Set("email", claims.Email)
		c.Set("role", claims.Role)

		c.Next()
	}
}

// โœ… GOOD: Role-based middleware
func requireRole(role string) gin.HandlerFunc {
	return func(c *gin.Context) {
		userRole, exists := c.Get("role")
		if !exists {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
			c.Abort()
			return
		}

		if userRole != role {
			c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden"})
			c.Abort()
			return
		}

		c.Next()
	}
}

Good: Session-Based Authentication

Session Management

package main

import (
	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/gin"
	"net/http"
)

// โœ… GOOD: Setup session store
func setupSessions(router *gin.Engine) {
	store := cookie.NewStore([]byte("secret-key"))
	router.Use(sessions.Sessions("session", store))
}

// โœ… GOOD: Login handler
func handleLogin(c *gin.Context) {
	var loginReq struct {
		Email    string `json:"email"`
		Password string `json:"password"`
	}

	if err := c.ShouldBindJSON(&loginReq); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Verify credentials
	user, err := verifyCredentials(loginReq.Email, loginReq.Password)
	if err != nil {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
		return
	}

	// Store in session
	session := sessions.Default(c)
	session.Set("user_id", user.ID)
	session.Set("email", user.Email)
	session.Save()

	c.JSON(http.StatusOK, gin.H{"message": "Logged in"})
}

// โœ… GOOD: Session middleware
func sessionMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		session := sessions.Default(c)
		userID := session.Get("user_id")

		if userID == nil {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
			c.Abort()
			return
		}

		c.Set("user_id", userID)
		c.Next()
	}
}

// โœ… GOOD: Logout handler
func handleLogout(c *gin.Context) {
	session := sessions.Default(c)
	session.Clear()
	session.Save()

	c.JSON(http.StatusOK, gin.H{"message": "Logged out"})
}

func verifyCredentials(email, password string) (*User, error) {
	// Verify credentials
	return nil, nil
}

Good: OAuth2 Integration

OAuth2 Setup

package main

import (
	"context"
	"github.com/gin-gonic/gin"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"net/http"
)

// โœ… GOOD: OAuth2 configuration
var googleOAuthConfig *oauth2.Config

func initOAuth2() {
	googleOAuthConfig = &oauth2.Config{
		ClientID:     "your-client-id",
		ClientSecret: "your-client-secret",
		RedirectURL:  "http://localhost:8080/auth/google/callback",
		Scopes: []string{
			"https://www.googleapis.com/auth/userinfo.email",
			"https://www.googleapis.com/auth/userinfo.profile",
		},
		Endpoint: google.Endpoint,
	}
}

// โœ… GOOD: OAuth2 login handler
func handleOAuth2Login(c *gin.Context) {
	url := googleOAuthConfig.AuthCodeURL("state", oauth2.AccessTypeOffline)
	c.Redirect(http.StatusTemporaryRedirect, url)
}

// โœ… GOOD: OAuth2 callback handler
func handleOAuth2Callback(c *gin.Context) {
	code := c.Query("code")

	token, err := googleOAuthConfig.Exchange(context.Background(), code)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to exchange token"})
		return
	}

	// Get user info
	client := googleOAuthConfig.Client(context.Background(), token)
	resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to get user info"})
		return
	}
	defer resp.Body.Close()

	// Store token and create session
	c.JSON(http.StatusOK, gin.H{"message": "Authenticated"})
}

Advanced Patterns

Multi-Factor Authentication

package main

import (
	"github.com/pquerna/otp/totp"
)

// โœ… GOOD: TOTP setup
func setupTOTP(userID string) (string, error) {
	key, err := totp.Generate(totp.GenerateOpts{
		Issuer:      "MyApp",
		AccountName: userID,
	})
	if err != nil {
		return "", err
	}

	return key.Secret(), nil
}

// โœ… GOOD: Verify TOTP
func verifyTOTP(secret, code string) bool {
	return totp.Validate(code, secret)
}

Permission-Based Access Control

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

// โœ… GOOD: Permission checking
func requirePermission(permission string) gin.HandlerFunc {
	return func(c *gin.Context) {
		userID, exists := c.Get("user_id")
		if !exists {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
			c.Abort()
			return
		}

		// Check if user has permission
		hasPermission := checkUserPermission(userID.(string), permission)
		if !hasPermission {
			c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden"})
			c.Abort()
			return
		}

		c.Next()
	}
}

func checkUserPermission(userID, permission string) bool {
	// Check permission in database
	return true
}

Best Practices

1. Never Store Passwords in Plain Text

// โœ… GOOD: Hash passwords
import "golang.org/x/crypto/bcrypt"

func hashPassword(password string) (string, error) {
	return bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
}

func verifyPassword(hashedPassword, password string) error {
	return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
}

// โŒ BAD: Store plain text
func storePassword(password string) {
	// db.Save(password) // NEVER!
}

2. Use HTTPS

// โœ… GOOD: Use HTTPS in production
router.RunTLS(":443", "cert.pem", "key.pem")

// โŒ BAD: Use HTTP
router.Run(":80")

3. Secure Token Storage

// โœ… GOOD: Store tokens securely
// Use httpOnly cookies for web apps
// Use secure storage for mobile apps

// โŒ BAD: Store in localStorage
// localStorage.setItem("token", token) // Vulnerable to XSS

Resources

Summary

Proper authentication and authorization are essential for web application security. Use JWT for stateless authentication, sessions for traditional web apps, and OAuth2 for third-party integration. Always hash passwords, use HTTPS, and follow security best practices.

Comments