gbrlsnchs / jwt

Go JWT signing, verifying and validating
MIT License
450 stars 34 forks source link

Providing functions for parsing JWK from a File or URL #52

Open VinGarcia opened 3 years ago

VinGarcia commented 3 years ago

Is your feature request related to a problem? Please describe. So currently the library does not provide a way for parsing JWK from URLs as is often necessary when working with some libraries, I had this requirement using auth0 and ory/hydra but this is a common pattern so I am sure this would be a useful feature.

Describe the solution you'd like So I was thinking of an elegant solution that would be concise and type-safe for the user, an example of what I mean is as follows:

var keys []*rsa.PublicKey
err := jwt.GetKeysFromURL(someURL, &keys)
if err != nil {
    log.Fatalf("unable to get keys from URL %s, reason: %s", someURL, err)
}

fmt.Println("number of loaded keys:", len(keys))

And for files:

var keys []*rsa.PublicKey
err := jwt.GetKeysFromURL(filePath, &keys)
if err != nil {
    log.Fatalf("unable to get keys from file %s, reason: %s", filePath, err)
}

fmt.Println("number of loaded keys:", len(keys))

The idea is that the user would be able to pass a list of any type of key he wants and we would parse the JSON from the URL or File and then type-assert the input list to see if it matches what we expect, if yes we fill it, if not we return an error.

I believe it would be possible to implement this without using any reflection, we would probably just need a switch input.(type) {...} construct to handle the different inputs, and I would be glad to make a PR if you like the idea.

Describe alternatives you've considered

I've solved this problem when working with github.com/lestrrat-go/jwx, but I didn't like the way that library does it, it converts the keys into an internal representation and forces the user to iterate over all the keys and manually convert them to the desired type.

I have also considered implementing it myself using this example I found online specific for RSA keys:

https://stackoverflow.com/a/66739021/2905274

But this is a complex function and I don't want to replicate it on all my microservices if I can avoid it.

So what do you think?

VinGarcia commented 3 years ago

Uhm, I was just thinking about what I just wrote, I think there is another alternative you might find cleaner, especially if the user wants to receive different types of keys. The idea would be to define a struct to represent a set of different types of keys in a way that is easy to access, e.g.:

type KeySet struct{
    RSA []*rsa.PublicKey
    OtherKeyType []*some.PackageName
    // other keys types go as new lists here
}

So we can just have a function that returns this struct, e.g.:

keySet, err := jwt.GetKeysFromURL(someURL, &keys)
if err != nil {
    log.Fatalf("unable to get keys from URL %s, reason: %s", someURL, err)
}

fmt.Println("number of loaded RSA keys:", len(keySet.RSA))

This would allow the user to use these keys as he prefers, but we might think of a way of making it easier to use a KeySet for verifying a token, maybe something like:

jwt.VerifyFromKeySet(token, keySet, &payload)

But I am not sure about this last idea yet, maybe you can help me with this one.