Open avpavp opened 5 years ago
@avpavp, I had a similar requirement that I was playing around with. Support for each caller to have its own shared secret. I've ended up with the following as the starting function for my middleware, replacing the original "jwtauth.Verifier" call:
func verifyCaller(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("Executing Verifier")
encodedToken := jwtauth.TokenFromHeader(r)
if encodedToken != "" {
encodedPayload := strings.Split(encodedToken, ".")
decodedPayload, err := base64.StdEncoding.DecodeString(encodedPayload[1])
if err != nil {
log.Println("Payload decode error:", err)
http.Error(w, http.StatusText(500), 500)
return
}
payload := make(map[string]string)
err = json.Unmarshal(decodedPayload, &payload)
caller := payload["caller"]
// Go off to secret vault, get secret for caller
sharedSecret := determineCallerSecret(caller)
if sharedSecret != "" {
// Secret has been determined - build tokenAuth, verify and modify context
log.Println("Caller: " + caller + ", Secret located.")
tokenAuth = jwtauth.New("HS256", []byte(sharedSecret), nil)
ctx := r.Context()
token, err := jwtauth.VerifyRequest(tokenAuth, r, jwtauth.TokenFromHeader)
ctx = jwtauth.NewContext(ctx, token, err)
next.ServeHTTP(w, r.WithContext(ctx))
} else {
log.Println("Err: secret lookup failed")
http.Error(w, http.StatusText(500), 500)
return
}
} else {
log.Println("Err: JWT token payload not found.")
http.Error(w, http.StatusText(401), 401)
return
}
})
}
This decodes the payload of the JWT and looks for a required entry "caller" - this value is then passed to a function which connects to how ever you are storing your secrets. I am yet to really test this, but some initial curl attempts with varying tokens look good.
I am going to try and find a better way to decode the payload and you may also want to support the ALG type changing based on caller. Also determineCallerSecret() should return an err, not just an empty string - but this was quick first pass.
EDIT: I'll also include what my testing router looks like (using gorilla/mux):
r := mux.NewRouter()
api := r.PathPrefix("/api/v1").Subrouter()
// Custom middleware to determine the caller, set the secret and verify the JWT
api.Use(verifyCaller)
// jwtauth middlware to authenticate based on the token
api.Use(jwtauth.Authenticator)
api.HandleFunc("", get).Methods(http.MethodGet)
note, underlying lib in master has changed to https://github.com/lestrrat-go/jwx but jwtauth api is largely the same
I'd like to have the possibility to have different clients use different secrets - how would I test against multiple secrets on the server side?
Thanks!