Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How would I implement multiple secrets on the server side? #40

Open
avpavp opened this issue Aug 22, 2019 · 2 comments
Open

How would I implement multiple secrets on the server side? #40

avpavp opened this issue Aug 22, 2019 · 2 comments

Comments

@avpavp
Copy link

avpavp commented Aug 22, 2019

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!

@jess-belliveau
Copy link

jess-belliveau commented Nov 21, 2019

@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)

@pkieltyka
Copy link
Member

note, underlying lib in master has changed to https://github.com/lestrrat-go/jwx but jwtauth api is largely the same

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants