Building a Golang API with JWT authentication
Tags: go, learning, jwt, programming
Published: 2023-06-10 03:29:31

First, let's start with some background information. JWT stands for JSON Web Tokens, and it is a standard for securely transmitting information between parties as a JSON object. JWTs are commonly used for authentication and authorization purposes, and they are composed of three parts: a header, a payload, and a signature.

When a user logs in to your application, you can generate a JWT and send it back to the client. The client can then include the JWT in subsequent requests to your API, and your API can use the JWT to authenticate the user and authorize access to protected resources.

Now, let's dive into the code. We'll start by creating a basic Golang API using the Gin framework. Make sure you have Gin installed before proceeding:

```go
package main

import (
    "github.com/gin-gonic/gin"
)

func main() {
    router := gin.Default()

    router.GET("/", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "Hello, world!",
        })
    })

    router.Run(":8080")
}
```

This code creates a simple API that listens on port 8080 and responds with a "Hello, world!" message when you make a GET request to the root endpoint.

Next, we'll add JWT authentication to our API. We'll be using the github.com/dgrijalva/jwt-go package to generate and validate JWTs.

```go
package main

import (
    "github.com/dgrijalva/jwt-go"
    "github.com/gin-gonic/gin"
    "net/http"
    "time"
)

var jwtKey = []byte("my_secret_key")

func main() {
    router := gin.Default()

    router.POST("/login", login)

    auth := router.Group("/auth")
    auth.Use(authMiddleware())
    {
        auth.GET("/hello", hello)
    }

    router.Run(":8080")
}

func login(c *gin.Context) {
    var loginData struct {
        Username string `json:"username" binding:"required"`
        Password string `json:"password" binding:"required"`
    }

    if err := c.ShouldBindJSON(&loginData); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    if loginData.Username != "myuser" || loginData.Password != "mypassword" {
        c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid login credentials"})
        return
    }

    expirationTime := time.Now().Add(5 * time.Minute)
    claims := &jwt.StandardClaims{
        ExpiresAt: expirationTime.Unix(),
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    tokenString, err := token.SignedString(jwtKey)

    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not generate token"})
        return
    }

    c.JSON(http.StatusOK, gin.H{"token": tokenString})
}

func authMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        tokenString := c.GetHeader("Authorization")
        if tokenString == "" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header missing"})
            return
        }

        token, err := jwt.ParseWithClaims(tokenString, &jwt.StandardClaims{}, func(token *jwt.Token) (interface{}, error) {
            if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
                return nil, jwt.ErrInvalidSigningMethod
            }

            return jwtKey, nil
        })

        if err != nil {
            if err == jwt.ErrSignatureInvalid {
                c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token signature"})
                return
            }

            c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
            return
        }

        if !token.Valid {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
            return
        }

        c.Next()
    }
}

func hello(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"message": "Hello, authenticated user!"})
}
```

Let's go through this code step by step. First, we define a `jwtKey` variable that will be used to sign and verify JWTs. This key should be kept secret and not shared with anyone.

Next, we define two endpoints: `/login` and `/auth/hello`. The `/login` endpoint accepts a username and password in JSON format, checks if they are valid, and generates a JWT if they are. The `/auth/hello` endpoint requires a valid JWT to be included in the `Authorization` header of the request.

The `login` function first checks if the username and password are valid. If they are, it creates a new JWT with an expiration time of 5 minutes and returnsit to the client in JSON format. 

The `authMiddleware` function is a middleware that checks if a valid JWT is included in the `Authorization` header of the request. If a valid JWT is present, it calls `c.Next()` to allow the request to proceed. If a valid JWT is not present or is invalid, it returns an error response.

Finally, the `hello` function is a protected endpoint that requires a valid JWT to be accessed. If a valid JWT is present, it returns a "Hello, authenticated user!" message in JSON format.

Let's start by discussing the `jwt-go` package that we used to implement JWT authentication in our Golang API. This package provides functions for creating, parsing, and validating JWTs.

When creating a JWT, we first create a `StandardClaims` struct that includes any claims that we want to include in the JWT payload, such as an expiration time. We then create a new JWT using the `jwt.NewWithClaims` function and sign it using our `jwtKey` variable. The resulting signed JWT is then included in the response to the client.

When validating a JWT, we first extract the JWT from the `Authorization` header of the request and parse it using the `jwt.ParseWithClaims` function. We pass in our `jwtKey` variable as the second argument to this function to verify the signature of the JWT. If the JWT is valid and has not expired, we call `c.Next()` to allow the request to proceed. If the JWT is invalid or has expired, we return an error response.

It's important to note that JWTs should be used in conjunction with HTTPS to ensure that they are transmitted securely. JWTs should also be kept secret and not shared with anyone else.

In addition to the `jwt-go` package, we used the `Gin` framework to create our Golang API. Gin is a lightweight web framework that provides features such as routing, middleware, and error handling. We defined two endpoints in our API: `/login`, which generates a JWT if the user provides valid credentials, and `/auth/hello`, which requires a valid JWT to be accessed.

That's it! _With this code, you have created a Golang API with JWT authentication. Of course, you can customize this code to fit your specific needs, but this should give you a good starting point._