使用 Apple 登入適用於 iOS 13 上使用第三方驗證服務的所有新應用程式。 iOS 和 Android 之間的實作詳細數據大不相同。 本指南將逐步解說您今天如何在 中 Xamarin.Forms執行這項操作。
在本指南和範例中,會使用特定平台服務來處理使用Apple登入:
- 使用搭配 OpenID/OpenAuth 與 Azure Functions 通訊的一般 Web 服務 Android
- iOS 使用原生 API 在 iOS 13 上進行驗證,並回復為 iOS 12 和以下的一般 Web 服務
Apple 登入流程範例
此範例提供讓Apple登入在您的應用程式中運作的有意見實作 Xamarin.Forms 。
我們會使用兩個 Azure Functions 來協助驗證流程:
applesignin_auth- 產生 Apple 登入授權 URL 並重新導向至該 URL。 我們會在伺服器端執行此動作,而不是行動應用程式,因此我們可以在 Apple 的伺服器傳送回呼時快取state並驗證它。applesignin_callback- 處理 Apple 的 POST 回呼,並安全地交換存取令牌和標識碼令牌的授權碼。 最後,它會重新導向回應用程式的 URI 配置,並傳回 URL 片段中的令牌。
行動應用程式會自行註冊以處理我們選取的自定義 URI 配置(在此案例 xamarinformsapplesignin://中),讓函 applesignin_callback 式可以將令牌轉送回該架構。
當使用者開始驗證時,會執行下列步驟:
- 行動應用程式會產生
nonce和state值,並將其傳遞至applesignin_authAzure 函式。 - Azure 函
applesignin_auth式會產生 Apple 登入授權 URL(使用提供的state和nonce),並將行動應用程式瀏覽器重新導向至它。 - 使用者會在 Apple 伺服器上裝載的 Apple 登入授權頁面中安全地輸入其認證。
- Apple 登入流程在 Apple 的伺服器上完成之後,Apple 會重新導向至
redirect_uriAzure 函式。applesignin_callback - 從 Apple 傳送至
applesignin_callback函式的要求會經過驗證,以確保傳回正確state,且標識符令牌宣告有效。 - Azure 函
applesignin_callback式會code交換 Apple 所張貼的 ,以取得存取令牌、重新整理令牌和標識碼令牌(其中包含使用者識別碼、名稱和電子郵件的相關宣告)。 - Azure 函
applesignin_callback式最後會將重新導向回應用程式的 URI 配置(xamarinformsapplesignin://),並附加具有令牌的 URI 片段(例如xamarinformsapplesignin://#access_token=...&refresh_token=...&id_token=...)。 - 行動裝置應用程式會將 URI 片段剖析為 ,
AppleAccount並驗證nonce收到的宣告符合nonce流程開始時所產生的 宣告。 - 行動應用程式現在已驗證!
Azure Functions
此範例使用 Azure Functions。 或者,ASP.NET Core Controller 或類似的 Web 伺服器解決方案可以提供相同的功能。
組態
使用 Azure Functions 時,必須設定數個應用程式設定:
APPLE_SIGNIN_KEY_ID- 這是您KeyId稍早的。APPLE_SIGNIN_TEAM_ID- 這通常是您在成員資格配置檔中找到的小組標識碼APPLE_SIGNIN_SERVER_ID:這是ServerId稍早的 。 這不是您的應用程式套件組合識別碼,而是您所建立服務標識碼的標識碼。APPLE_SIGNIN_APP_CALLBACK_URI- 這是您想要重新導向回應用程式的自定義 URI 配置。 在這裡範例xamarinformsapplesignin://中,會使用 。APPLE_SIGNIN_REDIRECT_URI- 您在 Apple 登入組態區段中建立服務識別碼時設定的重新導向 URL。 若要測試,其看起來可能如下:http://local.test:7071/api/applesignin_callbackAPPLE_SIGNIN_P8_KEY- 檔案.p8的文字內容,移除所有\n換行符,使其是一個長字串
安全性考量
請勿 將 P8 金鑰儲存在應用程式程式碼內。 應用程式程式代碼很容易下載和反組譯。
使用 來裝載驗證流程,以及攔截 URL 流覽事件以取得授權碼也被視為不良做法 WebView 。 目前沒有完全安全的方法來處理非 iOS13+ 裝置上的 Apple 登入,而不需要在伺服器上裝載一些程式碼來處理令牌交換。 建議您在伺服器上裝載授權 URL 產生程式代碼,以便在 Apple 向伺服器發出 POST 回呼時快取狀態並加以驗證。
跨平臺登入服務
Xamarin.Forms使用 DependencyService,您可以建立個別的驗證服務,以使用 iOS 上的平台服務,以及以共用介面為基礎的 Android 和其他非 iOS 平臺的一般 Web 服務。
public interface IAppleSignInService
{
bool Callback(string url);
Task<AppleAccount> SignInAsync();
}
在 iOS 上,會使用原生 API:
public class AppleSignInServiceiOS : IAppleSignInService
{
#if __IOS__13
AuthManager authManager;
#endif
bool Is13 => UIDevice.CurrentDevice.CheckSystemVersion(13, 0);
WebAppleSignInService webSignInService;
public AppleSignInServiceiOS()
{
if (!Is13)
webSignInService = new WebAppleSignInService();
}
public async Task<AppleAccount> SignInAsync()
{
// Fallback to web for older iOS versions
if (!Is13)
return await webSignInService.SignInAsync();
AppleAccount appleAccount = default;
#if __IOS__13
var provider = new ASAuthorizationAppleIdProvider();
var req = provider.CreateRequest();
authManager = new AuthManager(UIApplication.SharedApplication.KeyWindow);
req.RequestedScopes = new[] { ASAuthorizationScope.FullName, ASAuthorizationScope.Email };
var controller = new ASAuthorizationController(new[] { req });
controller.Delegate = authManager;
controller.PresentationContextProvider = authManager;
controller.PerformRequests();
var creds = await authManager.Credentials;
if (creds == null)
return null;
appleAccount = new AppleAccount();
appleAccount.IdToken = JwtToken.Decode(new NSString(creds.IdentityToken, NSStringEncoding.UTF8).ToString());
appleAccount.Email = creds.Email;
appleAccount.UserId = creds.User;
appleAccount.Name = NSPersonNameComponentsFormatter.GetLocalizedString(creds.FullName, NSPersonNameComponentsFormatterStyle.Default, NSPersonNameComponentsFormatterOptions.Phonetic);
appleAccount.RealUserStatus = creds.RealUserStatus.ToString();
#endif
return appleAccount;
}
public bool Callback(string url) => true;
}
#if __IOS__13
class AuthManager : NSObject, IASAuthorizationControllerDelegate, IASAuthorizationControllerPresentationContextProviding
{
public Task<ASAuthorizationAppleIdCredential> Credentials
=> tcsCredential?.Task;
TaskCompletionSource<ASAuthorizationAppleIdCredential> tcsCredential;
UIWindow presentingAnchor;
public AuthManager(UIWindow presentingWindow)
{
tcsCredential = new TaskCompletionSource<ASAuthorizationAppleIdCredential>();
presentingAnchor = presentingWindow;
}
public UIWindow GetPresentationAnchor(ASAuthorizationController controller)
=> presentingAnchor;
[Export("authorizationController:didCompleteWithAuthorization:")]
public void DidComplete(ASAuthorizationController controller, ASAuthorization authorization)
{
var creds = authorization.GetCredential<ASAuthorizationAppleIdCredential>();
tcsCredential?.TrySetResult(creds);
}
[Export("authorizationController:didCompleteWithError:")]
public void DidComplete(ASAuthorizationController controller, NSError error)
=> tcsCredential?.TrySetException(new Exception(error.LocalizedDescription));
}
#endif
編譯旗標 __IOS__13 可用來支援 iOS 13,以及復原至一般 Web 服務的舊版。
在 Android 上,使用 Azure Functions 的一般 Web 服務:
public class WebAppleSignInService : IAppleSignInService
{
// IMPORTANT: This is what you register each native platform's url handler to be
public const string CallbackUriScheme = "xamarinformsapplesignin";
public const string InitialAuthUrl = "http://local.test:7071/api/applesignin_auth";
string currentState;
string currentNonce;
TaskCompletionSource<AppleAccount> tcsAccount = null;
public bool Callback(string url)
{
// Only handle the url with our callback uri scheme
if (!url.StartsWith(CallbackUriScheme + "://"))
return false;
// Ensure we have a task waiting
if (tcsAccount != null && !tcsAccount.Task.IsCompleted)
{
try
{
// Parse the account from the url the app opened with
var account = AppleAccount.FromUrl(url);
// IMPORTANT: Validate the nonce returned is the same as our originating request!!
if (!account.IdToken.Nonce.Equals(currentNonce))
tcsAccount.TrySetException(new InvalidOperationException("Invalid or non-matching nonce returned"));
// Set our account result
tcsAccount.TrySetResult(account);
}
catch (Exception ex)
{
tcsAccount.TrySetException(ex);
}
}
tcsAccount.TrySetResult(null);
return false;
}
public async Task<AppleAccount> SignInAsync()
{
tcsAccount = new TaskCompletionSource<AppleAccount>();
// Generate state and nonce which the server will use to initial the auth
// with Apple. The nonce should flow all the way back to us when our function
// redirects to our app
currentState = Util.GenerateState();
currentNonce = Util.GenerateNonce();
// Start the auth request on our function (which will redirect to apple)
// inside a browser (either SFSafariViewController, Chrome Custom Tabs, or native browser)
await Xamarin.Essentials.Browser.OpenAsync($"{InitialAuthUrl}?&state={currentState}&nonce={currentNonce}",
Xamarin.Essentials.BrowserLaunchMode.SystemPreferred);
return await tcsAccount.Task;
}
}
摘要
本文說明設定使用Apple登入以在應用程式中使用 Xamarin.Forms 所需的步驟。