Securing APIs with JWT, OAuth, and Go: Concepts, Integration & Best Practices
· 3 min read · Originally on Medium
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
- Always use HTTPS
- Keep tokens short-lived (
exp) - Use
kidand key rotation for public APIs - Never store JWT in localStorage on web apps (use HttpOnly cookie or memory)
- Use
aud,iss,subclaims properly - Audit and log all token usage
- 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.