2

I am using auth0, and I have two clients (ios, react) and a Go backend API using go-auth0.

I followed the documentation and made a Verify method that looks like this:

func Verify(handle httprouter.Handle) httprouter.Handle {
    return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
        auth0Domain := viper.GetString("auth0.issuer")
        audience := []string{viper.GetString("auth0.audience")}

        client := auth0.NewJWKClient(auth0.JWKClientOptions{URI: auth0Domain + ".well-known/jwks.json"}, nil)
        configuration := auth0.NewConfiguration(client, audience, auth0Domain, jose.RS256)
        validator := auth0.NewValidator(configuration, nil)
        _, err := validator.ValidateRequest(r)
        if err != nil {
            w.Header().Set("Content-Type", "application/json")
            w.WriteHeader(http.StatusUnauthorized)
            json.NewEncoder(w).Encode(map[string]string{"error": "Unauthorized"})
            return
        }

        handle(w, r, p)
    }
}

Unfortunately I notice that it takes ~400ms for the first verify, and subsequent ones take ~50ms.

However, if I initialize a struct with a field for the validator, move all the setup code into an Initialize(), then it takes only ~1ms:

func Verify(handle httprouter.Handle) httprouter.Handle {
    return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {

        _, err := a.validator.ValidateRequest(r)
        if err != nil {
            w.Header().Set("Content-Type", "application/json")
            w.WriteHeader(http.StatusUnauthorized)
            json.NewEncoder(w).Encode(map[string]string{"error": "Unauthorized"})
            return
        }

        handle(w, r, p)
    }
}

Is this a bad idea to do? I am just learning about JWK today and looking at the auth0 code it seems they do construct a cache but I'm not entirely understanding how it works.

Can someone please let me know if moving the config into a struct and using its validator is a good idea?

UPDATE

auth0 has a builtin method to do this! Here's an example:

auth0.NewJWKClientWithCache(auth0.JWKClientOptions{URI: a.issuer + ".well-known/jwks.json"}, nil, auth0.NewMemoryKeyCacher(time.Duration(10)*time.Second, 5))

Use this method so it caches for you! :)

user1354934
  • 8,139
  • 15
  • 50
  • 80

1 Answers1

2

It should almost definitely be safe to cache the client object, and doing so tends to be a good idea in general. ("Create one client and reuse it" is a good general rule.)

My understanding is that the signing keys for JWTs are typically valid for months if not longer. (Auth0's documentation notes that its JWKS documents only ever have a single key, but it will issue signed tokens all the time, so the keys must be valid for "a while".) RFC 7517 doesn't define any expiration-related parameters on either a JWKS or an individual JWK, and I think the best practice is to use ordinary HTTP caching controls on the JWKS endpoint to refresh it occasionally, but not that often.

Community
  • 1
  • 1
David Maze
  • 130,717
  • 29
  • 175
  • 215
  • Thanks! I think in that case I will add a `expAt` key to the struct of 6 hours or something and hope that works. – user1354934 Aug 26 '18 at 15:52
  • hey i updated above since i realized that there is indeed a client with cache option :) – user1354934 Aug 26 '18 at 16:47
  • 1
    Take into account that when the keys are compromised - they might be regenerated in your Identity Provider. You will be still using your old compromised cached keys for token verification. – Yuriy Mar 05 '21 at 11:38