Closed ganlvtech closed 6 months ago
My temporary hack for this bug:
a := api.NewStoreClient(c)
// region fix token bug
_ = a.Token.Generate()
claims := jwt.MapClaims{}
token, _, _ := jwt.NewParser().ParseUnverified(a.Token.Bearer, claims)
issuedAt := time.Now().Unix() - 10
expiredAt := issuedAt + 3600
claims["iat"] = issuedAt
claims["exp"] = expiredAt
a.Token.Bearer, _ = token.SignedString(a.Token.AuthKey)
a.Token.ExpiredAt = expiredAt
// endregion fix token bug
response, err := a.GetTransactionInfo(ctx, transactionId)
@ganlvtech
I am confused that why my server is 1 second faster than Apple's server
? Does the api return 200 when the server time corresponding with Apple server time?
Furthermore, I try to change the iat
faster than server time with 100 seconds, the api return 200 in my test environment.
issuedAt := time.Now().Add(time.Duration(100) * time.Second).Unix()
expiredAt := time.Now().Add(time.Duration(1) * time.Hour).Unix()
Maybe the issuedAt
and expiredAt
of the struct Token
could be exposed, so that user could set them.
@richzw if time.Now() is 100 seconds faster. the expiredAt will be 60 * 60 + 100 seconds faster. You can try
expiredAt := time.Now().Add(time.Duration(1) * time.Hour + 100 * time.Second).Unix()
@richzw if time.Now() is 100 seconds faster. the expiredAt will be 60 * 60 + 100 seconds faster. You can try
expiredAt := time.Now().Add(time.Duration(1) * time.Hour + 100 * time.Second).Unix()
@ganlvtech , Thank you very much for your quick response. Sorry for misunderstand this issue before.
The issuedAt
and expiredAt
of the struct Token
are exposed for user could set them to prevent the default behavior cause this error.
@richzw this pull request make me must update the StoreConfig everytime I call the API. maybe we need a func() time.Time {}
@richzw this pull request make me must update the StoreConfig everytime I call the API. maybe we need a
func() time.Time {}
IMO, when your server time is correctly, there is no need to set the server time when the API is invoked every time.
In other side, it may not be convenient to update StoreConfig when the API is invoked. I am confused that func() time.Time {}
. Could you please give me some sample codes for that? The usage of this function func() time.Time {}
. @ganlvtech
https://github.com/awa/go-iap/blob/7499815685d853c5ca0d77168b3cdfd549058849/appstore/api/store.go#L40-L48 https://github.com/awa/go-iap/blob/947c9eb50105b310e6cc20050b12c4e88dccd99e/appstore/api/token.go#L78-L85
type StoreConfig struct {
KeyContent []byte // Loads a .p8 certificate
KeyID string // Your private key ID from App Store Connect (Ex: 2X9R4HXF34)
BundleID string // Your app’s bundle ID
Issuer string // Your issuer ID from the Keys page in App Store Connect (Ex: "57246542-96fe-1a63-e053-0824d011072a")
Sandbox bool // default is Production
TokenIssueAtFunc func() int64 // The token’s creation time, in UNIX time. Default is current timestamp.
TokenExpiredAtFunc func() int64 // The token’s expiration time, in UNIX time. Default is one hour later.
}
func (t *Token) Generate() error {
// ...
issuedAt := time.Now().Unix()
if t.IssueAtFunc != nil {
issuedAt = t.IssueAtFunc()
}
expiredAt := time.Now().Add(time.Duration(1) * time.Hour).Unix()
if t.ExpiredAtFunc != nil {
expiredAt = t.ExpiredAtFunc()
}
// ...
}
func main() {
a := api.NewStoreClient(&api.StoreConfig{
TokenIssueAtFunc: func() int64 {
return time.Now().Unix() - 10
},
TokenExpiredAtFunc: func() int64 {
return time.Now().Unix() - 10 + 3600
},
})
response, err := a.GetTransactionInfo(ctx, transactionId)
}
Just like golang-jwt
provides a TimeFunc
instead of a exact IssuedAt ExpiredAt int value as config.
https://pkg.go.dev/github.com/golang-jwt/jwt/v5#WithTimeFunc
Just like
golang-jwt
provides aTimeFunc
instead of a exact IssuedAt ExpiredAt int value as config.https://pkg.go.dev/github.com/golang-jwt/jwt/v5#WithTimeFunc
Thank you very much for your detailed response.
Here are my opinions about your suggestion. Please correct me if I am misunderstanding or something missing.
TokenIssueAtFunc func() int64
, we should update the StoreConfig every time when the API is invoked.TokenIssueAtFunc
is assigned to variable issuedAt
eventually. It seems that same to the variable TokenIssueAt int64
definition. WithTimeFunc
of jwt. It seems the primary use case is testing.
WithTimeFunc returns the ParserOption for specifying the time func. The primary use-case for this is testing
WithTimeFunc
used for validator. @ganlvtech
https://github.com/awa/go-iap/blob/63f7061999c16ce6ace18caef90b5cf5b5ba2ba4/appstore/api/token.go#L32-L33 https://github.com/awa/go-iap/blob/63f7061999c16ce6ace18caef90b5cf5b5ba2ba4/appstore/api/token.go#L51-L63 https://github.com/awa/go-iap/blob/63f7061999c16ce6ace18caef90b5cf5b5ba2ba4/appstore/api/store.go#L556
If I use IssuedAtFunc
to replace IssuedAt
field of struct Token
. GenerateIfExpired()
will call Generate()
if needed. And then Generate()
will call IssuedAtFunc()
to get current time as iat
. If it's IssuedAt
int64 field, it will not be updated to the latest time when GenerateIfExpired()
be executed.
func (t *Token) Generate() error {
// ...
issuedAt := time.Now().Unix()
if t.IssuedAtFunc != nil {
issuedAt = t.IssuedAtFunc()
}
expiredAt := time.Now().Add(time.Duration(1) * time.Hour).Unix()
if t.ExpiredAtFunc != nil {
expiredAt = t.ExpiredAtFunc()
}
// ...
}
By the way, it should be called IssuedAt
instead of IssueAt
as the RFC. https://www.rfc-editor.org/rfc/rfc7519#section-4.1.6
One more thing, this bug is caused by Apple's misusage of JWT. JWT should be signed and issued by its server. Sign with its server time and verify with its time. There won't be 1 second difference. JWT should never signed by client itself, but it happened in Apple's API. Most client API just sign payload with timestamp, nonce and a secret, no JWT at all.
@ganlvtech
Thank you very much for your detailed response. Now I understand it and this time function could also fix the issue that the issuedAt/ExpiredAt
failed to update automatically.
The related PR has been updated. Honored to have this conversation with you and I have benefited a lot.
Thank you for quickly fixing this issue.
This library helps me really much. Thank you very much for your maintaining this repo. ❤️
When my server is 1 second faster than Apple's server, it returns 401.
https://developer.apple.com/documentation/appstoreserverapi/generating_json_web_tokens_for_api_requests
https://github.com/awa/go-iap/blob/14fdbb2d2b3873e9939e56b741f26e233034334d/appstore/api/token.go#L75-L93
We can make issuedAt be the time several seconds ago.