最近在套第三方Web AppleId登入遇到了一點眉角,在這邊紀錄一下
Apple developer設定
在 Certificates, Identifiers & Profiles -> Identifiers -> Service IDs
按旁邊 + 號,選擇 Service ID 來創建您的 Service ID這裡有二個選項要填:
Description這裏填入您網站的名字(注意:這個值會顯示在前台網站給使用者看到)
Identifier你可以隨意取一個,作為辨識用勾選 Sign in with Apple 並按旁邊的 Configure
Primary App ID選擇你主要 App 的 App ID Register Website URLs
Domains and Subdomains 這裏填入您網站的網域
(注意:你的網站必須要有 https,不可用 localhost 或者 IP ,否則這裡不會過)
Return URLs 這裡填入回跳用的 Redirect URI
(一樣的規則,你的網站必須要有 https,不可用 localhost 或者 IP ,否則這裡不會過)
Sign Key 驗證金鑰
我們需要建立一個 Sign Key 在等一下跟蘋果 API 做驗證使用,這部分因為網站跟 App 驗證流程後半段是一樣的,不管支援哪一個部分都要做。
在 Certificates, Identifiers & Profiles -> Keys 按旁邊 + 號,建立一個 Key
Key name可以自行取名,勾選 Sign in with Apple 並按旁邊的 Configure
Primary App ID選擇主要 App 使用的 App ID
Grouped App IDs取名會把網站跟 App 群組綁在一起,按下 Continue 之後會讓你下載一個私鑰 p8 檔案,注意這只能被下載一次,請好好保存。如果不見的話就只能再重新產生一個。
以上設定完之後,開始實作登入,Apple有一份Web登入的文件
https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/incorporating_sign_in_with_apple_into_other_platforms
Apple登入的網址
https://appleid.apple.com/auth/authorize?client_id={0}&redirect_uri={1}&response_type=code&scope=openid%20email%20name&response_mode=form_post&state={2}
client_id
它可以是 App ID (也就是 Bundle ID) 也可以是 Service ID。
如果要 網站端做登入,它就會是 Service ID。

redirect_uri
OAuth 之後要轉跳的網址
Certificates, Identifiers & Profiles -> Identifiers -> Service IDs -> 您的 Service ID -> Configure -> Return URLs
state
一個您設定的,辨識用的字串
scope
我都填固定值 name email (注意:中間有空格要加入%20)
以上參數組好之後,可以直接連結會出現Apple登入畫面,登入完成後他會Post到你指定的redirect_uri,這邊注意一下他是Post不是Get喔,Facebook跟Line都是使用Get的!!
Apple CallBack處理
如文件所示他會callback的參數有code、id_token、state、user、error
scope
我都填固定值 name email (注意:中間有空格要加入%20)
code
有效期為五分鐘的一次性授權碼
user
在首次登入才會有值,裡面會有email、firstname、lastname所以要自己保存好,因為下次登入就不會有值了!
(若要測試可以把登入的App從AppleId移除,就可以再出現首次)
基本上是空值
error
返回的錯誤碼
Call Token API
使用code呼叫另一個Token API取得相關資料
Token API文件
https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens
如文件所示需帶入的參數為client_id、client_secret、code、grant_type、refresh_token、redirect_uri
client_id
如果要 網站端做登入,它就會是 Service ID
code
就是CallBack回來的code參數
grant_type
這裡我們填 authorization_code 來交換 AccessToken
refresh_token
這個不用給
redirect_uri
OAuth 之後要轉跳的網址
client_secret
一個 JWT 格式的要求文件,並利用前面申請的 Sign Key 來簽章
這邊最難的就是取client_secret,取這個需要幾個關鍵參數
TeamId
你的開發者帳號 Team ID,這可以在你的右上角看到
進去 Certificates, Identifiers & Profiles -> Identifiers -> App IDs -> 您的 App -> App ID Prefix 可以看見
KeyId
您建立驗證的 Sign Key 的 Key ID
在 Certificates, Identifiers & Profiles -> Keys -> 您的 Apple Sign Key -> View Key Details -> Key ID 可以看到
PriveKey
從Keys頁面下載,只能下載一次,請好好保存

下載之後會有PriveKey,以上相關參數放入下面的c#方法,使用GetClientSecret取得client_secret
private string GetClientSecret()
{
var signatureAlgorithm = GetEllipticCurveAlgorithm(_config.PriveKey);
ECDsaSecurityKey eCDsaSecurityKey = new ECDsaSecurityKey(signatureAlgorithm)
{
KeyId = _config.KeyId
};
var handler = new JwtSecurityTokenHandler();
var subject = new Claim("sub", _config.ClientId);//需要IOS提供
JwtSecurityToken token = handler.CreateJwtSecurityToken(
issuer: _config.TeamId,
audience: "https://appleid.apple.com",
expires: DateTime.UtcNow.AddMinutes(5),
issuedAt: DateTime.UtcNow,
notBefore: DateTime.UtcNow,
subject: new ClaimsIdentity(new[] { subject }),
signingCredentials: new SigningCredentials(eCDsaSecurityKey, SecurityAlgorithms.EcdsaSha256));
return token.RawData;
}
private ECDsa GetEllipticCurveAlgorithm(string privateKey)
{
var keyParams = (ECPrivateKeyParameters)PrivateKeyFactory.CreateKey(Convert.FromBase64String(privateKey));
var normalizedEcPoint = keyParams.Parameters.G.Multiply(keyParams.D).Normalize();
return ECDsa.Create(new ECParameters
{
Curve = ECCurve.CreateFromValue(keyParams.PublicKeyParamSet.Id),
D = keyParams.D.ToByteArrayUnsigned(),
Q =
{
X = normalizedEcPoint.XCoord.GetEncoded(),
Y = normalizedEcPoint.YCoord.GetEncoded()
}
});
}
相關參數組好之後在Post到Token API就會取得id_token
id_token是一個JWT,用base64UrlDecode解開之後Payload,sub就是openId
{
"iss": "https://appleid.apple.com",
"aud": "com.your.app.id",
"exp": 1596621649,
"iat": 1596621049,
"sub": "001451.3dc436155xxxxxxxxxxxxxxxxxxxx59f.0447",
"c_hash": "iUqI9Vyxxxxxxxxxg-CyoA",
"email": "8m2xxxxmew@privaterelay.appleid.com",
"email_verified": "true",
"is_private_email": "true",
"auth_time": 1596621049,
"nonce_supported": true
}
參考文件
https://blog.jks.coffee/sign-in-with-apple/
https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/incorporating_sign_in_with_apple_into_other_platforms
https://blog.csdn.net/Anlan2010/article/details/108419737