Autenticación segura en Go: escribiendo código sin vulnerabilidades

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

¿Por qué la autenticación en Go no es una broma?

\n\n

Go 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\n

En 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\n

Hash de contraseñas correctamente: por qué md5 es malvado

\n\n

El 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\n

En 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.

\n\n

Ejemplo: registro de usuario

\n\n
package 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\n

Tenga 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.

\n\n

Verificación de contraseña al iniciar sesión

\n\n

Es 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.

\n\n
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\n

JWT: autenticación ligera con trampa

\n\n

Despué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\n

Generación correcta de JWT en Go

\n\n

Usamos la biblioteca github.com/golang-jwt/jwt/v5. Nunca escriba su propia implementación para criptografía, solo use bibliotecas probadas.

\n\n
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                

Blogs

Book Recommendations