在GO中验证AWS Cognito JWT

huangapple go评论73阅读模式
英文:

Validate AWS Cognito JWT in GO

问题

我正在尝试验证从AWS Cognito(托管UI)返回的JWT。我注意到一旦在Cognito中完成登录,它会尝试使用一些参数访问我的应用,例如"id_token"和"access_token"。通过jwt.io进行检查,看起来"id_token"是JWT。

作为一个测试,我在GO中编写了一个期望带有JWT令牌和访问令牌的请求体的POST函数(并从这个答案中实现):

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

我正在使用的包有:

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

显然,在令牌解析时失败了。我做错了什么,还有我应该如何处理parsedToken

我对此还不熟悉,所以不知道这是否是正确的方法,希望能得到一些指导。

英文:

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.

答案1

得分: 1

如果您正在使用github.com/golang-jwt/jwt包(以前称为github.com/dgrijalva/jwt-go),那么您可能会从这个示例中受益:

您可以在这里查看更多的JWKs Go示例:github.com/MicahParks/keyfunc/tree/master/examples

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/golang-jwt/jwt"

	"github.com/MicahParks/keyfunc"
)

func main() {

	// 从您的AWS区域和userPoolId获取JWKs URL。
	//
	// 在这里查看AWS文档:
	// https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html
	regionID := ""   // TODO 获取您的AWS Cognito实例的区域ID。
	userPoolID := "" // TODO 获取您的AWS Cognito实例的用户池ID。
	jwksURL := fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json", regionID, userPoolID)

	// 创建keyfunc选项。使用一个记录日志的错误处理程序。当发现一个未知KID签名的JWT或在指定的间隔时间内刷新JWKs时。限制刷新速率。在初始JWKs刷新请求之后超时10秒。这个超时时间也用于创建keyfunc.Get的初始context.Context。
	refreshInterval := time.Hour
	refreshRateLimit := time.Minute * 5
	refreshTimeout := time.Second * 10
	refreshUnknownKID := true
	options := keyfunc.Options{
		RefreshErrorHandler: func(err error) {
			log.Printf("jwt.KeyFunc出现错误\n错误:%s\n", err.Error())
		},
		RefreshInterval:   &refreshInterval,
		RefreshRateLimit:  &refreshRateLimit,
		RefreshTimeout:    &refreshTimeout,
		RefreshUnknownKID: &refreshUnknownKID,
	}

	// 从给定URL的资源创建JWKs。
	jwks, err := keyfunc.Get(jwksURL, options)
	if err != nil {
		log.Fatalf("无法从给定URL的资源创建JWKs。\n错误:%s\n", err.Error())
	}

	// 获取要解析的JWT。
	jwtB64 := "eyJraWQiOiJmNTVkOWE0ZSIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJLZXNoYSIsImF1ZCI6IlRhc2h1YW4iLCJpc3MiOiJqd2tzLXNlcnZpY2UuYXBwc3BvdC5jb20iLCJleHAiOjE2MTkwMjUyMTEsImlhdCI6MTYxOTAyNTE3NywianRpIjoiMWY3MTgwNzAtZTBiOC00OGNmLTlmMDItMGE1M2ZiZWNhYWQwIn0.vetsI8W0c4Z-bs2YCVcPb9HsBm1BrMhxTBSQto1koG_lV-2nHwksz8vMuk7J7Q1sMa7WUkXxgthqu9RGVgtGO2xor6Ub0WBhZfIlFeaRGd6ZZKiapb-ASNK7EyRIeX20htRf9MzFGwpWjtrS5NIGvn1a7_x9WcXU9hlnkXaAWBTUJ2H73UbjDdVtlKFZGWM5VGANY4VG7gSMaJqCIKMxRPn2jnYbvPIYz81sjjbd-sc2-ePRjso7Rk6s382YdOm-lDUDl2APE-gqkLWdOJcj68fc6EBIociradX_ADytj-JYEI6v0-zI-8jSckYIGTUF5wjamcDfF5qyKpjsmdrZJA"

	// 解析JWT。
	token, err := jwt.Parse(jwtB64, jwks.KeyFunc)
	if err != nil {
		log.Fatalf("无法解析JWT。\n错误:%s\n", err.Error())
	}

	// 检查令牌是否有效。
	if !token.Valid {
		log.Fatalf("令牌无效。")
	}

	log.Println("令牌有效。")
}
英文:

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.")
}

答案2

得分: 0

我建议首先进行最小的检查--即,首先尝试仅进行解析而不进行验证,然后逐步添加验证:

  1. jwt.Parse([]byte(token)) // 可能因为JWS而失败
  2. jwt.Parse([]byte(token), jwt.WithKeySet(...)) // 应该没问题?
  3. jwt.Parse(..., jwt.WithValidation(true), ...) // 逐步添加条件

请注意,我不知道id_token中有什么内容,因为我从未使用过Cognito。如果它是一个原始的JWT,你不应该需要一个密钥集,而且(1)应该可以工作。

英文:

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.

huangapple
  • 本文由 发表于 2021年7月28日 04:06:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/68551177.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定