package main import ( "encoding/json" "errors" "log" "net/http" "os" "strings" "time" "github.com/golang-jwt/jwt/v5" "golang.org/x/crypto/bcrypt" ) type Credentials struct { Username string `json:"username"` Password string `json:"password"` } type Claims struct { Username string `json:"username"` jwt.RegisteredClaims } var ( // In production, use environment variable for JWT key jwtKey = getJWTKey() adminUsername = "admin" // In a real app, store hashed password and retrieve from a secure storage adminPasswordHash = mustHashPassword("admin") // Default password, should be changed after first login ) func getJWTKey() []byte { key := os.Getenv("JWT_SECRET") if key == "" { return []byte("default-secret-key-change-in-production") } return []byte(key) } func mustHashPassword(password string) string { hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { log.Fatal("Failed to hash password:", err) } return string(hash) } func authenticateUser(creds Credentials) (string, error) { // In a real app, verify against a database if creds.Username != adminUsername { return "", errors.New("invalid credentials") } if err := bcrypt.CompareHashAndPassword([]byte(adminPasswordHash), []byte(creds.Password)); err != nil { return "", errors.New("invalid credentials") } // Create JWT token expirationTime := time.Now().Add(24 * time.Hour) claims := &Claims{ Username: creds.Username, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(expirationTime), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString(jwtKey) if err != nil { return "", err } return tokenString, nil } func verifyToken(tokenString string) (*Claims, error) { claims := &Claims{} token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, jwt.ErrSignatureInvalid } return jwtKey, nil }) if err != nil { return nil, err } if !token.Valid { return nil, errors.New("invalid token") } return claims, nil } // AuthMiddleware verifies the JWT token in the Authorization header func AuthMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Get token from Authorization header authHeader := r.Header.Get("Authorization") if authHeader == "" { http.Error(w, `{"error":"Authorization header required"}`, http.StatusUnauthorized) return } // Format: Bearer tokenString := "" if len(authHeader) > 7 && strings.ToUpper(authHeader[0:7]) == "BEARER " { tokenString = authHeader[7:] } else { http.Error(w, `{"error":"Authorization header format must be Bearer "}`, http.StatusUnauthorized) return } _, err := verifyToken(tokenString) if err != nil { http.Error(w, `{"error":"Invalid or expired token`+err.Error()+`"}`, http.StatusUnauthorized) return } // Token is valid, proceed with the request next.ServeHTTP(w, r) }) } func LoginHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, `{"error":"Method not allowed"}`, http.StatusMethodNotAllowed) return } var creds Credentials err := json.NewDecoder(r.Body).Decode(&creds) if err != nil { http.Error(w, `{"error":"Invalid request body"}`, http.StatusBadRequest) return } token, err := authenticateUser(creds) if err != nil { http.Error(w, `{"error":"Invalid credentials"}`, http.StatusUnauthorized) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "token": token, }) }