2022年1月22日 星期六

asp.net core web使用appleId登入

最近在套第三方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移除,就可以再出現首次)

id_token

基本上是空值

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