Building an Authentication Service

Overview

Building an authentication service involves creating a system to manage user identities, authenticate users, and issue tokens for secure access to other services. This chapter will guide you through the process of designing and implementing an authentication service in Go.

1. Introduction to Authentication

1.1 Understanding Authentication and Authorization:

1.2 Types of Authentication:

2. Setting Up the Project

2.1 Initializing the Project:

sh
mkdir auth-service cd auth-service go mod init github.com/yourusername/auth-service

2.2 Adding Dependencies:

sh
go get github.com/dgrijalva/jwt-go go get github.com/gorilla/mux go get golang.org/x/crypto/bcrypt

3. Designing the Authentication Service

3.1 Defining User Model:

go
package models type User struct { ID string `json:"id"` Username string `json:"username"` Password string `json:"-"` }

3.2 Setting Up Database:

3.3 Implementing User Registration:

go
package handlers import ( "encoding/json" "net/http" "golang.org/x/crypto/bcrypt" "github.com/yourusername/auth-service/models" ) func Register(w http.ResponseWriter, r *http.Request) { var user models.User json.NewDecoder(r.Body).Decode(&user) hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost) user.Password = string(hashedPassword) // Save user to the database json.NewEncoder(w).Encode(user) }

3.4 Implementing User Login:

go
package handlers import ( "encoding/json" "net/http" "time" "github.com/dgrijalva/jwt-go" "golang.org/x/crypto/bcrypt" "github.com/yourusername/auth-service/models" ) var jwtKey = []byte("your_secret_key") type Claims struct { Username string `json:"username"` jwt.StandardClaims } func Login(w http.ResponseWriter, r *http.Request) { var user models.User json.NewDecoder(r.Body).Decode(&user) // Retrieve user from the database storedUser, err := getUserFromDB(user.Username) if err != nil || bcrypt.CompareHashAndPassword([]byte(storedUser.Password), []byte(user.Password)) != nil { http.Error(w, "Invalid credentials", http.StatusUnauthorized) return } expirationTime := time.Now().Add(24 * time.Hour) claims := &Claims{ Username: user.Username, StandardClaims: jwt.StandardClaims{ ExpiresAt: expirationTime.Unix(), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString(jwtKey) if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) return } http.SetCookie(w, &http.Cookie{ Name: "token", Value: tokenString, Expires: expirationTime, }) }

4. Token Management

4.1 JWT Token Generation:

4.2 Token Validation Middleware:

go
package middleware import ( "net/http" "github.com/dgrijalva/jwt-go" ) func AuthMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie("token") if err != nil { if err == http.ErrNoCookie { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } http.Error(w, "Bad request", http.StatusBadRequest) return } tokenStr := cookie.Value claims := &Claims{} token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) { return jwtKey, nil }) if err != nil { if err == jwt.ErrSignatureInvalid { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } http.Error(w, "Bad request", http.StatusBadRequest) return } if !token.Valid { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } next.ServeHTTP(w, r) }) }

5. Security Best Practices

5.1 Secure Password Storage:

5.2 Secure Token Storage:

5.3 Regular Token Rotation:

6. Testing and Deployment

6.1 Unit Testing:

go
package handlers_test import ( "testing" "net/http" "net/http/httptest" "bytes" "encoding/json" "github.com/yourusername/auth-service/models" ) func TestRegister(t *testing.T) { user := models.User{Username: "testuser", Password: "password"} body, _ := json.Marshal(user) req, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(body)) rr := httptest.NewRecorder() handler := http.HandlerFunc(Register) handler.ServeHTTP(rr, req) if status := rr.Code; status != http.StatusOK { t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK) } }

6.2 Continuous Integration and Deployment:

Becoming a Senior Go Developer: Mastering Go and Its Ecosystem