Implementing a secure REST API is crucial for protecting sensitive data and ensuring only authorized users access your application. Fortunately, JSON Web Tokens (JWT) offer a straightforward way to add authentication to your Go REST API. In this guide, we’ll walk you through securing your Go REST API with JWT auth, making the process easy even for beginners. Whether you’re building a small project or a large-scale application, this step-by-step tutorial will help you understand and implement JWT authentication effectively.

Why Use JWT for Your Go REST API?
JWT, or JSON Web Token, is a popular method for securing APIs. It’s a compact, self-contained token that securely transmits information between parties. For instance, when a user logs in, the server generates a JWT, which the client includes in subsequent requests to prove their identity. As a result, JWT is ideal for stateless authentication in REST APIs.
Moreover, JWT is widely used because it’s:
- Secure: It uses cryptographic signatures to verify authenticity.
- Scalable: It works well in distributed systems, as tokens are self-contained.
- Simple: It integrates easily with Go and other languages.
By securing your Go REST API with JWT, you ensure only authorized users access protected routes, keeping your application safe.
Prerequisites for This Tutorial
Before diving into the code, let’s ensure you have everything you need. This guide targets beginner to intermediate developers, so we’ll keep things simple yet comprehensive. You’ll need:
- Basic knowledge of Go programming.
- Go installed on your machine (version 1.18 or later recommended).
- A code editor like VS Code.
- Familiarity with REST APIs and HTTP requests.
- A tool like Postman for testing API endpoints.
If you’re new to Go or REST APIs, don’t worry. We’ll explain each step clearly, including technical terms, to make JWT auth easy to understand.
Setting Up Your Go Project
To get started, let’s set up a basic Go project for your REST API. First, create a new directory for your project and initialize it as a Go module.
mkdir go-jwt-api
cd go-jwt-api
go mod init go-jwt-api
Next, install the necessary packages. For this tutorial, we’ll use:
- gorilla/mux: A powerful router for handling HTTP requests.
- dgrijalva/jwt-go: A popular library for JWT in Go.
Run these commands to install them:
go get github.com/gorilla/mux
go get github.com/dgrijalva/jwt-go
Now, you’re ready to build a simple Go REST API and secure it with JWT auth.
Also read about Spring Boot Audit Logs
Creating a Basic Go REST API
Let’s create a minimal REST API with a public and a protected endpoint. The public endpoint will handle user login, while the protected endpoint will require JWT authentication.
Here’s a basic Go REST API setup:
package main
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/login", loginHandler).Methods("POST")
r.HandleFunc("/protected", protectedHandler).Methods("GET")
log.Fatal(http.ListenAndServe(":8080", r))
}
func loginHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Login endpoint")
}
func protectedHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Protected endpoint")
}
This code sets up a server with two endpoints:
/login
: A public endpoint for user login./protected
: A restricted endpoint that will require JWT authentication.
However, the /protected
endpoint isn’t secure yet. Let’s implement JWT to protect it.
Understanding JWT Structure
Before coding the authentication logic, let’s break down what a JWT is. A JWT consists of three parts:
- Header: Contains metadata, like the token type and signing algorithm.
- Payload: Holds claims, such as user ID and expiration time.
- Signature: Verifies the token’s authenticity using a secret key.
These parts are encoded in Base64 and separated by dots (.
). For example: header.payload.signature
. When a client sends a JWT, the server verifies the signature to ensure the token hasn’t been tampered with.
Implementing JWT Authentication
Now, let’s secure your Go REST API with JWT auth. We’ll create a login system that generates a JWT and a middleware to protect the /protected
endpoint.
Step 1: Generate a JWT on Login
First, modify the loginHandler
to generate a JWT when a user logs in. For simplicity, we’ll use hardcoded credentials, but in a real application, you’d verify credentials against a database.
Here’s the updated code:
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/gorilla/mux"
)
var secretKey = []byte("my-secret-key") // Replace with a secure key in production
type User struct {
Username string `json:"username"`
Password string `json:"password"`
}
func loginHandler(w http.ResponseWriter, r *http.Request) {
var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, "Invalid request payload", http.StatusBadRequest)
return
}
// Hardcoded credentials for demo purposes
if user.Username != "admin" || user.Password != "password" {
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
return
}
// Create JWT token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"username": user.Username,
"exp": time.Now().Add(time.Hour * 24).Unix(), // Token expires in 24 hours
})
tokenString, err := token.SignedString(secretKey)
if err != nil {
http.Error(w, "Error generating token", http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(map[string]string{"token": tokenString})
}
This code:
- Accepts a JSON payload with
username
andpassword
. - Verifies the credentials (hardcoded for simplicity).
- Generates a JWT with a username claim and a 24-hour expiration.
- Returns the JWT to the client.
Step 2: Create JWT Middleware
Next, we need middleware to verify the JWT for protected routes. The middleware checks the Authorization
header for a valid token.
Here’s the middleware code:
func jwtMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
if len(tokenString) == 0 {
http.Error(w, "Missing Authorization Header", http.StatusUnauthorized)
return
}
// Remove "Bearer " prefix if present
tokenString = strings.TrimPrefix(tokenString, "Bearer ")
// Parse and validate token
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return secretKey, nil
})
if err != nil || !token.Valid {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
This middleware:
- Checks for the
Authorization
header. - Extracts and validates the JWT.
- Calls the next handler if the token is valid, or returns an error if it’s not.
Step 3: Secure the Protected Endpoint
Apply the middleware to the /protected
endpoint. Update the main
function:
func main() {
r := mux.NewRouter()
r.HandleFunc("/login", loginHandler).Methods("POST")
r.Handle("/protected", jwtMiddleware(http.HandlerFunc(protectedHandler))).Methods("GET")
log.Fatal(http.ListenAndServe(":8080", r))
}
func protectedHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome to the protected endpoint!")
}
Now, the /protected
endpoint is only accessible with a valid JWT.
Testing Your Secure Go REST API
To test your API, follow these steps:
- Start the server:
go run main.go
. - Use Postman or
curl
to send a POST request to/login
with this JSON body:{ "username": "admin", "password": "password" }
You’ll receive a JWT in the response. - Copy the JWT and send a GET request to
/protected
with the header:Authorization: Bearer <your-jwt-token>
If the token is valid, you’ll see “Welcome to the protected endpoint!” Otherwise, you’ll get an error.
For example, using curl
:
curl -X POST http://localhost:8080/login -d '{"username":"admin","password":"password"}'
curl -H "Authorization: Bearer <your-jwt-token>" http://localhost:8080/protected
Best Practices for JWT Authentication
To make your Go REST API with JWT auth more secure, consider these best practices:
- Use a Strong Secret Key: Store your secret key in an environment variable, not in the code.
- Set Short Token Expirations: Use short-lived tokens (e.g., 1 hour) and implement refresh tokens.
- Use HTTPS: Always use HTTPS to encrypt data in transit.
- Validate Claims: Check additional claims, like user roles, in your middleware.
- Handle Errors Gracefully: Provide clear error messages without exposing sensitive details.
For more details on JWT best practices, refer to the official JWT documentation.
Common Issues and Troubleshooting
Here are some common problems and solutions:
- Invalid Token Error: Ensure the
Authorization
header includes “Bearer” followed by the token. - Token Expired: Check the
exp
claim and generate a new token if needed. - Signature Mismatch: Verify that the secret key matches on both token creation and validation.
By addressing these issues, you can ensure your JWT auth works smoothly.
Full Code Example
Below is the complete Go code for your secure REST API with JWT authentication:
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/gorilla/mux"
)
var secretKey = []byte("my-secret-key") // Replace with a secure key in production
type User struct {
Username string `json:"username"`
Password string `json:"password"`
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/login", loginHandler).Methods("POST")
r.Handle("/protected", jwtMiddleware(http.HandlerFunc(protectedHandler))).Methods("GET")
log.Fatal(http.ListenAndServe(":8080", r))
}
func loginHandler(w http.ResponseWriter, r *http.Request) {
var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, "Invalid request payload", http.StatusBadRequest)
return
}
if user.Username != "admin" || user.Password != "password" {
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
return
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"username": user.Username,
"exp": time.Now().Add(time.Hour * 24).Unix(),
})
tokenString, err := token.SignedString(secretKey)
if err != nil {
http.Error(w, "Error generating token", http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(map[string]string{"token": tokenString})
}
func jwtMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
if len(tokenString) == 0 {
http.Error(w, "Missing Authorization Header", http.StatusUnauthorized)
return
}
tokenString = strings.TrimPrefix(tokenString, "Bearer ")
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return secretKey, nil
})
if err != nil || !token.Valid {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
func protectedHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome to the protected endpoint!")
}
Conclusion
Securing your Go REST API with JWT auth is easier than it seems. By following this guide, you’ve learned how to generate JWTs, create middleware to protect endpoints, and test your API. Moreover, you’ve explored best practices to keep your API secure and scalable. With these tools, you can confidently build secure Go REST APIs for your projects.
For further learning, check out the official Go documentation or explore advanced JWT features like refresh tokens. Start implementing JWT auth today and make your Go REST API secure and reliable!