Security Best Practices
Go backend security spans input validation, SQL injection prevention, authentication (JWT, OAuth2), HTTPS/TLS, secrets management, and dependency scanning.
Introduction
Go backend security spans input validation, SQL injection prevention, authentication (JWT, OAuth2), HTTPS/TLS, secrets management, and dependency scanning. Go's memory safety prevents buffer overflows, but application-level vulnerabilities remain your responsibility.
OWASP Top 10 applies: broken access control, injection, insecure design. Use golang.org/x/crypto/bcrypt for passwords, crypto/rand for tokens, and run govulncheck in CI. Interviewers ask JWT validation, CORS, rate limiting, and secret storage.
This lesson covers JWT middleware, parameterized queries, security headers, and dependency vulnerability scanning — production security checklist for Go APIs.
The story
A fintech API gateway validates JWT signatures with pinned public keys, enforces TLS 1.3 for all outbound calls to payment processors, and sanitizes SQL parameters through prepared statements — never string concatenation. When Cloudflare mitigates a DDoS, the origin Go service must still reject forged internal headers and rate-limit authenticated abuse.
Security in Go layers defense: crypto/rand for tokens, bcrypt for passwords, middleware for auth, and regular govulncheck scans in CI for known CVEs in dependencies.
Understanding the topic
Key concepts
- Parameterized SQL — never string concat user input.
- bcrypt.HashPassword for storage; constant-time compare.
- JWT: validate signature, exp, iss; don't trust claims without verify.
- crypto/rand for tokens — never math/rand for secrets.
- HTTPS only in production; HSTS header.
- govulncheck scans module vulnerabilities.
Step-by-step explanation
- Middleware validates JWT on protected routes.
- RBAC checks role claim against required permission.
- Rate limiter per IP prevents brute force.
- Input validation on all external data.
- Secrets from env/vault — rotated regularly.
- CI runs govulncheck and docker scan on every build.
Practical code example
Security middleware with headers, body limit, and JWT auth stub:
package mainimport ("context""net/http""strings")type ctxKey stringconst userIDKey ctxKey = "userID"func securityHeaders(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {w.Header().Set("X-Content-Type-Options", "nosniff")w.Header().Set("X-Frame-Options", "DENY")w.Header().Set("Strict-Transport-Security", "max-age=31536000")next.ServeHTTP(w, r)})}func maxBody(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {r.Body = http.MaxBytesReader(w, r.Body, 1<<20) // 1MBnext.ServeHTTP(w, r)})}func jwtAuth(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {auth := r.Header.Get("Authorization")if !strings.HasPrefix(auth, "Bearer ") {http.Error(w, "unauthorized", http.StatusUnauthorized)return}token := strings.TrimPrefix(auth, "Bearer ")// validateJWT(token) — verify signature, exp, issuerif token == "" {http.Error(w, "invalid token", http.StatusUnauthorized)return}ctx := context.WithValue(r.Context(), userIDKey, "user-123")next.ServeHTTP(w, r.WithContext(ctx))})}func main() {mux := http.NewServeMux()mux.HandleFunc("GET /api/profile", func(w http.ResponseWriter, r *http.Request) {uid := r.Context().Value(userIDKey)w.Write([]byte("profile:" + uid.(string)))})handler := securityHeaders(maxBody(jwtAuth(mux)))http.ListenAndServe(":8080", handler)}
Line-by-line code explanation
crypto/rand.Read(b)generates cryptographically secure random bytes for tokens.bcrypt.GenerateFromPasswordhashes passwords with adaptive cost — never store plaintext.db.QueryContext(ctx, "SELECT ... WHERE id=$1", id)— parameterized queries prevent SQL injection.html.EscapeString(s)prevents XSS when reflecting user input in HTML responses.tls.Config{MinVersion: tls.VersionTLS12}enforces modern TLS on server and client.middleware authvalidates Bearer tokens before reaching business handlers.govulncheck ./...reports known vulnerabilities in module dependencies.limit request body size—http.MaxBytesReaderprevents large payload DoS.
Key takeaway: Use golang-jwt/jwt for real validation. MaxBytesReader prevents large body DoS. Never log JWT or secrets.
Real-world use
Where you'll use this in production
- OAuth2 login flows with Google/GitHub providers.
- API key authentication for developer portals.
- PCI-compliant payment APIs with audit logging.
- Zero-trust internal service mTLS on service mesh.
Best practices
- Validate all input; whitelist acceptable values.
- Use bcrypt/scrypt for passwords; never store plaintext.
- Short-lived JWT with refresh token rotation.
- Run govulncheck and dependabot in CI.
- Principle of least privilege for DB and cloud IAM.
- Security headers on all HTTP responses.
Common mistakes
- SQL string concatenation with user input.
- JWT alg:none attack — whitelist allowed algorithms.
- Secrets in git history or Docker layers.
- CORS * with credentials enabled.
- Logging Authorization headers.
Advanced interview questions
Q1BeginnerPrevent SQL injection in Go?
Q2BeginnerSecure random tokens?
Q3IntermediateJWT validation checklist?
Q4Intermediategovulncheck purpose?
Q5AdvancedSecure public REST API design.
Summary
Parameterized SQL, bcrypt passwords, crypto/rand tokens. Validate JWT properly; security headers on all responses. Secrets in vault/env — never in source or images. govulncheck in CI for dependency vulnerabilities. Next lesson: deployment basics.