DEV Community

Cover image for Building a Golang API with JWT authentication
Adrian DY
Adrian DY

Posted on

Building a Golang API with JWT authentication

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:

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")
}
Enter fullscreen mode Exit fullscreen mode

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.

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!"})
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)