3

I am getting "key is of invalid type" when I try to verify a Cognito based JWT in my middleware. Currently I set up the middle ware like this when the Fiber app is being setup:

// read the "jwks.json" that I got from AWS locally
signingKey, err := ioutil.ReadFile("./jwks.json")
if err != nil {
    log.Fatal("Error when opening file: ", err)
}

// pass in the signing key when middle ware is created
app.Get("/api", middleware.Protected(signingKey), handlers.ReadSomeData)

Then my middleware looks like this where most of it is from Go Fiber's JWT example repo.

func Protected(signingKey []byte) func(*fiber.Ctx) error {
    return jwtware.New(jwtware.Config{
        SigningKey:    signingKey,
        ErrorHandler:  jwtError,
        SigningMethod: "RS256",
    })
}

func jwtError(c *fiber.Ctx, err error) error {
    if err.Error() == "Missing or malformed JWT" {
        c.Status(fiber.StatusBadRequest)
        return c.JSON(fiber.Map{"status": "error", "message": err.Error(), "data": nil})

    } else {
        c.Status(fiber.StatusUnauthorized)
        return c.JSON(fiber.Map{"status": "error", "message": err.Error(), "data": nil})
    }
}

After an answer, I tried using the "SigningKeys" param but there was a type mismatch so I ended up reading in the jwks json file like so:

func Protected() func(*fiber.Ctx) error {

    signingKey, err := os.ReadFile("./jwks.json")
    if err != nil {

    }

    x := make(map[string]interface{})

    json.Unmarshal(signingKey, &x)

    return jwtware.New(jwtware.Config{
        SigningKeys:   x,
        ErrorHandler:  jwtError,
        SigningMethod: "RS256",
    })
}

However now my error is "Unexpected jwt key id=XXXXXXXXXXXX"

Anthony Pham
  • 151
  • 2
  • 15
  • Not sure. Try changing `SigningKey` to `SigningKeys`. From docs it seems like a more proper field for jwks. https://github.com/gofiber/jwt – Paperclip Sep 12 '22 at 14:05

2 Answers2

2

It turns out fiber has a built-in functionality to pull the jwks.json data if you provide it a url to the keys. Probably there is also a method to make it load a local file, but with AWS keys you usually do not do it - keys may chnage depending on the environment you are in - production or test.

You need to know your AWS user pool region and that user pool's ID. That is normally provided in the user pool settings view, but you can easily come up with it based on the following example provided in AWS documentation:

https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json

For more see: AWS: Verifying a JSON web token

Here's a simple example to get it working with AWS Cognito JWT url:

    authMiddleware := jwtware.New(jwtware.Config{
        TokenLookup: "header:Authorization",
        AuthScheme:  "Bearer",
        KeySetURLs: []string{
            "https://cognito-idp.some-region-1.amazonaws.com/some-region-1_MYUserPoolId/.well-known/jwks.json",
        },
    })

    // Match any route
    app.Use(authMiddleware, func(c *fiber.Ctx) error {
        return c.SendString(" Yay!")
    })

    log.Fatal(app.Listen(":3000"))

You should now be able to test it with a request like this:

curl --location --request GET 'http://127.0.0.1:3000' \
--header 'Authorization: Bearer MyAWSJWTToken..'

Or with any HTTP client(like Postman). You must provide your JWT in a Authorization header.

See also:

Paperclip
  • 615
  • 5
  • 14
0

The github.com/gofiber/jwt project uses github.com/MicahParks/keyfunc as a JWK Set client. It also has not updated it since github.com/MicahParks/keyfunc was in pre-release. I would recommend not using that project in its current state due to know bugs.

Here's an example of parsing a JWK Set from AWS Cognito and then using the keys in that set to parse a JWT directly from the github.com/Micahparks/keyfunc project:

Here's a link to this example from the github.com/MicahParks/keyfunc project: link

package main

import (
    "fmt"
    "log"
    "time"

    "github.com/golang-jwt/jwt/v4"

    "github.com/MicahParks/keyfunc"
)

func main() {
    // Get the JWKS URL from your AWS region and userPoolId.
    //
    // See the AWS docs here:
    // https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html
    regionID := ""   // TODO Get the region ID for your AWS Cognito instance.
    userPoolID := "" // TODO Get the user pool ID of your AWS Cognito instance.
    jwksURL := fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json", regionID, userPoolID)

    // Create the keyfunc options. Use an error handler that logs. Refresh the JWKS when a JWT signed by an unknown KID
    // is found or at the specified interval. Rate limit these refreshes. Timeout the initial JWKS refresh request after
    // 10 seconds. This timeout is also used to create the initial context.Context for keyfunc.Get.
    options := keyfunc.Options{
        RefreshErrorHandler: func(err error) {
            log.Printf("There was an error with the jwt.Keyfunc\nError: %s", err.Error())
        },
        RefreshInterval:   time.Hour,
        RefreshRateLimit:  time.Minute * 5,
        RefreshTimeout:    time.Second * 10,
        RefreshUnknownKID: true,
    }

    // Create the JWKS from the resource at the given URL.
    jwks, err := keyfunc.Get(jwksURL, options)
    if err != nil {
        log.Fatalf("Failed to create JWKS from resource at the given URL.\nError: %s", err.Error())
    }

    // Get a JWT to parse.
    jwtB64 := "eyJraWQiOiJmNTVkOWE0ZSIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJLZXNoYSIsImF1ZCI6IlRhc2h1YW4iLCJpc3MiOiJqd2tzLXNlcnZpY2UuYXBwc3BvdC5jb20iLCJleHAiOjE2MTkwMjUyMTEsImlhdCI6MTYxOTAyNTE3NywianRpIjoiMWY3MTgwNzAtZTBiOC00OGNmLTlmMDItMGE1M2ZiZWNhYWQwIn0.vetsI8W0c4Z-bs2YCVcPb9HsBm1BrMhxTBSQto1koG_lV-2nHwksz8vMuk7J7Q1sMa7WUkXxgthqu9RGVgtGO2xor6Ub0WBhZfIlFeaRGd6ZZKiapb-ASNK7EyRIeX20htRf9MzFGwpWjtrS5NIGvn1a7_x9WcXU9hlnkXaAWBTUJ2H73UbjDdVtlKFZGWM5VGANY4VG7gSMaJqCIKMxRPn2jnYbvPIYz81sjjbd-sc2-ePRjso7Rk6s382YdOm-lDUDl2APE-gqkLWdOJcj68fc6EBIociradX_ADytj-JYEI6v0-zI-8jSckYIGTUF5wjamcDfF5qyKpjsmdrZJA"

    // Parse the JWT.
    token, err := jwt.Parse(jwtB64, jwks.Keyfunc)
    if err != nil {
        log.Fatalf("Failed to parse the JWT.\nError: %s", err.Error())
    }

    // Check if the token is valid.
    if !token.Valid {
        log.Fatalf("The token is not valid.")
    }
    log.Println("The token is valid.")

    // End the background refresh goroutine when it's no longer needed.
    jwks.EndBackground()
}
Micah Parks
  • 1,504
  • 1
  • 10
  • 22