Why Authentication in Go is No Joke?
\n\nGo is a language chosen for speed and reliability. But even the fastest server turns into a pumpkin if passwords can be stolen through it. Secure authentication in Go applications is not just "install a library and forget it." It's a whole science: how to store passwords, how not to mess up JWT, how to protect against CSRF when everything is already on fire.
\n\nIn this article, we won't lecture you on theory. We'll write code. Real, working code that you can immediately insert into your project. We'll break down the three pillars of security: password hashing (bcrypt), working with JWT tokens, and protection against Cross-Site Request Forgery (CSRF).
\n\nHashing Passwords Correctly: Why md5 is Evil
\n\nThe first step to security is never, I repeat, never store passwords in plain text. Even if you trust your database. A breach can happen to anyone (ask Yahoo or Adobe). Proper hashing is your shield.
\n\nIn Go, the standard choice is golang.org/x/crypto/bcrypt. Why bcrypt? It is resistant to brute-force attacks (includes a "salt" and slows down computation). MD5 or SHA-1 are computed in microseconds. Bcrypt takes hundreds of milliseconds. For an attacker, this is a disaster; for the user, it's imperceptible.
Example: User Registration
\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("Password:", password)\n fmt.Println("Hash: ", hash)\n\n match := checkPasswordHash(password, hash)\n fmt.Println("Match:", match)\n}\n\nNote: bcrypt.DefaultCost is 10 iterations. For production, you can increase it to 12-14, but don't overdo it, otherwise the server will collapse under load. Test on your own hardware.
Password Verification on Login
\n\nIt's simple: take the password from the form, take the hash from the database, compare them using bcrypt.CompareHashAndPassword. If it returns nil, the user is legitimate. If there's an error, send a 401.
func loginHandler(w http.ResponseWriter, r *http.Request) {\n // get password from form\n storedHash := getUserHashFromDB(r.FormValue("email"))\n if !checkPasswordHash(r.FormValue("password"), storedHash) {\n http.Error(w, "Invalid password", http.StatusUnauthorized)\n return\n }\n // create session or token\n}\n\nJWT: Lightweight Authentication with a Catch
\n\nAfter the user enters their password, they need to be issued a "pass." One popular method is JWT (JSON Web Token). It's a compact token that contains user data and a signature. But the devil is in the details: an incorrect JWT implementation can make your application vulnerable.
\n\nCorrect JWT Generation in Go
\n\nUse the library github.com/golang-jwt/jwt/v5. Never write your own cryptography wheel — only use proven libraries.
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