golang 苹果一键登录 sing in with apple

前言

本文是对AppleID登录接入的相关总结,希望对其他人能有帮助。

sing in with apple

官方文档

“通过 Apple 登录”让用户能用自己的 Apple ID 轻松登录您的 app 和网站。用户不必填写表单、验证电子邮件地址和选择新密码,就可以使用“通过 Apple 登录”设置帐户并立即开始使用您的 app。所有帐户都通过双重认证受到保护,具有极高的安全性,Apple 亦不会跟踪用户在您的 app 或网站中的活动。

代码

package main

import (
    "crypto/ecdsa"
    "crypto/x509"
    "encoding/json"
    "encoding/pem"
    "errors"
    "github.com/dgrijalva/jwt-go"
    "io/ioutil"
    "net/http"
    "net/url"
    "strings"
    "time"
)

var (
    secret = `-----BEGIN PRIVATE KEY-----
    your PRIVATE KEY-
-----END PRIVATE KEY-----`
    keyId        = "keyId"
    teamId       = "teamId"
    clientID     = "clientID" //网页授权登录填写的是Services Id,App端登录需要的是AppId
    appID        = "appID"
    redirectUrl  = ""
    authTokenUrl = "https://appleid.apple.com/auth/token"
)

// create client_secret
func GetAppleSecret() string {
    token := &jwt.Token{
        Header: map[string]interface{}{
            "alg": "ES256",
            "kid": keyId,
        },
        Claims: jwt.MapClaims{
            "iss": teamId,
            "iat": time.Now().Unix(),
            // constraint: exp - iat <= 180 days
            "exp": time.Now().Add(24 * time.Hour).Unix(),
            "aud": "https://appleid.apple.com",
            "sub": appID,
        },
        Method: jwt.SigningMethodES256,
    }

    ecdsaKey, _ := AuthKeyFromBytes([]byte(secret))
    ss, _ := token.SignedString(ecdsaKey)
    return ss
}

func AuthKeyFromBytes(key []byte) (*ecdsa.PrivateKey, error) {
    var err error

    var block *pem.Block
    if block, _ = pem.Decode(key); block == nil {
        return nil, errors.New("token: AuthKey must be a valid .p8 PEM file")
    }

    var parsedKey interface{}
    if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil {
        return nil, err
    }

    var pkey *ecdsa.PrivateKey
    var ok bool
    if pkey, ok = parsedKey.(*ecdsa.PrivateKey); !ok {
        return nil, errors.New("token: AuthKey must be of type ecdsa.PrivateKey")
    }

    return pkey, nil
}

type AppleAuthTokenRes struct {
    Error        string `json:"error"`
    AccessToken  string `json:"access_token"`
    ExpiresIn    int    `json:"expires_in"`
    IDToken      string `json:"id_token"`
    RefreshToken string `json:"refresh_token"`
    TokenType    string `json:"token_type"`
}

func AppleAuthToken(code string) (*AppleAuthTokenRes, error) {
    form := url.Values{}
    form.Set("client_id", appID)
    form.Set("client_secret", GetAppleSecret())
    form.Set("code", code)
    form.Set("grant_type", "authorization_code")
    form.Set("redirect_uri", redirectUrl)

    var request *http.Request
    var err error
    if request, err = http.NewRequest("POST", authTokenUrl, strings.NewReader(form.Encode())); err != nil {
        return nil, err
    }
    request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

    var response *http.Response
    if response, err = http.DefaultClient.Do(request); nil != err {
        return nil, err
    }
    defer response.Body.Close()

    data, err := ioutil.ReadAll(response.Body)
    if err != nil {
        return nil, err
    }

    res := &AppleAuthTokenRes{}
    err = json.Unmarshal(data, res)
    if err != nil {
        return nil, err
    }

    if res.Error != "" {
        return res, errors.New(res.Error)
    }
    return res, nil
}

遇到的问题

报错invalid_grant

可能原因1:
前端传过来的code使用一次或者过几分钟就失效了,需要重新生成。
可能原因2:
网页授权登录填写的是Services Id,App端登录需要的是AppI。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容