0

I'm having trouble converting a JWT payload back to a struct in golang

I have two servers which communicate with each other and have a JWT auth to enforce security.

The payloads use the below struct

type ResponseBody struct {
    Header       dto.MessageHeader       `json:"message_header"`
    OrderBodyParams dto.OrderBodyParams `json:"order_response"`
    Status              string                  `json:"status"`
    ErrorMessage        string                  `json:"errors"`
}

Server A takes this struct - converts it into byte date and sends it as a JWT payload

the relevant code is below

func request(secretKey string, path string, method string, requestParams interface{}, response interface{}) error {

    tr := &http.Transport{
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    }
    client := &http.Client{
        Timeout:   time.Second * 15,
        Transport: tr,
    }

    //convert body into byte data
    requestBody, err := json.Marshal(requestParams)
    if err != nil {
        return err
    }

    tokenString, expirationTime, err := authentication.GenerateJWTAuthToken(secretKey, requestBody)
    if err != nil {
      return err
    }

    req, _ := http.NewRequest(method, path, bytes.NewBuffer([]byte(tokenString)))
    req.Header.Set("Content-Type", "application/json")
    req.AddCookie(&http.Cookie{
        Name:    "token",
        Value:   tokenString,
        Expires: expirationTime,
    })

    _, err = client.Do(req)

    if err != nil {
       return err
    }

    return nil
}

As you can see , i'm merely converting the body to byte data - and converting it into a JWT payload

the function GenerateJWTAuthToken is like the below

type Claims struct {
    Payload []byte `json:"payload"`
    jwt.StandardClaims
}


func GenerateJWTAuthToken(secretKey string, payload []byte) (string, time.Time, error) {
    var jwtKey = []byte(secretKey)

    // Set expiration to 5 minutes from now (Maybe lesser?)
    expirationTime := time.Now().Add(5 * time.Minute)
    // create the payload
    claims := &Claims{
        Payload: payload,
        StandardClaims: jwt.StandardClaims{
            ExpiresAt: expirationTime.Unix(),
        },
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    tokenString, err := token.SignedString(jwtKey)

    if err != nil {
        return "", time.Time{}, fmt.Errorf("Error generating JWT token : %+v", err)
    }
    return tokenString, expirationTime, nil
}

Now server B - recieves the data and validates the JWT and converts the byte payload back to the ResponseBody struct

func Postback(appState *appsetup.AppState) http.HandlerFunc {
    fn := func(w http.ResponseWriter, r *http.Request) {

        body, err := ioutil.ReadAll(r.Body)
        tokenString := string(body)
        if err != nil {
            w.WriteHeader(http.StatusUnauthorized)
            return
        }

        // Verify the JWT token send from server A and ensure its a valid request
        // If the token is invalid , dont proceed further
        token, payload, err := authentication.VerifyJWTToken(appState.SecretKey, tokenString)

        if err != nil {
            if err == jwt.ErrSignatureInvalid {
                w.WriteHeader(http.StatusUnauthorized)
                return
            }
            w.WriteHeader(http.StatusBadRequest)
            return
        }
        if !token.Valid {
            w.WriteHeader(http.StatusUnauthorized)
            return
        }


        requestData := ResponseBody{}
        err = binary.Read(bytes.NewReader(payload), binary.LittleEndian, &requestData)
        if err != nil {
            fmt.Printf("Couldnt convert payload body to struct : %+v ", err)
            return
        }
        return requestData
    }
    return http.HandlerFunc(fn)
}

The VerifyJWTToken function is pretty straightforward as well (i'm hoping)

func VerifyJWTToken(secretKey string, tokenString string) (*jwt.Token, []byte, error) {
    var jwtKey = []byte(secretKey)
    var payload []byte
    claims := &Claims{}

    token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {

        if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
        }

        return jwtKey, nil
    })

    if err != nil {
        return nil, payload, err
    }

    return token, claims.Payload, nil
}

But i'm having trouble converting the payload back to the ResponseBody struct -

It seems to fail at the line err = binary.Read(bytes.NewReader(bodyBuffer), binary.LittleEndian, &requestData)

with the below error

msg":"Couldnt convert payload body to struct : binary.Read: invalid type *ResponseBody "

Is there something i'm missing?

icza
  • 389,944
  • 63
  • 907
  • 827
Jayaram
  • 6,276
  • 12
  • 42
  • 78
  • Off topic but why use a cookie in server to server communication, you're already setting a header for something else? – Dominic Aug 22 '19 at 14:34
  • @Dominic - you're right. Out of curiosity , are there restrictions as to how big the payload needs to be when you send it as a header? – Jayaram Aug 22 '19 at 14:46
  • There is technically no limit but servers do impose some - https://stackoverflow.com/questions/686217/maximum-on-http-header-values But that's better than cookies which have a max of 4KB (I think) – Dominic Aug 22 '19 at 15:39

1 Answers1

0

You're using JSON to marshal your struct:

requestBody, err := json.Marshal(requestParams)`

So you should use JSON unmarshaling to get back the struct. Yet, you're using binary.Read() to read it:

err = binary.Read(bytes.NewReader(payload), binary.LittleEndian, &requestData)

Instead do it like this:

err = json.Unmarshal(payload, &requestData)
icza
  • 389,944
  • 63
  • 907
  • 827