This episode secures your Go API: TLS with certificates, JWT and auth middleware, the OAuth2 flow and sessions, and security practices like input validation, CORS, rate limiting, and security headers to make the API ready for the internet.

The API you built in episode 10 is still plain: traffic is sent without encryption and anyone can access it. In episode 11, you will secure your Go API layer by layer, from transport to authorization.
Episode 11 covers four security areas: TLS for transport encryption, JWT as stateless authentication tokens, OAuth2 flows and sessions for integration with identity providers, and API security practices such as input validation, CORS, rate limiting, and security headers.
Without TLS, all data — including credentials — is sent as plain text and can be read by anyone on the network path. HTTPS requires TLS encryption. For production, get a certificate from Let's Encrypt via certbot or automatically from a cloud platform.
openssl req -x509 -newkey rsa:4096 -keyout key.pem \
-out cert.pem -days 365 -nodes -subj "/CN=localhost"http.Server can serve TLS with ListenAndServeTLS, using a certificate and private key:
package main
import (
"net/http"
)
func main() {
srv := &http.Server{
Addr: ":8443",
Handler: http.DefaultServeMux,
}
srv.ListenAndServeTLS("cert.pem", "key.pem")
}In production, never commit private keys to the repository. Platforms like Kubernetes store them as secrets or use a certificate manager for automatic renewal.
JWT is a three-part JSON token — header, payload, and signature — that is signed so it can't be forged. The payload holds claims like sub for the subject and exp for the expiry. Popular package: github.com/golang-jwt/jwt/v5.
go get github.com/golang-jwt/jwt/v5package main
import (
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
)
var rahasia = []byte("kunci-sangat-rahasia")
func buatToken(id string) (string, error) {
klaim := jwt.RegisteredClaims{
Subject: id,
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, klaim)
return token.SignedString(rahasia)
}
func main() {
token, err := buatToken("42")
if err != nil {
panic(err)
}
fmt.Println(token)
}The signature is built from the same secret used during verification. Store the secret in an environment variable or a secret manager, not in code.
Token verification happens in middleware: read the Authorization header, check the Bearer format, parse it, and verify the signature before proceeding to the handler.
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
raw := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
token, err := jwt.Parse(raw, func(t *jwt.Token) (any, error) {
return rahasia, nil
})
if err != nil || !token.Valid {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}Always limit the token lifetime, don't store tokens in localStorage for web applications, and use the right signature (HS256 for a single server, RS256 for distributed setups). To quickly test a generated token, decode the header and payload parts with jq -R 'split(".")[1] | @base64d' <<< "$TOKEN".
OAuth2 lets users log in with their Google, GitHub, and other accounts without sharing passwords. The golang.org/x/oauth2 package manages the authorization code flow safely. The access token received is used to call the provider, and the refresh token extends the session.
For applications that need logout and revocation, server-based sessions fit better than JWT. Store sessions in Redis or a database with an expiry, and send the session ID through HttpOnly and Secure cookies. The HttpOnly cookie prevents access from JavaScript, reducing XSS risk.
http.SetCookie(w, &http.Cookie{
Name: "session_id",
Value: sid,
HttpOnly: true,
Secure: true,
Path: "/",
SameSite: http.SameSiteStrictMode,
})Never trust user input. Validate every field — length, format, and type — before processing. Libraries like go-playground/validator simplify struct validation with tags:
type LoginRequest struct {
Email string `validate:"required,email"`
Password string `validate:"required,min=8"`
}CORS restricts which domains may call the API from a browser. Rate limiting restricts the number of requests per client per time window to prevent abuse and brute force. For production, apply centralized rate limiting like Redis together with golang.org/x/time/rate or dedicated middleware.
Always send security headers on every response: Content-Security-Policy to restrict content sources, X-Content-Type-Options: nosniff to prevent MIME sniffing, and Strict-Transport-Security to enforce HTTPS. This combination dramatically raises an application's security level at almost zero cost.
Episode 11 secured your Go API from transport down to the application layer: TLS with ListenAndServeTLS, JWT with golang-jwt and authorization middleware, OAuth2 flows and secure cookie-based sessions, plus input validation, CORS, rate limiting, and security headers practices.
Key takeaways:
In the next episode we will discuss concurrency, context, and safe parallelism — goroutines, channels, and select as concurrency primitives, context.Context for cancellation and deadlines, and synchronization with sync.Mutex, WaitGroup, Once, and atomic operations. This is Go's most distinctive identity.