Apple login, verify identityToken method
Overview
Apple returns the user ID, authorizationCode and identityToken after authorized login, where:
- authorizationCode corresponds to a verification method that interacts with the Apple server. Domestic three-party logins, such as WeChat logins, are all such verification methods, but this article does not cover them.
- identityToken uses the public key obtained by the Apple server to decrypt and verify the token. It seems that the key is rotated, and it is also very clever to verify that the token is valid. At the same time, it can ensure the correctness of the information by comparing the information.
Reference
Apple official documents
https://blog.csdn.net/we_are_the_world_123/article/details/114943076
https://xyccstudio.cn/blogs/xblog/ios/login.html
Verification process
Get Apple’s public key
The address is as follows: Click to view
https://appleid.apple.com/auth/keys
The obtained content is as follows:
{<!-- --> "keys": [ {<!-- --> "kty": "RSA", "kid": "fh6Bs8C", "use": "sig", "alg": "RS256", "n": "u704gotMSZc6CSSVNCZ1d0S9dZKwO2BVzfdTKYz8wSNm7R_KIufOQf3ru7Pph1FjW6gQ8zgvhnv4IebkGWsZJlodduTC7c0sRb5PZpEyM6PtO8FPHowaracJJsK1f6_r SLstLdWbSDXeSq7vBvDu3Q31RaoV_0YlEzQwPsbCvD45oVy5Vo5oBePUm4cqi6T3cZ-10gr9QJCVwvx7KiQsttp0kUkHM94PlxbG_HAWlEZjvAlxfEDc-_xZQwC6fVjfazs3j1b2DZ WsGmBRdx1snO75nM7hpyRRQB4jVejW9TuZDtPtsNadXTr9I5NjxPdIYMORj9XKEh44Z73yfv0gtw", "e": "AQAB" }, {<!-- --> "kty": "RSA", "kid": "YuyXoY", "use": "sig", "alg": "RS256", "n": "1JiU4l3YCeT4o0gVmxGTEK1IXR-Ghdg5Bzka12tzmtdCxU00ChH66aV-4HRBjF1t95IsaeHeDFRgmF0lJbTDTqa6_VZo2hc0zTiUAsGLacN6slePvDcR1IMucQGtPP5tGh IbU-HKabsKOFdD4VQ5PCXifjpN9R-1qOR571BxCAl4u1kUUIePAAJcBcqGRFSI_I1j_jbN3gflK_8ZNmgnPrXA0kZXzj1I7ZHgekGbZoxmDrzYm2zmja1MsE5A_JX7itBYnlR41LOtvL RCNtw7K3EFlbfB6hkPL-Swk5XNGbWZdTROmaTNzJhV-lWT0gGm6V1qWAK2qOZoIDa_3Ud0Gw", "e": "AQAB" }, {<!-- --> "kty": "RSA", "kid": "W6WcOKB", "use": "sig", "alg": "RS256", "n": "2Zc5d0-zkZ5AKmtYTvxHc3vRc41YfbklflxG9SWsg5qXUxvfgpktGAcxXLFAd9Uglzow9ezvmTGce5d3DhAYKwHAEPT9hbaMDj7DfmEwuNO8UahfnBkBXsCoUaL3QITF5 _DAPsZroTqs7tkQQZ7qPkQXCSu2aosgOJmaoKQgwcOdjD0D49ne2B_dkxBcNCcJT9pTSWJ8NfGycjWAQsvC8CGstH8oKwhC5raDcc2IGXMOQC7Qr75d6J5Q24CePHj_JD7zjbwYy9KN H8wyr829eO_G4OEUW50FAN6HKtvjhJIguMl_1BLZ93z2KJyxExiNTZBUBQbbgCNBfzTv7JrxMw", "e": "AQAB" } ] }
Decrypt token
Analysis token
An example of an identityToken:
eyJraWQiOiJmaDZCczhDIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLnRqZ2QuZGV2ZWxvcC5tb2JpbGV6Lldpc2VUVi IsImV4cCI6MTY5MTY0Mzc3MSwiaWF0IjoxNjkxNTU3MzcxLCJzdWIiOiIwMDA0NDAuM2E5M2Y3MDY1NTE0NGY5Mjg5ZGJlY2Q2ODhhMmQxNzguMDc0NSIsImNfaGFzaCI6IjkyOEVmVEliSkVVUDB VQlhNRF9wZHciLCJlbWFpbCI6InRqZ2RpcHR2QGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjoidHJ1ZSIsImF1dGhfdGltZSI6MTY5MTU1NzM3MSwibm9uY2Vfc3VwcG9ydGVkIjp0cnV lfQ.gt1DjUwX5oAzmYyoVoFtqjWv8D3SXkBm327RnadilOQxYbLLM82l45EWozjykRMhMtH4bWsP47m6DtBvwfA13kQg9HNTFCYOdm7j2SnF_3S5m_6sHvaxkK0VDbuk3Z-aWOcM lHMj4kFOdM9EMC5-1Qr_k9y6i7RRmh_xkAoapoFwSlLCb41J9qnornTbZnRT3CMgMngY90uxvlowYj38YpFW5Q1YDEQVtpDTz55yVIN7yPZ5bUlOHgj2wFWp_2IVo2qKCVgC4-qtoxsRDl R_Wghmv5lh_uwMGAkwBcsTS0eP0fGuYPNoZYuYzpiaLV9j3v01N191xgfUzKVa584Zeg
The jwt token is passed. The token is divided into three parts. The first part header records the encryption method, the second part body is the information recorded by the token, and the third part is the signature.
The three parts of this token are as follows:
header
{<!-- --> "kid": "fh6Bs8C", "alg": "RS256" }
the body
{<!-- --> "iss": "https://appleid.apple.com", "aud": "com.xxxxxx", "exp": 1691643771, "iat": 1691557371, "sub": "000440.3a93f70655144f9289dbecd688a2d178.0745", "c_hash": "928EfTIbJEUP0UBXMD_pdw", "email": "xxxxxx", "email_verified": "true", "auth_time": 1691557371, "nonce_supported": true }
sign
gt1DjUwX5oAzmYyoVoFtqjWv8D3SXkBm327RnadilOQxYbLLM82l45EWozjykRMhMtH4bWsP47m6DtBvwfA13kQg9HNTFCYOdm7j2SnF_3S5m_6sHvaxkK0VDbuk3Z-aWOc MlHMj4kFOdM9EMC5-1Qr_k9y6i7RRmh_xkAoapoFwSlLCb41J9qnornTbZnRT3CMgMngY90uxvlowYj38YpFW5Q1YDEQVtpDTz55yVIN7yPZ5bUlOHgj2wFWp_2IVo2qKCVgC4-qtoxsRD lR_Wghmv5lh_uwMGAkwBcsTS0eP0fGuYPNoZYuYzpiaLV9j3v01N191xgfUzKVa584Zeg
Verification token
Use the kid parsed from the header to select a key and use the key for token verification.
Check payload
After there is no problem with the token, check whether the content in the above body is consistent with the relevant content of apple. That is, the verification of the overall process is completed.
Overall process code
package login import ( "crypto/rsa" "encoding/base64" "encoding/json" "errors" "io" "math/big" "net/http" "strings" "github.com/dgrijalva/jwt-go" ) const ( PUBLIC_KEY_REQ_URL = "https://appleid.apple.com/auth/keys" APPLE_URL = "https://appleid.apple.com" APPLICATION_CLIENT_ID = "xxx" ) type JwtClaims struct {<!-- --> jwt.StandardClaims } type JwtHeader struct {<!-- --> Kid string `json:"kid"` Alg string `json:"alg"` } type JwtKeys struct {<!-- --> Kty string `json:"kty"` Kid string `json:"kid"` Use string `json:"use"` Alg string `json:"alg"` N string `json:"n"` E string `json:"e"` } func VerifyIdentityToken(cliToken string, cliUserID string) error {<!-- --> cliTokenArr := strings. Split(cliToken, ".") if len(cliTokenArr) < 3 {<!-- --> return errors. New("cliToken Split err") } cliHeader, err := jwt. DecodeSegment(cliTokenArr[0]) if err != nil {<!-- --> return err } var jHeader JwtHeader err = json. Unmarshal(cliHeader, &jHeader) if err != nil {<!-- --> return err } token, err := jwt.ParseWithClaims(cliToken, & amp;JwtClaims{<!-- -->}, func(token *jwt.Token) (interface{<!-- -->}, error) {<! -- --> pk := GetRSAPublicKey(jHeader.Kid) return pk, nil }) if err != nil {<!-- --> return err } if claims, ok := token.Claims.(*JwtClaims); ok & amp; & amp; token.Valid {<!-- --> if claims.Issuer != APPLE_URL || claims.Audience != APPLICATION_CLIENT_ID || claims.Subject != cliUserID {<!-- --> return errors.New("verify token info fail, info is not match") } } else {<!-- --> return errors. New("token claims parse fail") } return nil } func GetRSAPublicKey(kid string) *rsa.PublicKey {<!-- --> var body []byte resp, err := http. Get(PUBLIC_KEY_REQ_URL) if err != nil {<!-- --> return nil } else {<!-- --> defer resp.Body.Close() _body, err := io. ReadAll(resp. Body) if err != nil {<!-- --> return nil } body = _body } var jKeys map[string][]JwtKeys err = json. Unmarshal(body, &jKeys) if err != nil {<!-- --> return nil } var pubKey rsa.PublicKey for _, data := range jKeys {<!-- --> for _, val := range data {<!-- --> if val. Kid == kid {<!-- --> n_bin, _ := base64.RawURLEncoding.DecodeString(val.N) n_data := new(big.Int).SetBytes(n_bin) e_bin, _ := base64.RawURLEncoding.DecodeString(val.E) e_data := new(big.Int).SetBytes(e_bin) pubKey.N = n_data pubKey.E = int(e_data.Uint64()) break } } } if pubKey.E <= 0 {<!-- --> return nil } return &pubKey }
use case
package main import ( "fmt" "xyccstudio/login" ) func main() {<!-- --> err := login.VerifyIdentityToken("eyJraWQiOiJmaDZCczhDIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLnRqZ2QuZGV2ZWxvc C5tb2JpbGV6Lldpc2VUViIsImV4cCI6MTY5MTY0Mzc3MSwiaWF0IjoxNjkxNTU3MzcxLCJzdWIiOiIwMDA0NDAuM2E5M2Y3MDY1NTE0NGY5Mjg5ZGJlY2Q2ODhhMmQxNzguMDc0NSIsImNfa GFzaCI6IjkyOEVmVEliSkVVUDBVQlhNRF9wZHciLCJlbWFpbCI6InRqZ2RpcHR2QGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjoidHJ1ZSIsImF1dGhfdGltZSI6MTY5MTU1NzM3MSwibm9uY2V fc3VwcG9ydGVkIjp0cnVlfQ.gt1DjUwX5oAzmYyoVoFtqjWv8D3SXkBm327RnadilOQxYbLLM82l45EWozjykRMhMtH4bWsP47m6DtBvwfA13kQg9HNTFCYOdm7j2SnF_3S5m_ 6sHvaxkK0VDbuk3Z-aWOcMlHMj4kFOdM9EMC5-1Qr_k9y6i7RRmh_xkAoapoFwSlLCb41J9qnornTbZnRT3CMgMngY90uxvlowYj38YpFW5Q1YDEQVtpDTz55yVIN7yPZ5bUlOHgj2wFW p_2IVo2qKCVgC4-qtoxsRDlR_Wghmv5lh_uwMGAkwBcsTS0eP0fGuYPNoZYuYzpiaLV9j3v01N191xgfUzKVa584Zeg", "000440.3a93f70655144f9289dbecd688a2d178.07 45") fmt.Printf("check result % + v", err) }