This is a premium alert message you can set from Layout! Get Now!

A guide to JWT authentication in Go

0

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.

Prerequisites

You’ll need to meet these basic requirements to get the most out of this tutorial.

  • Go 1.16 or later installed on your machine (for security reasons)
  • Experience building web applications in Go or any other language (optional)

Table of Contents

Getting started with the Golang-JWT package

After 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.

Setting up a web server in Go

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.

API network page

Generating JWTs for authentication using the Golang-JWT package

You 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.

Verifying JWT 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.

Endpoint 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.

Extracting claims from JWT tokens

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 the 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.

Conclusion

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!

The post A guide to JWT authentication in Go appeared first on LogRocket Blog.



from LogRocket Blog https://ift.tt/Tk3rIUs
Gain $200 in a week
via Read more

Post a Comment

0 Comments
* Please Don't Spam Here. All the Comments are Reviewed by Admin.
Post a Comment

Search This Blog

To Top