0

I am trying validate JWT returned from a login from AWS Cognito (hosted UI). I noticed that once the login is done in cognito, it tries to access my app with some params like "id_token" and "access_token". Checked with jwt.io and looks like "id_token" is the jwt.

As a test, I wrote a post function in GO expecting a body with the jwt token and the access token (and implemented from this answer)

func auth(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")

    keyset, err := jwk.Fetch(context.Background(), "https://cognito-idp.{Region}.amazonaws.com/{poolID}/.well-known/jwks.json")
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        json.NewEncoder(w).Encode(&model.ErrorResponse{
            Response: model.Response{
                Result: false,
            },
            StatusCd:   "500",
            StatusDesc: "Failed to fetch jwks. Authorization failed.",
            Error:      "errRes",
        })
    }
    authRequest := &model.AuthRequest{}

    json.NewDecoder(r.Body).Decode(&authRequest)

    parsedToken, err := jwt.Parse(
        []byte(authRequest.Token), //This is the JWT
        jwt.WithKeySet(keyset),
        jwt.WithValidate(true),
        jwt.WithIssuer("https://cognito-idp.{Region}.amazonaws.com/{poolID}"),
        jwt.WithAudience("{XX APP CLIENT ID XX}"),
        jwt.WithClaimValue("key", authRequest.Access), //This is the Access Token
    )
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        json.NewEncoder(w).Encode(&model.ErrorResponse{
            Response: model.Response{
                Result: false,
            },
            StatusCd:   "500",
            StatusDesc: "Failed token parse. Authorization failed.",
            Error:      "errRes",
        })
    }

    result := parsedToken
    json.NewEncoder(w).Encode(result)
}

Packages I am using are

"github.com/lestrrat-go/jwx/jwk"
"github.com/lestrrat-go/jwx/jwt"

Obviously, it failed at the token parse. What am I doing wrong and also what should I do with the parsedToken ?

I am new to this so, I have no clue if this is the correct approach and would really like some guidance.

zer0
  • 4,657
  • 7
  • 28
  • 49
  • I dont get this line ```jwt.WithClaimValue("key", authRequest.Access)``` does authRequest.Access evaluate to "access" ? With the jwt as a string you can just B64 decode to see what claims are there, if its not there then that is at least on place things will fail. You should be able to decode it here https://jwt.io/ I think that line WithClaimValue should be key = "token_use" and then value is "id" or "access" – Nigel Savage Jul 27 '21 at 20:24
  • @NigelSavage Thank you - I changed it to `jwt.WithClaimValue("token_use", "access")` but it still fails. – zer0 Jul 27 '21 at 20:35
  • I would suggest to decode the jwt token and see the claims – Nigel Savage Jul 28 '21 at 09:40

2 Answers2

1

If you're using the github.com/golang-jwt/jwt package (formally known as github.com/dgrijalva/jwt-go,) then you'd probably benefit from this example:

You can check out more JWKs Go examples here: github.com/MicahParks/keyfunc/tree/master/examples.

package main

import (
    "fmt"
    "log"
    "time"

    "github.com/golang-jwt/jwt"

    "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.
    refreshInterval := time.Hour
    refreshRateLimit := time.Minute * 5
    refreshTimeout := time.Second * 10
    refreshUnknownKID := true
    options := keyfunc.Options{
        RefreshErrorHandler: func(err error) {
            log.Printf("There was an error with the jwt.KeyFunc\nError:%s\n", err.Error())
        },
        RefreshInterval:   &refreshInterval,
        RefreshRateLimit:  &refreshRateLimit,
        RefreshTimeout:    &refreshTimeout,
        RefreshUnknownKID: &refreshUnknownKID,
    }

    // 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\n", 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\n", err.Error())
    }

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

    log.Println("The token is valid.")
}
Micah Parks
  • 1,504
  • 1
  • 10
  • 22
0

I would suggest to start out by doing the minimal checks -- i.e., first try just parsing without validation, then add validations one by one:

  1. jwt.Parse([]byte(token)) // probably fails because of JWS
  2. jwt.Parse([]byte(token), jwt.WithKeySet(...)) // should be OK?
  3. jwt.Parse(..., jwt.WithValidation(true), ...) // add conditions one by one

Please note that I have no idea what's in id_token, as I have never used Cognito If it's a raw JWT, you shouldn't need a key set, and (1) should work.

lestrrat
  • 31
  • 2
  • 1
    I ended up using this solution for now https://stackoverflow.com/a/66574942/1456913 but I will also try your suggestion. Also, if you are THE lestrrat, thank you for your contribution :) – zer0 Jul 29 '21 at 15:23