Apple three-party login, identityToken verification method, golang server processing

Apple login, verify identityToken method

Overview

After Apple authorizes the login, it returns the user ID, authorizationCode and identityToken, among which:

  • authorizationCode corresponds to a verification method that interacts with Apple servers. Domestic three-party logins, such as WeChat login, are all this verification method, but this article does not cover it.
  • identityToken uses the public key obtained from 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 documentation

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 content obtained is as follows:

{
    
    
  "keys": [
    {
    
    
      "kty": "RSA",
      "kid": "fh6Bs8C",
      "use": "sig",
      "alg": "RS256",
      "n": "u704gotMSZc6CSSVNCZ1d0S9dZKwO2BVzfdTKYz8wSNm7R_KIufOQf3ru7Pph1FjW6gQ8zgvhnv4IebkGWsZJlodduTC7c0sRb5PZpEyM6PtO8FPHowaracJJsK1f6_rSLstLdWbSDXeSq7vBvDu3Q31RaoV_0YlEzQwPsbCvD45oVy5Vo5oBePUm4cqi6T3cZ-10gr9QJCVwvx7KiQsttp0kUkHM94PlxbG_HAWlEZjvAlxfEDc-_xZQwC6fVjfazs3j1b2DZWsGmBRdx1snO75nM7hpyRRQB4jVejW9TuZDtPtsNadXTr9I5NjxPdIYMORj9XKEh44Z73yfv0gtw",
      "e": "AQAB"
    },
    {
    
    
      "kty": "RSA",
      "kid": "YuyXoY",
      "use": "sig",
      "alg": "RS256",
      "n": "1JiU4l3YCeT4o0gVmxGTEK1IXR-Ghdg5Bzka12tzmtdCxU00ChH66aV-4HRBjF1t95IsaeHeDFRgmF0lJbTDTqa6_VZo2hc0zTiUAsGLacN6slePvDcR1IMucQGtPP5tGhIbU-HKabsKOFdD4VQ5PCXifjpN9R-1qOR571BxCAl4u1kUUIePAAJcBcqGRFSI_I1j_jbN3gflK_8ZNmgnPrXA0kZXzj1I7ZHgekGbZoxmDrzYm2zmja1MsE5A_JX7itBYnlR41LOtvLRCNtw7K3EFlbfB6hkPL-Swk5XNGbWZdTROmaTNzJhV-lWT0gGm6V1qWAK2qOZoIDa_3Ud0Gw",
      "e": "AQAB"
    },
    {
    
    
      "kty": "RSA",
      "kid": "W6WcOKB",
      "use": "sig",
      "alg": "RS256",
      "n": "2Zc5d0-zkZ5AKmtYTvxHc3vRc41YfbklflxG9SWsg5qXUxvfgpktGAcxXLFAd9Uglzow9ezvmTGce5d3DhAYKwHAEPT9hbaMDj7DfmEwuNO8UahfnBkBXsCoUaL3QITF5_DAPsZroTqs7tkQQZ7qPkQXCSu2aosgOJmaoKQgwcOdjD0D49ne2B_dkxBcNCcJT9pTSWJ8NfGycjWAQsvC8CGstH8oKwhC5raDcc2IGXMOQC7Qr75d6J5Q24CePHj_JD7zjbwYy9KNH8wyr829eO_G4OEUW50FAN6HKtvjhJIguMl_1BLZ93z2KJyxExiNTZBUBQbbgCNBfzTv7JrxMw",
      "e": "AQAB"
    }
  ]
}

decryption token

Analyze token

An example of identityToken:

eyJraWQiOiJmaDZCczhDIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLnRqZ2QuZGV2ZWxvcC5tb2JpbGV6Lldpc2VUViIsImV4cCI6MTY5MTY0Mzc3MSwiaWF0IjoxNjkxNTU3MzcxLCJzdWIiOiIwMDA0NDAuM2E5M2Y3MDY1NTE0NGY5Mjg5ZGJlY2Q2ODhhMmQxNzguMDc0NSIsImNfaGFzaCI6IjkyOEVmVEliSkVVUDBVQlhNRF9wZHciLCJlbWFpbCI6InRqZ2RpcHR2QGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjoidHJ1ZSIsImF1dGhfdGltZSI6MTY5MTU1NzM3MSwibm9uY2Vfc3VwcG9ydGVkIjp0cnVlfQ.gt1DjUwX5oAzmYyoVoFtqjWv8D3SXkBm327RnadilOQxYbLLM82l45EWozjykRMhMtH4bWsP47m6DtBvwfA13kQg9HNTFCYOdm7j2SnF_3S5m_6sHvaxkK0VDbuk3Z-aWOcMlHMj4kFOdM9EMC5-1Qr_k9y6i7RRmh_xkAoapoFwSlLCb41J9qnornTbZnRT3CMgMngY90uxvlowYj38YpFW5Q1YDEQVtpDTz55yVIN7yPZ5bUlOHgj2wFWp_2IVo2qKCVgC4-qtoxsRDlR_Wghmv5lh_uwMGAkwBcsTS0eP0fGuYPNoZYuYzpiaLV9j3v01N191xgfUzKVa584Zeg

jwt token passes. The token is divided into three parts. The first part of the header records the encryption method, the second part of the 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"
}

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-aWOcMlHMj4kFOdM9EMC5-1Qr_k9y6i7RRmh_xkAoapoFwSlLCb41J9qnornTbZnRT3CMgMngY90uxvlowYj38YpFW5Q1YDEQVtpDTz55yVIN7yPZ5bUlOHgj2wFWp_2IVo2qKCVgC4-qtoxsRDlR_Wghmv5lh_uwMGAkwBcsTS0eP0fGuYPNoZYuYzpiaLV9j3v01N191xgfUzKVa584Zeg

Verification token

Select a key through the kid parsed from the header 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 apple-related content. That is to complete the verification of the overall process.

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, &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 && 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 cases

package main

import (
	"fmt"

	"xyccstudio/login"
)

func main() {
    
    
	err := login.VerifyIdentityToken("eyJraWQiOiJmaDZCczhDIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLnRqZ2QuZGV2ZWxvcC5tb2JpbGV6Lldpc2VUViIsImV4cCI6MTY5MTY0Mzc3MSwiaWF0IjoxNjkxNTU3MzcxLCJzdWIiOiIwMDA0NDAuM2E5M2Y3MDY1NTE0NGY5Mjg5ZGJlY2Q2ODhhMmQxNzguMDc0NSIsImNfaGFzaCI6IjkyOEVmVEliSkVVUDBVQlhNRF9wZHciLCJlbWFpbCI6InRqZ2RpcHR2QGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjoidHJ1ZSIsImF1dGhfdGltZSI6MTY5MTU1NzM3MSwibm9uY2Vfc3VwcG9ydGVkIjp0cnVlfQ.gt1DjUwX5oAzmYyoVoFtqjWv8D3SXkBm327RnadilOQxYbLLM82l45EWozjykRMhMtH4bWsP47m6DtBvwfA13kQg9HNTFCYOdm7j2SnF_3S5m_6sHvaxkK0VDbuk3Z-aWOcMlHMj4kFOdM9EMC5-1Qr_k9y6i7RRmh_xkAoapoFwSlLCb41J9qnornTbZnRT3CMgMngY90uxvlowYj38YpFW5Q1YDEQVtpDTz55yVIN7yPZ5bUlOHgj2wFWp_2IVo2qKCVgC4-qtoxsRDlR_Wghmv5lh_uwMGAkwBcsTS0eP0fGuYPNoZYuYzpiaLV9j3v01N191xgfUzKVa584Zeg", "000440.3a93f70655144f9289dbecd688a2d178.0745")
	fmt.Printf("check result %+v", err)
}

Guess you like

Origin blog.csdn.net/xo19882011/article/details/132188991