在GO中验证AWS Cognito JWT

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

Validate AWS Cognito JWT in GO

问题

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

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

  1. func auth(w http.ResponseWriter, r *http.Request) {
  2. w.Header().Set("Content-Type", "application/json")
  3. keyset, err := jwk.Fetch(context.Background(), "https://cognito-idp.{Region}.amazonaws.com/{poolID}/.well-known/jwks.json")
  4. if err != nil {
  5. w.WriteHeader(http.StatusInternalServerError)
  6. json.NewEncoder(w).Encode(&model.ErrorResponse{
  7. Response: model.Response{
  8. Result: false,
  9. },
  10. StatusCd: "500",
  11. StatusDesc: "Failed to fetch jwks. Authorization failed.",
  12. Error: "errRes",
  13. })
  14. }
  15. authRequest := &model.AuthRequest{}
  16. json.NewDecoder(r.Body).Decode(&authRequest)
  17. parsedToken, err := jwt.Parse(
  18. []byte(authRequest.Token), //This is the JWT
  19. jwt.WithKeySet(keyset),
  20. jwt.WithValidate(true),
  21. jwt.WithIssuer("https://cognito-idp.{Region}.amazonaws.com/{poolID}"),
  22. jwt.WithAudience("{XX APP CLIENT ID XX}"),
  23. jwt.WithClaimValue("key", authRequest.Access), //This is the Access Token
  24. )
  25. if err != nil {
  26. w.WriteHeader(http.StatusInternalServerError)
  27. json.NewEncoder(w).Encode(&model.ErrorResponse{
  28. Response: model.Response{
  29. Result: false,
  30. },
  31. StatusCd: "500",
  32. StatusDesc: "Failed token parse. Authorization failed.",
  33. Error: "errRes",
  34. })
  35. }
  36. result := parsedToken
  37. json.NewEncoder(w).Encode(result)
  38. }

我正在使用的包有:

  1. "github.com/lestrrat-go/jwx/jwk"
  2. "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)

  1. func auth(w http.ResponseWriter, r *http.Request) {
  2. w.Header().Set("Content-Type", "application/json")
  3. keyset, err := jwk.Fetch(context.Background(), "https://cognito-idp.{Region}.amazonaws.com/{poolID}/.well-known/jwks.json")
  4. if err != nil {
  5. w.WriteHeader(http.StatusInternalServerError)
  6. json.NewEncoder(w).Encode(&model.ErrorResponse{
  7. Response: model.Response{
  8. Result: false,
  9. },
  10. StatusCd: "500",
  11. StatusDesc: "Failed to fetch jwks. Authorization failed.",
  12. Error: "errRes",
  13. })
  14. }
  15. authRequest := &model.AuthRequest{}
  16. json.NewDecoder(r.Body).Decode(&authRequest)
  17. parsedToken, err := jwt.Parse(
  18. []byte(authRequest.Token), //This is the JWT
  19. jwt.WithKeySet(keyset),
  20. jwt.WithValidate(true),
  21. jwt.WithIssuer("https://cognito-idp.{Region}.amazonaws.com/{poolID}"),
  22. jwt.WithAudience("{XX APP CLIENT ID XX}"),
  23. jwt.WithClaimValue("key", authRequest.Access), //This is the Access Token
  24. )
  25. if err != nil {
  26. w.WriteHeader(http.StatusInternalServerError)
  27. json.NewEncoder(w).Encode(&model.ErrorResponse{
  28. Response: model.Response{
  29. Result: false,
  30. },
  31. StatusCd: "500",
  32. StatusDesc: "Failed token parse. Authorization failed.",
  33. Error: "errRes",
  34. })
  35. }
  36. result := parsedToken
  37. json.NewEncoder(w).Encode(result)
  38. }

Packages I am using are

  1. "github.com/lestrrat-go/jwx/jwk"
  2. "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

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "time"
  6. "github.com/golang-jwt/jwt"
  7. "github.com/MicahParks/keyfunc"
  8. )
  9. func main() {
  10. // 从您的AWS区域和userPoolId获取JWKs URL。
  11. //
  12. // 在这里查看AWS文档:
  13. // https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html
  14. regionID := "" // TODO 获取您的AWS Cognito实例的区域ID。
  15. userPoolID := "" // TODO 获取您的AWS Cognito实例的用户池ID。
  16. jwksURL := fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json", regionID, userPoolID)
  17. // 创建keyfunc选项。使用一个记录日志的错误处理程序。当发现一个未知KID签名的JWT或在指定的间隔时间内刷新JWKs时。限制刷新速率。在初始JWKs刷新请求之后超时10秒。这个超时时间也用于创建keyfunc.Get的初始context.Context。
  18. refreshInterval := time.Hour
  19. refreshRateLimit := time.Minute * 5
  20. refreshTimeout := time.Second * 10
  21. refreshUnknownKID := true
  22. options := keyfunc.Options{
  23. RefreshErrorHandler: func(err error) {
  24. log.Printf("jwt.KeyFunc出现错误\n错误:%s\n", err.Error())
  25. },
  26. RefreshInterval: &refreshInterval,
  27. RefreshRateLimit: &refreshRateLimit,
  28. RefreshTimeout: &refreshTimeout,
  29. RefreshUnknownKID: &refreshUnknownKID,
  30. }
  31. // 从给定URL的资源创建JWKs。
  32. jwks, err := keyfunc.Get(jwksURL, options)
  33. if err != nil {
  34. log.Fatalf("无法从给定URL的资源创建JWKs。\n错误:%s\n", err.Error())
  35. }
  36. // 获取要解析的JWT。
  37. jwtB64 := "eyJraWQiOiJmNTVkOWE0ZSIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJLZXNoYSIsImF1ZCI6IlRhc2h1YW4iLCJpc3MiOiJqd2tzLXNlcnZpY2UuYXBwc3BvdC5jb20iLCJleHAiOjE2MTkwMjUyMTEsImlhdCI6MTYxOTAyNTE3NywianRpIjoiMWY3MTgwNzAtZTBiOC00OGNmLTlmMDItMGE1M2ZiZWNhYWQwIn0.vetsI8W0c4Z-bs2YCVcPb9HsBm1BrMhxTBSQto1koG_lV-2nHwksz8vMuk7J7Q1sMa7WUkXxgthqu9RGVgtGO2xor6Ub0WBhZfIlFeaRGd6ZZKiapb-ASNK7EyRIeX20htRf9MzFGwpWjtrS5NIGvn1a7_x9WcXU9hlnkXaAWBTUJ2H73UbjDdVtlKFZGWM5VGANY4VG7gSMaJqCIKMxRPn2jnYbvPIYz81sjjbd-sc2-ePRjso7Rk6s382YdOm-lDUDl2APE-gqkLWdOJcj68fc6EBIociradX_ADytj-JYEI6v0-zI-8jSckYIGTUF5wjamcDfF5qyKpjsmdrZJA"
  38. // 解析JWT。
  39. token, err := jwt.Parse(jwtB64, jwks.KeyFunc)
  40. if err != nil {
  41. log.Fatalf("无法解析JWT。\n错误:%s\n", err.Error())
  42. }
  43. // 检查令牌是否有效。
  44. if !token.Valid {
  45. log.Fatalf("令牌无效。")
  46. }
  47. log.Println("令牌有效。")
  48. }
英文:

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.

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "time"
  6. "github.com/golang-jwt/jwt"
  7. "github.com/MicahParks/keyfunc"
  8. )
  9. func main() {
  10. // Get the JWKs URL from your AWS region and userPoolId.
  11. //
  12. // See the AWS docs here:
  13. // https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html
  14. regionID := "" // TODO Get the region ID for your AWS Cognito instance.
  15. userPoolID := "" // TODO Get the user pool ID of your AWS Cognito instance.
  16. jwksURL := fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json", regionID, userPoolID)
  17. // Create the keyfunc options. Use an error handler that logs. Refresh the JWKs when a JWT signed by an unknown KID
  18. // is found or at the specified interval. Rate limit these refreshes. Timeout the initial JWKs refresh request after
  19. // 10 seconds. This timeout is also used to create the initial context.Context for keyfunc.Get.
  20. refreshInterval := time.Hour
  21. refreshRateLimit := time.Minute * 5
  22. refreshTimeout := time.Second * 10
  23. refreshUnknownKID := true
  24. options := keyfunc.Options{
  25. RefreshErrorHandler: func(err error) {
  26. log.Printf("There was an error with the jwt.KeyFunc\nError:%s\n", err.Error())
  27. },
  28. RefreshInterval: &refreshInterval,
  29. RefreshRateLimit: &refreshRateLimit,
  30. RefreshTimeout: &refreshTimeout,
  31. RefreshUnknownKID: &refreshUnknownKID,
  32. }
  33. // Create the JWKs from the resource at the given URL.
  34. jwks, err := keyfunc.Get(jwksURL, options)
  35. if err != nil {
  36. log.Fatalf("Failed to create JWKs from resource at the given URL.\nError:%s\n", err.Error())
  37. }
  38. // Get a JWT to parse.
  39. jwtB64 := "eyJraWQiOiJmNTVkOWE0ZSIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJLZXNoYSIsImF1ZCI6IlRhc2h1YW4iLCJpc3MiOiJqd2tzLXNlcnZpY2UuYXBwc3BvdC5jb20iLCJleHAiOjE2MTkwMjUyMTEsImlhdCI6MTYxOTAyNTE3NywianRpIjoiMWY3MTgwNzAtZTBiOC00OGNmLTlmMDItMGE1M2ZiZWNhYWQwIn0.vetsI8W0c4Z-bs2YCVcPb9HsBm1BrMhxTBSQto1koG_lV-2nHwksz8vMuk7J7Q1sMa7WUkXxgthqu9RGVgtGO2xor6Ub0WBhZfIlFeaRGd6ZZKiapb-ASNK7EyRIeX20htRf9MzFGwpWjtrS5NIGvn1a7_x9WcXU9hlnkXaAWBTUJ2H73UbjDdVtlKFZGWM5VGANY4VG7gSMaJqCIKMxRPn2jnYbvPIYz81sjjbd-sc2-ePRjso7Rk6s382YdOm-lDUDl2APE-gqkLWdOJcj68fc6EBIociradX_ADytj-JYEI6v0-zI-8jSckYIGTUF5wjamcDfF5qyKpjsmdrZJA"
  40. // Parse the JWT.
  41. token, err := jwt.Parse(jwtB64, jwks.KeyFunc)
  42. if err != nil {
  43. log.Fatalf("Failed to parse the JWT.\nError:%s\n", err.Error())
  44. }
  45. // Check if the token is valid.
  46. if !token.Valid {
  47. log.Fatalf("The token is not valid.")
  48. }
  49. log.Println("The token is valid.")
  50. }

答案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:

确定