Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
November 17, 2022 08:42 am GMT

Web Development JWT Practices

Foreword

The previous post briefly introduced a high-performance Go HTTP framework - Hertz. This article is based on a demo from the Hertz open-source repository. tree/main/bizdemo/hertz_jwt), which describes how to use Hertz to complete the JWT authentication and authorization process.

It should be noted here that hertz-jwt is one of many external extensions to Hertz, it is forked from gin-jwt and adapted to Hertz.

Hertz's rich extension ecosystem is a great convenience for developers and worth exploring beyond this article.

image-20221114215243547

Introduction

  • Use hz to generate code
  • Use JWT to complete login and authentication
  • Use Gorm and MySQL

Download

git clone https://github.com/cloudwego/hertz-examples.gitcd bizdemo/hertz_jwt

Architecture

hertz_jwt Makefile # Generate hertz scaffolding code using the hz command line tool biz  dal   init.go    mysql       init.go # Initialising database connections       user.go # Database operations  handler   ping.go   register.go # Register handler  model   sql    user.sql   user.go # Defining the database model  mw   jwt.go # Initialising the hertz-jwt middleware  router   register.go  utils      md5.go # MD5 docker-compose.yml # MySQL container go.mod go.sum main.go # Hertz startup functions readme.md router.go # Routing Registration router_gen.go

Analysis

The list of interfaces for this demo is as follows.

// customizeRegister registers customize routers.func customizedRegister(r *server.Hertz) {    r.POST("/register", handler.Register)    r.POST("/login", mw.JwtMiddleware.LoginHandler)    auth := r.Group("/auth", mw.JwtMiddleware.MiddlewareFunc())    auth.GET("/ping", handler.Ping)}

User Registration

The user data of the current demo is persisted through mysql by gorm, so before logging in, the user needs to be registered. The registration process is as follows.

  1. get the username, password and email address
  2. determine if the user exists
  3. create the user

User log-in (Authentication)

The server needs to validate the user account and password and sign a jwt token when the user logs in for the first time.

JwtMiddleware, err = jwt.New(&jwt.HertzJWTMiddleware{    Key:           []byte("secret key"),    Timeout:       time.Hour,    MaxRefresh:    time.Hour,    Authenticator: func(ctx context.Context, c *app.RequestContext) (interface{}, error) {        var loginStruct struct {            Account  string `form:"account" json:"account" query:"account" vd:"(len($) > 0 && len($) < 30); msg:'Illegal format'"`            Password string `form:"password" json:"password" query:"password" vd:"(len($) > 0 && len($) < 30); msg:'Illegal format'"`        }        if err := c.BindAndValidate(&loginStruct); err != nil {            return nil, err        }        users, err := mysql.CheckUser(loginStruct.Account, utils2.MD5(loginStruct.Password))        if err != nil {            return nil, err        }        if len(users) == 0 {            return nil, errors.New("user already exists or wrong password")        }        return users[0], nil    },    PayloadFunc: func(data interface{}) jwt.MapClaims {        if v, ok := data.(*model.User); ok {            return jwt.MapClaims{                jwt.IdentityKey: v,            }        }        return jwt.MapClaims{}    },})
  • Authenticator: A function that sets up the user information to be authenticated when logging in. The demo defines a loginStruct structure that receives the user login information and authenticates the validity. The return value of this function, users[0], will provide the payload data source for the subsequent generation of the jwt token.
  • PayloadFunc: Its input is the return value of Authenticator, which is responsible for parsing users[0] and injecting the username into the payload part of the token.

  • Key: specifies the key used to encrypt the jwt token as "secret key".

  • Timeout: specifies that the token is valid for one hour.

  • MaxRefresh: sets the maximum token refresh time, allowing the client to refresh the token within TokenTime + MaxRefresh, appending a Timeout duration.

Return of Token

JwtMiddleware, err = jwt.New(&jwt.HertzJWTMiddleware{    LoginResponse: func(ctx context.Context, c *app.RequestContext, code int, token string, expire time.Time) {        c.JSON(http.StatusOK, utils.H{            "code":    code,            "token":   token,            "expire":  expire.Format(time.RFC3339),            "message": "success",        })    },})
  • LoginResponse: After a successful login, jwt token information is returned with the response, you can customize the content of this part, but be careful not to change the function signature as it is strongly bound to LoginHandler.

Token Validation

When a client requests a route with a jwt middleware configured, the server verifies the jwt token it carries.

JwtMiddleware, err = jwt.New(&jwt.HertzJWTMiddleware{    TokenLookup:   "header: Authorization, query: token, cookie: jwt",    TokenHeadName: "Bearer",    HTTPStatusMessageFunc: func(e error, ctx context.Context, c *app.RequestContext) string {        hlog.CtxErrorf(ctx, "jwt biz err = %+v", e.Error())        return e.Error()    },    Unauthorized: func(ctx context.Context, c *app.RequestContext, code int, message string) {        c.JSON(http.StatusOK, utils.H{            "code":    code,            "message": message,        })    },})
  • TokenLookup: This is used to set the source of the token, you can choose header, query, cookie, or param, the default is header:Authorization, the first one read from the left side takes precedence. The current demo will use header as the data source, so when accessing the /ping interface, you will need to store the token information in the HTTP Header.
  • TokenHeadName: This is used to set the prefix used to retrieve the token from the header, the default is "Bearer".
  • HTTPStatusMessageFunc: This is used to set the error message that will be included in the response when an error occurs in the jwt validation process, you can wrap these yourself.
  • Unauthorized: used to set the response function for a failed jwt validation process, the current demo returns the error code and error message.

Extracting User Information

JwtMiddleware, err = jwt.New(&jwt.HertzJWTMiddleware{    IdentityKey: IdentityKey,    IdentityHandler: func(ctx context.Context, c *app.RequestContext) interface{} {        claims := jwt.ExtractClaims(ctx, c)        return &model.User{            UserName: claims[IdentityKey].(string),        }    },})// Ping .func Ping(ctx context.Context, c *app.RequestContext) {    user, _ := c.Get(mw.IdentityKey)    c.JSON(200, utils.H{        "message": fmt.Sprintf("username:%v", user.(*model.User).UserName),    })}
  • IdentityHandler: The function used to set the identity information to be retrieved. In the demo, here the payload of the token is extracted and the username is stored in the context information with the IdentityKey.
  • IdentityKey: sets the key used to retrieve the identity, the default is "identity".
  • Ping: Constructs the response. Retrieves the username information from the context information and returns it.

Other Components

Code Generation

Most of the code above is scaffolded code generated through the hz command line tool, so developers don't need to spend a lot of time building a good code structure and just focus on writing the business.

hz new -mod github.com/cloudwego/hertz-examples/bizdemo/hertz_jwt

Most of the code above is scaffolded code generated through the hz command line tool, so developers don't need to spend a lot of time building a good code structure and just focus on writing the business.

Sample CodeSourced from hz Official Document

// idl/hello.thriftnamespace go hello.examplestruct HelloReq {    1: string Name (api.query="name"); // api annotations to facilitate parameter binding}struct HelloResp {    1: string RespBody;}service HelloService {    HelloResp HelloMethod(1: HelloReq request) (api.get="/hello");}// execute under $GOPATHhz new -idl idl/hello.thrift

Parameter Binding

Hertz uses the open-source library go-tagexpr for parameter binding and validation, which the current demo uses for user registration and login.

// registervar registerStruct struct {    // Binding and validation of parameters by declaring tags    Username string `form:"username" json:"username" query:"username" vd:"(len($) > 0 && len($) < 128); msg:'Illegal format'"`    Email    string `form:"email" json:"email" query:"email" vd:"(len($) > 0 && len($) < 128) && email($); msg:'Illegal format'"`    Password string `form:"password" json:"password" query:"password" vd:"(len($) > 0 && len($) < 128); msg:'Illegal format'"`}// loginvar loginStruct struct {    // Binding and validation of parameters by declaring tags    Account  string `form:"account" json:"account" query:"account" vd:"(len($) > 0 && len($) < 30); msg:'Illegal format'"`    Password string `form:"password" json:"password" query:"password" vd:"(len($) > 0 && len($) < 30); msg:'Illegal format'"`}if err := c.BindAndValidate(&loginStruct); err != nil {    return nil, err}
  • vd: used to validate the data format, e.g. string length checks (128), email type checks (email)
  • form: binds the body content of the request. content-type -> multipart/form-data or application/x-www-form-urlencoded, binds the key-value of the form
  • json: bind the body content of the request content-type -> application/json, bind the json parameters
  • query: binds the query parameter of the request

The parameter bindings need to be in accordance with the priority.

path > form > query > cookie > header > json > raw_body

Further usage can be found in Documentation

Gorm

For more information on Gorm's operation of MySQL, please refer to Gorm

Run

  • Running the mysql container
cd bizdemo/hertz_jwt && docker-compose up
  • Creating a mysql database

After connecting to mysql, execute user.sql

  • run the demo
cd bizdemo/hertz_jwt && go run main.go

API Requests

Register

# requestcurl --location --request POST 'localhost:8888/register' \--header 'Content-Type: application/json' \--data-raw '{    "Username": "admin",    "Email": "[email protected]",    "Password": "admin"}'# response{    "code": 200,    "message": "success"}

Login

# requestcurl --location --request POST 'localhost:8888/login' \--header 'Content-Type: application/json' \--data-raw '{    "Account": "admin",    "Password": "admin"}'# response{    "code": 200,    "expire": "2022-11-16T11:05:24+08:00",    "message": "success",    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2Njg1Njc5MjQsImlkIjoyLCJvcmlnX2lhdCI6MTY2ODU2NDMyNH0.qzbDJLQv4se6dOHN51p21Rp3DjV1Lf131l_5k4cK6Wk"}

Routing Access

# requestcurl --location --request GET 'localhost:8888/auth/ping' \--header 'Authorization: Bearer ${token}'# response{    "message": "username:admin"}

References


Original Link: https://dev.to/baize1998/web-development-jwt-practices-59cn

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To