Building a Golang API with JWT authentication
Adrian DY
Posted on June 10, 2023
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")
}
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!"})
}
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.
Posted on June 10, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.