¿Por qué la autenticación en Go no es una broma?
\n\nGo es un lenguaje que se elige por su velocidad y fiabilidad. Pero incluso el servidor más rápido se convierte en una calabaza si a través de él se pueden robar contraseñas. La autenticación segura en aplicaciones Go no es solo "instalé una biblioteca y me olvidé". Es toda una ciencia: cómo almacenar contraseñas, cómo no arruinar JWT, cómo protegerse contra CSRF cuando todo ya está ardiendo.
\n\nEn este artículo no daremos conferencias sobre teoría. Escribiremos código. Código real, funcional, que se puede insertar directamente en su proyecto. Analizaremos los tres pilares de la seguridad: hash de contraseñas (bcrypt), trabajo con tokens JWT y protección contra falsificación de solicitudes entre sitios (CSRF).
\n\nHash de contraseñas correctamente: por qué md5 es malvado
\n\nEl primer paso hacia la seguridad es nunca, escuche, nunca almacenar contraseñas en texto plano. Incluso si confía en su base de datos. Una vulneración puede ocurrirle a cualquiera (pregúntele a Yahoo o Adobe). El hash correcto es su escudo.
\n\nEn Go, la elección estándar es golang.org/x/crypto/bcrypt. ¿Por qué bcrypt? Es resistente a ataques de fuerza bruta (incluye "sal" y ralentiza los cálculos). MD5 o SHA-1 se calculan en microsegundos. Bcrypt, en cientos de milisegundos. Para un atacante es un desastre, para el usuario es imperceptible.
Ejemplo: registro de usuario
\n\npackage main\n\nimport (\n "fmt"\n "golang.org/x/crypto/bcrypt"\n)\n\nfunc hashPassword(password string) (string, error) {\n bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)\n return string(bytes), err\n}\n\nfunc checkPasswordHash(password, hash string) bool {\n err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))\n return err == nil\n}\n\nfunc main() {\n password := "mySecretPass123"\n hash, _ := hashPassword(password)\n\n fmt.Println("Contraseña:", password)\n fmt.Println("Hash: ", hash)\n\n match := checkPasswordHash(password, hash)\n fmt.Println("Coincide:", match)\n}\n\nTenga en cuenta: bcrypt.DefaultCost son 10 iteraciones. Para producción se puede aumentar a 12-14, pero no se exceda, o el servidor colapsará bajo carga. Pruebe en su propio hardware.
Verificación de contraseña al iniciar sesión
\n\nEs simple: tomamos la contraseña del formulario, tomamos el hash de la base de datos, los comparamos mediante bcrypt.CompareHashAndPassword. Si devuelve nil, el usuario es legítimo. Si hay error, enviamos un 401.
func loginHandler(w http.ResponseWriter, r *http.Request) {\n // obtenemos password del formulario\n storedHash := getUserHashFromDB(r.FormValue("email"))\n if !checkPasswordHash(r.FormValue("password"), storedHash) {\n http.Error(w, "Contraseña incorrecta", http.StatusUnauthorized)\n return\n }\n // creamos sesión o token\n}\n\nJWT: autenticación ligera con trampa
\n\nDespués de que el usuario ingresa la contraseña, es necesario emitirle un "pase". Una de las formas populares es JWT (JSON Web Token). Es un token compacto que contiene datos del usuario y una firma. Pero el diablo está en los detalles: una implementación incorrecta de JWT puede hacer que su aplicación sea vulnerable.
\n\nGeneración correcta de JWT en Go
\n\nUsamos la biblioteca github.com/golang-jwt/jwt/v5. Nunca escriba su propia implementación para criptografía, solo use bibliotecas probadas.
import (\n "time"\n "github.com/golang-jwt/jwt/v5"\n)\n\nvar jwtKey = []byte("my_secret_key_which_must_be_long_and_random")\n\ntype Claims struct {\n Username string `json:"username"`\n jwt.RegisteredClaims\n}\n\nfunc generateToken(username string) (string, error) {\n expirationTime := time.Now().Add(15 * time.Minute)\n claims := &Claims{\n Username: username,\n RegisteredClaims: jwt.RegisteredClaims{\n ExpiresAt: jwt.NewNumericDate(expirationTime),\n },\n }\n\n token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)\n return token.SignedS