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.1 Understanding Authentication and Authorization:
1.2 Types of Authentication:
2.1 Initializing the Project:
shmkdir auth-service
cd auth-service
go mod init github.com/yourusername/auth-service
2.2 Adding Dependencies:
shgo get github.com/dgrijalva/jwt-go go get github.com/gorilla/mux go get golang.org/x/crypto/bcrypt
3.1 Defining User Model:
User
struct to represent user data.gopackage 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:
gopackage 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:
gopackage 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.1 JWT Token Generation:
4.2 Token Validation Middleware:
gopackage 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.1 Secure Password Storage:
5.2 Secure Token Storage:
5.3 Regular Token Rotation:
6.1 Unit Testing:
gopackage 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: