Securing APIs with JWT, OAuth, and Go: Concepts, Integration & Best Practices

· 3 min read · Originally on Medium

Securing APIs with JWT, OAuth, and Go: Concepts, Integration & Best Practices

Modern APIs power everything — from mobile apps to microservices — but they're also prime targets for attackers. Ensuring that your API endpoints are secure is no longer optional. Two of the most widely adopted standards in API security are JWT (JSON Web Token) and OAuth 2.0.

In this article, we'll explore the role these standards play in securing APIs, when and how to use them, what threats they mitigate, and how to integrate them — illustrated with Golang examples.

JWT and OAuth: What's the Difference?

  • JWT is a compact, signed token format used for secure information exchange and stateless authentication.
  • OAuth 2.0 is an authorization framework that allows applications to access user resources via third-party identity providers (Google, GitHub, etc.) — without managing user credentials directly.

TL;DR: OAuth issues access tokens. JWT is often the format of those tokens.

When to Use What?

JWT and OAuth often complement each other. You might use OAuth to authenticate with Google, and issue your own JWT afterward for internal API access.

API Security Flow: OAuth + JWT

Here's a typical flow for secure user authentication using Google OAuth and JWT in your Go backend.

Integration in Golang

1. Generate JWT

import (
	"time"
	"github.com/golang-jwt/jwt/v5"
)
 
var jwtKey = []byte("your-secret-key")
 
func GenerateJWT(userID string) (string, error) {
	claims := jwt.MapClaims{
		"sub": userID,
		"role": "user",
		"exp":  time.Now().Add(1 * time.Hour).Unix(),
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(jwtKey)
}

2. Protect API Routes with JWT Middleware

func JWTMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		tokenStr := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
		token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
			return jwtKey, nil
		})
		if err != nil || !token.Valid {
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}
		next.ServeHTTP(w, r)
	})
}

3. OAuth 2.0 Login (Google)

import (
	"context"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
)
 
var googleOAuthConfig = &oauth2.Config{
	ClientID:     "your-client-id",
	ClientSecret: "your-client-secret",
	RedirectURL:  "http://localhost:8080/callback",
	Scopes:       []string{"openid", "email", "profile"},
	Endpoint:     google.Endpoint,
}

Redirect to Google:

func handleLogin(w http.ResponseWriter, r *http.Request) {
	url := googleOAuthConfig.AuthCodeURL("random-state")
	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}

Exchange Code for Token:

func handleCallback(w http.ResponseWriter, r *http.Request) {
	code := r.URL.Query().Get("code")
	token, err := googleOAuthConfig.Exchange(context.Background(), code)
	if err != nil {
		http.Error(w, "Failed to exchange token", http.StatusInternalServerError)
		return
	}
	client := googleOAuthConfig.Client(context.Background(), token)
	resp, _ := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
	defer resp.Body.Close()
	// Decode user info and issue internal JWT
}

Best Practices

  1. Always use HTTPS
  2. Keep tokens short-lived (exp)
  3. Use kid and key rotation for public APIs
  4. Never store JWT in localStorage on web apps (use HttpOnly cookie or memory)
  5. Use aud, iss, sub claims properly
  6. Audit and log all token usage
  7. Rotate OAuth client secrets and revoke tokens on logout

Final Thoughts

JWT and OAuth are foundational tools in your API security toolbox. Golang, being performant and strongly typed, is a perfect fit for secure, scalable authentication mechanisms.

Use OAuth for identity and delegated access. Use JWT for stateless API authentication and authorization.

Together, they protect your users, your data, and your infrastructure.

Keep reading

I Accidentally Built a Software Factory

While building Forge, I wrote a skill file so agents could fix issues overnight. A few weeks later, everyone was talking about software factories, a term from 1968.

AI Is an Assistant, Not a Genie

AI agents can build a working prototype in an afternoon. Making it production ready — robust, observable, and maintainable — still depends on a human who understands the trade-offs.

Stop Mixing Auth with Business Logic

Learn why separating authentication from domain logic improves security, scalability, and maintainability in modern microservices architectures.

View all blogs

About the author

Thant Hein Thwin

Lead Software Engineer

Available for work