make jwt encode faster

770 Views Asked by At

I have a /token endpoint that is working with a password grant. Because it's encoding a JWT token, it has high latency of about 1 second. Is there a way to make signing the JWT faster?

I'm using Go with the github.com/dgrijalva/jwt-go library.

package main

import (
    "crypto/rsa"
    "git.snappfood.ir/golang/framework/assert"
    "io/ioutil"
    "time"

    
    "github.com/dgrijalva/jwt-go"
    "github.com/sirupsen/logrus"
)

var (
    private *rsa.PrivateKey
    public  *rsa.PublicKey
)

func main() {
    var err error
    var priv, pub []byte
    pub, err = ioutil.ReadFile("public.pem")
    if err!=nil{
        panic(err)
    }
    priv, err = ioutil.ReadFile("private.pem")
    if err!=nil{
        panic(err)
    }

    public, err = jwt.ParseRSAPublicKeyFromPEM(pub)
    if err!=nil{
        panic(err)
    }

    private, err = jwt.ParseRSAPrivateKeyFromPEM(priv)
    if err!=nil{
        panic(err)
    }

    data := map[string]interface{}{
        "jti": "dara",
        "scopes": func() []string {
            return []string{"sara", "dara"}
        }(),
        "aud": "aud",
        "sub": "",
    }
    cl := jwt.MapClaims{}

    for k, v := range data {
        cl[k] = v
    }
    cl["iat"] = time.Now().Add(-6 * time.Hour).Unix()
    cl["exp"] = time.Now().UTC().Add(1 * time.Hour).Unix()

    t := jwt.NewWithClaims(jwt.GetSigningMethod("RS256"), cl)

    t2 := time.Now()
    tokenString, err := t.SignedString(private)
    assert.Nil(err)
    logrus.Warn(time.Since(t2))
    logrus.Warn(tokenString)

}
2

There are 2 best solutions below

0
CESCO On

In Golang you can always step down to C level. But with that comes a large increase of complexity of shipping your app. But if any ms counts for you, it might be the way out.

But at the same time, (I have not profiled anything) it is still Go. You could easily parallelise the private/public ParseRSAPrivateKeyFromPEM calls to shave a few ms off

0
offchance On

Without jumping into library details, it's small but you don't need to initialize a map[string]interface then loop over it to populate a jwt.MapClaims{}. You can just start with a jwt.MapClaims{} like this:

cl := jwt.MapClaims{
    "jti": "dara",
    "scopes": func() []string {
        return []string{"sara", "dara"}
    }(),
    "aud": "aud",
    "sub": "",
    // no need to do these separately:
    "iat": time.Now().Add(-6 * time.Hour).Unix(),
    "exp": time.Now().UTC().Add(1 * time.Hour).Unix(),
}

Consider using Benchmark Tests to experiment with different approaches.


EDIT:

I think time.Now() can be slow sometimes depending on the machine/os and can be a possible factor. So make sure to benchmark time.Now() and look for issues such as this if you want to dig deeper