#Securing Your Go REST APIs With JWTs
Note - The full source code for this tutorial can be found here: TutorialEdge/go-jwt-tutorial
JWTs, or JSON Web Tokens as they are more formally known, are a compact, URL-safe means of representing claims to be transferred between two parties. This is essentially a confusing way of saying that JWTs allow you to transmit information from a client to the server in a stateless, but secure way.
Prerequisites
Before you can follow this article, you will need the following:
- You will need Go version 1.11+ installed on your development machine.
Introduction
The JWT standard uses either a secret, using the HMAC algorithm, or a public/private key pair using RSA or ECDSA.
Note - If you are interested in the formal definition of what JWTs are, then I recommend checking out the RFC: RFC-7519
These are heavily used within Single-Page Applications (SPAs) as a means of secure communications as they allow us to do two key things:
- Authentication - The most commonly used practice. Once a user logs in to your application, or authenticates in some manner, every request that is then sent from the client on behalf of the user will contain the JWT.
- Information Exchange - The second use for JWTs is to securely transmit information between different systems. These JWTs can be signed using public/private key pairs so you can verify each system in this transaction in a secure manner and JWTs contain an anti-tamper mechanism as they are signed based off the header and the payload.
So, if you haven’t guessed by now, in this tutorial, we’ll be looking at exactly what it takes to build a secure Go-based REST API that uses JSON Web Tokens to communicate!
Video Tutorial
This tutorial is available in a video format, if you want to support me and my work then please feel free to leave a like and subscribe to my channel for my content!
A Simple REST API
So, we are going to be using the code from one of my other articles,
Creating a simple REST API in Go,
to get us started. This will feature a really simple Hello World style
endpoint and it will run on port 8081.
package main
import (
"fmt"
"log"
"net/http"
)
func homePage(w http.ResponseWriter, r *http.Request){
fmt.Fprintf(w, "Hello World")
fmt.Println("Endpoint Hit: homePage")
}
func handleRequests() {
http.HandleFunc("/", homePage)
log.Fatal(http.ListenAndServe(":8081", nil))
}
func main() {
handleRequests()
}
When we run this an attempt to hit our homepage, running on
http://localhost:8081/, we should see the message Hello World in our
browser.
JWT Authentication
So, now that we have a simple API that we can now protect using signed JWT tokens, let’s build a client API that will try to request data from this original API.
To do this, we can use a JWT that has been signed with a secure key that both our client and server will have knowledge off. Let’s walk through how this will work:
- Our client will generate a signed JWT based of our shared passphrase.
- When our client goes to hit our server API, it will include this JWT as part of the request.
- Our server will be able to read this JWT and validate the token using the same passphrase.
- If the JWT is valid, it will then return the highly confidential
hello worldmessage back to the client, otherwise it’ll returnnot authorized.
Our architecture diagram is going to end up looking a little something like this:

Our Server
So, let’s see this in action, let’s create a really simple server:
package main
import (
"fmt"
"log"
"net/http"
jwt "github.com/golang-jwt/jwt/v5"
)
var mySigningKey = []byte("captainjacksparrowsayshi")
func homePage(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World")
fmt.Println("Endpoint Hit: homePage")
}
func isAuthorized(endpoint func(http.ResponseWriter, *http.Request)) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header["Token"] != nil {
token, err := jwt.Parse(r.Header["Token"][0], func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("There was an error")
}
return mySigningKey, nil
})
if err != nil {
fmt.Fprintf(w, err.Error())
}
if token.Valid {
endpoint(w, r)
}
} else {
fmt.Fprintf(w, "Not Authorized")
}
})
}
func handleRequests() {
http.Handle("/", isAuthorized(homePage))
log.Fatal(http.ListenAndServe(":9000", nil))
}
func main() {
handleRequests()
}
Let’s break this down. We’ve created a really simple API that feature a solitary
endpoint, this is protected by our isAuthorized middleware decorator. In this
isAuthorized function, we check to see that the incoming request features the
Token header in the request and we then subsequently check to see if the token
is valid based off our private mySigningKey.
If this is a valid token, we then serve the protected endpoint.
Note - This example uses decorators, if you aren’t comfortable with the concept of decorators in Go then I recommend you check out my other article here: Getting Started with Decorators in Go
Our Client
Now that we have a server that features a JWT secured endpoint, let’s build something that can interact with it.
We’ll be building a simple client application which will attempt to call our /
endpoint of our server.
package main
import (
"fmt"
"io"
"log"
"net/http"
"time"
jwt "github.com/golang-jwt/jwt/v5"
)
var mySigningKey = []byte("captainjacksparrowsayshi")
func homePage(w http.ResponseWriter, r *http.Request) {
validToken, err := GenerateJWT()
if err != nil {
fmt.Println("Failed to generate token")
}
client := &http.Client{}
req, _ := http.NewRequest("GET", "http://localhost:9000/", nil)
req.Header.Set("Token", validToken)
res, err := client.Do(req)
if err != nil {
fmt.Fprintf(w, "Error: %s", err.Error())
}
body, err := io.ReadAll(res.Body)
if err != nil {
fmt.Fprintf(w, "Error reading response: %s", err.Error())
return
}
fmt.Fprintf(w, string(body))
}
func GenerateJWT() (string, error) {
token := jwt.New(jwt.SigningMethodHS256)
claims := token.Claims.(jwt.MapClaims)
claims["authorized"] = true
claims["client"] = "Elliot Forbes"
claims["exp"] = time.Now().Add(time.Minute * 30).Unix()
tokenString, err := token.SignedString(mySigningKey)
if err != nil {
fmt.Errorf("Something Went Wrong: %s", err.Error())
return "", err
}
return tokenString, nil
}
func handleRequests() {
http.HandleFunc("/", homePage)
log.Fatal(http.ListenAndServe(":9001", nil))
}
func main() {
handleRequests()
}
Let’s break down what’s happening in the above code. Again, we’ve defined a
really simple API that features a single endpoint. This endpoint, when
triggered, generates a new JWT using our secure mySigningKey, it then creates
a new http client and sets the Token header equal to the JWT string that we
have just generated.
It then attempts to hit our server application which is running on
http://localhost:9000 using this signed JWT token. Our server then validates
the token we’ve generated in the client and proceeds to serve us our super
secret Hello World message.
JWT Library Migration Note
If you’re using the older dgrijalva/jwt-go library, you should migrate to golang-jwt/jwt/v5 (also known as jwt-go), which is the maintained fork. The original library is no longer actively maintained. The API is largely compatible, but you’ll want to update your import statements and ensure you’re using the latest version for security patches.
Token Refresh and Rotation Best Practices
In production systems, tokens should have a limited lifetime and be refreshed periodically. Rather than issuing tokens that are valid for extended periods, implement a refresh token pattern where short-lived access tokens (typically 15 minutes to 1 hour) are paired with longer-lived refresh tokens. When an access token expires, the client uses the refresh token to obtain a new access token without requiring the user to re-authenticate. Additionally, implement token rotation where refresh tokens are invalidated after use, requiring clients to obtain a new refresh token with each refresh request. This limits the window of vulnerability if a token is compromised.
Storing JWTs Securely
When working with JWTs on the client-side, storage method matters significantly for security. The two primary approaches have different security tradeoffs:
- HTTPOnly Cookies - The most secure approach for web browsers. HTTPOnly cookies cannot be accessed via JavaScript, protecting them from XSS attacks. Browsers automatically include them in requests and handle same-site policies.
- localStorage/sessionStorage - More convenient for SPAs but vulnerable to XSS attacks since they’re accessible to JavaScript. Any malicious script can steal tokens stored here.
For web applications, HTTPOnly cookies are strongly recommended. For native mobile apps, use platform-specific secure storage (Keychain on iOS, Keystore on Android). Never store JWTs in localStorage unless you have specific security requirements that outweigh the XSS risk.
Modern Encryption Approaches in Go 1.26
While JWTs remain a powerful tool for authentication, Go 1.26 introduces the new crypto/hpke package implementing Hybrid Public Key Encryption (RFC 9180). This provides an alternative encryption mechanism for sensitive communications and can be used alongside JWT for enhanced cryptographic flexibility. HPKE combines the benefits of public-key and symmetric encryption, making it suitable for modern security architectures where you need both asymmetric and symmetric cryptographic primitives.
Conclusion
Hopefully, this tutorial helped to demystify the art of securing your Go applications and REST APIs using JSON Web Tokens. This was a lot of fun writing this article, and I hope it has helped you in your Go development travels.
If you enjoyed this tutorial, then please let me know in the comments section below, or give this article a share across social media, it really helps me and my site! For a more complete REST API example, check out Building a Basic REST API with Fiber.
Note - If you want to keep track of when new Go articles are posted to the site, then please feel free to follow me on twitter for all the latest news: @Elliot_F.
Further Reading
If you fancy reading up more on JSON Web Tokens and how they are used then I can thoroughly recommend the following articles:
- Go Decorators Tutorial - Go Decorators
- RFC-7519 - https://tools.ietf.org/html/rfc7519
- JWT Introduction - https://jwt.io/introduction/
- Creating HTTP Middleware in Go
Ready to Go Further?
This tutorial is part of the Go Learning Path — a curated route from beginner to advanced Go developer, covering authentication, REST APIs, testing, gRPC and more.
Continue Learning
Creating a RESTful API With Golang
this tutorial demonstrates how you can create your own simple RESTful JSON api using Go(Lang)
The Complete Guide to Building REST APIs in Go
Master building production-ready REST APIs in Go 1.26 with modern patterns, frameworks, and best practices for 2025-2026
Writing Clean Functions in Go with the Full Mapping Strategy
In this tutorial, we're going to be discussing how you can build clean functions in Go.
Creating Real-Time Chat and Activity Systems With Getstream.io
In this video tutorial, we are going to be looking at how you can build realtime chat and activity systems with GetStream.io's fantastic product offering,