Using JWT in the gin framework

Posted by kidbrax on Sat, 29 Jan 2022 02:03:13 +0100

The full name of JWT is JSON Web Token. It is a cross domain authentication solution and belongs to an open standard. It specifies an implementation method of Token. At present, it is mostly used in front and back-end separation projects and oauth2 0 business scenario.

What is JWT?

The full name of JWT is JSON Web Token. It is a cross domain authentication solution and belongs to an open standard. It specifies an implementation method of Token. At present, it is mostly used in front and back-end separation projects and oauth2 0 business scenario.

Why JWT?

In some previous web projects, we usually used cookie session mode to realize user authentication. The relevant processes are as follows:

  1. The user fills in the user name and password in the browser and sends them to the server
  2. After the server verifies the user name and password, it will generate a session data and a corresponding ID (usually called session_id) to save the relevant information of the current user
  3. When the server returns a response, the session of the previous step will be_ Cookie whose ID is written to the user's browser
  4. Each subsequent user's request from the browser will automatically carry the session_ Cookie with ID
  5. The server passes the session in the request_ ID can find the previously saved session data of the user, so as to obtain the relevant information of the user.

This scheme relies on the client (browser) to save cookies, and needs to store the user's session data on the server.

In the era of mobile Internet, our users may use browsers or apps to access our services. Our web applications may be deployed on different ports at the front and back ends. Sometimes we need to support third-party login, so the cookie session mode is a little weak.

JWT is a lightweight authentication mode based on Token. After the server passes the authentication, a JSON object will be generated. After signing, a Token (Token) will be obtained and sent back to the user. The user only needs to bring this Token for subsequent requests, and the server can obtain the relevant information of the user after decryption.

To connect the principle of JWT, I recommend you to read: Ruan Yifeng's introduction to JWT

Generate JWT and parse JWT

Here, we directly use the JWT go library to realize our functions of generating JWT and parsing JWT.

Define requirements

We need to customize our own requirements to determine what data is saved in JWT. For example, we specify that username information should be stored in JWT, so we define a MyClaims structure as follows:

// MyClaims custom declaration structure and embedded JWT StandardClaims
// jwt package comes with jwt Standardclaims contains only official fields
// We need to record an additional username field here, so we need to customize the structure
// If you want to save more information, you can add it to this structure
type MyClaims struct {
	Username string `json:"username"`
	jwt.StandardClaims
}

Then we define the expiration time of JWT, taking 2 hours as an example:

const TokenExpireDuration = time.Hour * 2

Next, you need to define Secret:

var MySecret = []byte("Summer slipped away")

Generate JWT

// GenToken generates JWT
func GenToken(username string) (string, error) {
	// Create our own statement
	c := MyClaims{
		username, // Custom field
		jwt.StandardClaims{
			ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // Expiration time
			Issuer:    "my-project",                               // Issuer 
		},
	}
	// Creates a signed object using the specified signing method
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
	// Use the specified secret signature and obtain the complete encoded string token
	return token.SignedString(MySecret)
}

Parsing JWT

// ParseToken parsing JWT
func ParseToken(tokenString string) (*MyClaims, error) {
	// Parse token
	token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (i interface{}, err error) {
		return MySecret, nil
	})
	if err != nil {
		return nil, err
	}
	if claims, ok := token.Claims.(*MyClaims); ok && token.Valid { // Verification token
		return claims, nil
	}
	return nil, errors.New("invalid token")
}

Using JWT in the gin framework

First, we register a route / auth to provide external access to Token s:

r.POST("/auth", authHandler)

Our authHandler is defined as follows:

func authHandler(c *gin.Context) {
	// The user sends the user name and password
	var user UserInfo
	err := c.ShouldBind(&user)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": 2001,
			"msg":  "Invalid parameter",
		})
		return
	}
	// Verify that the user name and password are correct
	if user.Username == "q1mi" && user.Password == "q1mi123" {
		// Generate Token
		tokenString, _ := GenToken(user.Username)
		c.JSON(http.StatusOK, gin.H{
			"code": 2000,
			"msg":  "success",
			"data": gin.H{"token": tokenString},
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"code": 2002,
		"msg":  "Authentication failed",
	})
	return
}

After the user obtains the Token through the above interface, he will then carry the Token to request our other interfaces. At this time, it is necessary to verify the requested tokens. Obviously, we should implement a middleware to verify the Token. The specific implementation is as follows:

// JWT authmiddleware authentication middleware based on JWT
func JWTAuthMiddleware() func(c *gin.Context) {
	return func(c *gin.Context) {
		// There are three ways for the client to carry a Token. 1 Put in request header 2 Put in the request body 3 Put in URI
		// Here, it is assumed that the Token is placed in the Authorization of the Header and starts with Bearer
		// The specific implementation method here should be determined according to your actual business situation
		authHeader := c.Request.Header.Get("Authorization")
		if authHeader == "" {
			c.JSON(http.StatusOK, gin.H{
				"code": 2003,
				"msg":  "Request header auth Empty",
			})
			c.Abort()
			return
		}
		// Split by space
		parts := strings.SplitN(authHeader, " ", 2)
		if !(len(parts) == 2 && parts[0] == "Bearer") {
			c.JSON(http.StatusOK, gin.H{
				"code": 2004,
				"msg":  "Request header auth Incorrect format",
			})
			c.Abort()
			return
		}
		// parts[1] is the obtained tokenString. We use the previously defined function to parse JWT to parse it
		mc, err := ParseToken(parts[1])
		if err != nil {
			c.JSON(http.StatusOK, gin.H{
				"code": 2005,
				"msg":  "invalid Token",
			})
			c.Abort()
			return
		}
		// Save the currently requested username information to the requested context c
		c.Set("username", mc.Username)
		c.Next() // Subsequent processing functions can use c.Get("username") to obtain the currently requested user information
	}
}

Register a / home route and send a request to verify it.

r.GET("/home", JWTAuthMiddleware(), homeHandler)

func homeHandler(c *gin.Context) {
	username := c.MustGet("username").(string)
	c.JSON(http.StatusOK, gin.H{
		"code": 2000,
		"msg":  "success",
		"data": gin.H{"username": username},
	})
}

If you don't want to implement the above functions yourself, you can also use the package encapsulated by others on Github, such as https://github.com/appleboy/gin-jwt.

Topics: Go