JSON Web Tokens (JWTs) are a popular method for dealing with online authentication, and you can implement JWT authentication in any server-side programming language.
For background reading JWTs in general, I recommend learning more about JWTs, best practices, and securing RESTful APIs with JWTs with these articles on the LogRocket blog.
This article is aimed at helping you get started with implementing JWT authentication in your Go web applications using the golang-jwt
package.
The golang-jwt
package is the most popular package for implementing JWTs in Go, owing to its features and ease of use. The golang-jwt
package provides functionality for generating and validating JWTs.
You’ll need to meet these basic requirements to get the most out of this tutorial.
Golang-JWT
packageGolang-JWT
pakageAfter setting up your Go workspace and initializing the Go modules file go.mod
, run this command on your terminal in the workspace directory to install the golang-jwt
package:
go get github.com/golang-jwt/jwt
Once you’ve installed the golang-jwt
, create a Go file and import these packages and modules:
import ( "log" "encoding/json" "github.com/golang-jwt/jwt" "net/http" "time" )
You’ll use these packages in this tutorial to log errors, set up a server, and set the token expiration time.
Let’s start with creating a simple web server with an endpoint that will be secured with a JWT.
func main() { http.HandleFunc("/home", handlePage) err := http.ListenAndServe(":8080", nil) if err != nil { log.Println("There was an error listening on port :8080", err) } }
The main function sets up the home endpoint with a handler function handlePage
that you’ll set up. The handlePage
function will secure the page using JWTs. The server is set to listen on port :8080
, but you can use any port of your choice.
The handlePage
handler function will return the encoded JSON of the Message
struct as a response to the client if the request is authorized after the request body is encoded.
type Message struct { Status string `json:"status"` Info string `json:"info"` } func handlePage(writer http.ResponseWriter, request *http.Request) { writer.Header().Set("Content-Type", "application/json") var message Message err := json.NewDecoder(request.Body).Decode(&message) if err != nil { return } err = json.NewEncoder(writer).Encode(message) if err != nil { return } }
The handlePage
function, at this point, isn’t authenticated and making requests to the page will work freely. You’ll learn how to add authentication to your handler functions later in this tutorial.
Golang-JWT
packageYou will need a secret key to generate JWT tokens using the golang-jwt
package. Here’s an example private key for this tutorial; however, you should use a cryptographically secure string for your secret key and load it from an environment variables file (.env).
Check out this article to learn how to use environment variables in your Go applications.
var sampleSecretKey = []byte("SecretYouShouldHide")
Kindly note that whoever has the secret key you use for your JWTs can authenticate users of your application. The sampleSecretKey
variable holds the private key in this case.
Here’s a function for generating JWT tokens. The function should return a string and an error. If there’s an error generating the JWT, the function returns an empty string and the error. If there are no errors, the function returns the JWT string and the nil
type.
func generateJWT() (string, error) {
}
You can create a new token using the New
method of the JWT package. The New
method takes in a signing method (the cryptographic algorithm for the JWT) and returns a JWT token.
token := jwt.New(jwt.SigningMethodEdDSA)
If you want to modify the JWT, you can use the Claims
method of the token.
claims := token.Claims.(jwt.MapClaims) claims["exp"] = time.Now().Add(10 * time.Minute) claims["authorized"] = true claims["user"] = "username"
In this case, you’re setting an expiry time for the JWT, which is ten minutes, using the time
module and the username and authorization status. You’ll be able to retrieve the claims when attempting to verify the JWT.
The final part of generating a JWT is to sign the string using your secret key. You can sign your token string using the SignedString
method of the token. The SignedString
method takes the secret key and returns a signed token string.
tokenString, err := token.SignedString(sampleSecretKey) if err != nil { return "", err } return tokenString, nil
In cases where there are errors signing the token, you can return an empty string and the error.
Unlike cookies, you don’t need to store JWT; all you need is your signing key to verify tokens.
The conventional method of verifying JWTs uses middleware (handler functions that take in other handler functions for operations). Here’s how to use middleware to verify that a request is authorized.
func verifyJWT(endpointHandler func(writer http.ResponseWriter, request *http.Request)) http.HandlerFunc { }
The verifyJWT
function is a middleware that takes in the handler function for the request you want to verify. The handler function uses the token parameter from the request header to verify the request and respond based on the status.
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
})
The verifyJWT
function returns the handler function passed in as a parameter if the request is authorized.
The first step to verifying JWTs is to inspect the token in the request’s header.
if request.Header["Token"] != nil { }
If there’s a token, you can proceed to verify the token and verify claims.
You’ll have to parse the token, and you can parse the token using the Parse
method of the jwt
package. The parse
method takes in the token and a JWT decorator function and returns an interface and an error.
You need to use the same signing method you used to sign the token when you generated it to verify the signature using the Method
method of the token. In this case, the signing method was the ECDSA method.
token, err := jwt.Parse(request.Header\["Token"\][0], func(token *jwt.Token) (interface{}, error) { _, ok := token.Method.(*jwt.SigningMethodECDSA) if !ok { writer.WriteHeader(http.StatusUnauthorized) _, err := writer.Write([]byte("You're Unauthorized!")) if err != nil { return nil, err } } return "", nil })
If the signature verification fails (the function returns !ok
), you can return a StatusUnauthorized
header to the client.
if err != nil { writer.WriteHeader(http.StatusUnauthorized) _, err2 := writer.Write([]byte("You're Unauthorized due to error parsing the JWT")) if err2 != nil { return } }
In the code above, there’s an error parsing the token. Therefore, the user is unauthorized, and you can write a message and return an unauthorized status.
You can validate the token using the Valid
method of the token.
if token.Valid { endpointHandler(writer, request) } else { writer.WriteHeader(http.StatusUnauthorized) _, err := writer.Write([]byte("You're Unauthorized due to invalid token")) if err != nil { return } }
If the token is valid, you can pass in the endpoint handler with the writer
and request
parameters of the handler function for the middleware function to return the endpoint.
Here’s the else
statement for a case where there’s no token in the header of the client’s request:
else { writer.WriteHeader(http.StatusUnauthorized) _, err := writer.Write([]byte("You're Unauthorized due to No token in the header")) if err != nil { return } }
Since you’re using middleware, the handler function in your route declaration will be the verifyJWT
middleware with the handler function for the route as the argument.
http.HandleFunc("/home", verifyJWT(handlePage))
Once you’ve added your verification function to the route, the endpoint is authenticated.
On the client side, the client has to provide an issued token. Here’s a function that uses the generateJWT
function to add tokens in requests.
func authPage(writer http.ResponseWriter, ) { token, err := generateJWT() if err != nil { return } client := &http.Client{} request, _ := http.NewRequest("POST", "<http://localhost:8080/>", nil) request.Header.Set("Token", token) _, _ = client.Do(request) }
In the authPage
function, the token
variable holds the token from the generateJWT
function. Using a reference to the Client
type of the http
package, you can create a new client and make a request to the endpoint. The request
variable is the request instance and — using the Set
method of the header
method of the request instance — you can set the token in the request header as shown above.
You can also choose to set the token as a cookie and retrieve it for verification whenever a client makes a request to the authenticated endpoint.
When you’re generating a JWT, you can choose to embed information in the token. In the generateJWT
function, you added the username
variable to the claims
map.
Here’s how you can extract the claims, using the username
claims as an example. You can use middleware or add functionality to your verification function when verifying the token signature.
func extractClaims(_ http.ResponseWriter, request *http.Request) (string, error) { if request.Header["Token"] != nil { tokenString := request.Header\["Token"\][0] token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodECDSA); !ok { return nil, fmt.Errorf("there's an error with the signing method") } return sampleSecretKey, nil }) if err != nil { return "Error Parsing Token: ", err } }
In the extractClaims
functions, the process is the same as the verifyJWT
function; you retrieved the token from the header, parsed the token, and verified the signature.
claims, ok := token.Claims.(jwt.MapClaims) if ok && token.Valid { username := claims["username"].(string) return username, nil } } return "unable to extract claims", nil
On validating the token, you can retrieve the claims using the Claims
method and use the claims map to retrieve the data in the JWT, as shown above.
This tutorial taught you how to use JWT authentication to authenticate your API and web page endpoints in Go with JSON Web Tokens by using the golang-jwt
package. You can find the complete code in this tutorial as a GitHub Gist.
Remember to use environment variables for your secret keys and do not hide sensitive data in JWTs. There are many JWT tutorials on the LogRocket blog that you can check out to get started in the language or framework you’re interested in using!
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
2 Replies to "A guide to JWT authentication in Go"
Your code is incorrect. You should be returning the secret key on the happy path of the jwt.Parse function, not an empty string. Returning an empty string results in getting an error from the jwt.Parse function. Here is the correct implementation for anybody that was stuck on this as me:
token, err := jwt.Parse(r.Header[“Token”][0], func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return “”, fmt.Errorf(“Unexpected signing method: %v”, token.Header[“alg”])
}
return sampleSecretKey, nil
})
Wrong code to verify token, wasted my 2 hours