Doing user authentication with JWT is a relatively simple way.

Common authentication methods

The mainstream methods of user authentication are broadly classified as session-based and token-based. User authentication with JWT

sesion-based authentication method

  1. User sends username and password to the server.
  2. The server authenticates and saves relevant data in the current conversation (sesion), such as user role, login time, etc.
  3. The server returns a session_id to the user, which is written to the user’s cookie or other storage.
  4. Each subsequent request from the user will send the session_id back to the server via a cookie.
  5. The server receives the session_id, finds the pre-saved data, and thus learns the user’s identity.
  6. The user logs out and the server clears the data corresponding to the session_id.

In this way, the server needs to save the session_id and related data to be verified when receiving a user request, for example, to Redis.

token-based authentication method

  1. User sends username and password to the server.
  2. The server signs the relevant data, such as user ID, authentication expiration date, etc., and then generates a token and returns it to the client.
  3. The client writes the token to local storage.
  4. The token is appended to the header for each subsequent request from the user.
  5. The server gets the header of the user request, gets the user data and does signature verification. If the verification is successful, it means the data has not been tampered with and is valid.

jwt is one of the token-based authentication methods. Here we use jwt-go to use jwt in our Golang project. The following code is sample code, some of the content has been trimmed for reference only.

Generate Token

The server side needs to provide a login interface for user login. The client provides the user name and password, the server checks them, and if they pass the check, the Token is generated and returned to the client.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import (
    jwt "github.com/dgrijalva/jwt-go"
)

func GenerateToken(uid string, role int, expireDuration time.Duration) (string, error) {
    expire := time.Now().Add(expireDuration)
    // 将 uid,用户角色, 过期时间作为数据写入 token 中
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, util.LoginClaims{
        Uid:  uid,
        Role: role,
        StandardClaims: jwt.StandardClaims{
            ExpiresAt: expire.Unix(),
        },
    })
    // SecretKey 用于对用户数据进行签名,不能暴露
    return token.SignedString([]byte(util.SecretKey))
}

func (ctl *LoginController) Login(rw http.ResponseWriter, req *http.Request) {
    var u loginRequest
    json.NewDecoder(req.Body).Decode(&u)

    // 将用户传入的用户名和密码和数据库中的进行比对
    user, err := ctl.db.GetUserByName(req.Context(), u.User)
    if err != nil {
        log.Warn("get user from db by name error: %v", err)
        httputil.Error(rw, errors.ErrInternal)
        return
    }

    if common.EncodePassowrd(u.Password, u.User) != user.Password {
        log.Warn("name [%s] password incorrent", u.User)
        httputil.Error(rw, errors.ErrLoginFailed)
        return
    }

    // 生成返回给用户的 token
    token, err := GenerateToken(user.UID, user.Role, 3*24*time.Hour)
    if err != nil {
        log.Warn("name [%s] generateToken error: %v", u.User, err)
        httputil.Error(rw, errors.ErrInternal)
        return
    }

    res := struct {
        Token string `json:"token"`
    }{
        Token: token,
    }
    httputil.Reply(rw, &res)
}

Checking Token

Here the client is required to set the token obtained through the login interface in the Authorization header of the sent request each time.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
func (a *AuthFilter) Filter(next http.Handler) http.Handler {
    return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
        tokenStr := req.Header.Get("Authorization")
        token, err := jwt.ParseWithClaims(tokenStr, &util.LoginClaims{}, func(token *jwt.Token) (interface{}, error) {
            return []byte(util.SecretKey), nil 
        }   
        if err != nil {
            httputil.Error(rw, errors.ErrUnauthorized)
            return
        }   

        if claims, ok := token.Claims.(*util.LoginClaims); ok && token.Valid {
            log.Infof("uid: %s, role: %v", claims.Uid, claims.Role)
        } else {
            httputil.Error(rw, errors.ErrUnauthorized)
            return
        }   
        next.ServeHTTP(rw, req)
    }   
}

Points to note

  • Since the data in the Token returned by jwt is only Base64 processed and not encrypted, no important information should be put in it.
  • Since jwt Token is stateless, anyone who gets this Token can access it, so to reduce theft, you can set the Token validity period to be shorter. For some important operations, try to authenticate again.
  • Use HTTPS as much as possible to reduce the leakage of Token.