共用方式為


使用登入搭配Apple in Xamarin.Forms

使用 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 來協助驗證流程:

  1. applesignin_auth - 產生 Apple 登入授權 URL 並重新導向至該 URL。 我們會在伺服器端執行此動作,而不是行動應用程式,因此我們可以在 Apple 的伺服器傳送回呼時快取 state 並驗證它。
  2. applesignin_callback - 處理 Apple 的 POST 回呼,並安全地交換存取令牌和標識碼令牌的授權碼。 最後,它會重新導向回應用程式的 URI 配置,並傳回 URL 片段中的令牌。

行動應用程式會自行註冊以處理我們選取的自定義 URI 配置(在此案例 xamarinformsapplesignin://中),讓函 applesignin_callback 式可以將令牌轉送回該架構。

當使用者開始驗證時,會執行下列步驟:

  1. 行動應用程式會產生 noncestate 值,並將其傳遞至 applesignin_auth Azure 函式。
  2. Azure 函 applesignin_auth 式會產生 Apple 登入授權 URL(使用提供的 statenonce),並將行動應用程式瀏覽器重新導向至它。
  3. 使用者會在 Apple 伺服器上裝載的 Apple 登入授權頁面中安全地輸入其認證。
  4. Apple 登入流程在 Apple 的伺服器上完成之後,Apple 會重新導向至 redirect_uri Azure 函式。applesignin_callback
  5. 從 Apple 傳送至 applesignin_callback 函式的要求會經過驗證,以確保傳回正確 state ,且標識符令牌宣告有效。
  6. Azure 函applesignin_callback式會code交換 Apple 所張貼的 ,以取得存取令牌、重新整理令牌標識碼令牌(其中包含使用者識別碼、名稱和電子郵件的相關宣告)。
  7. Azure 函 applesignin_callback 式最後會將重新導向回應用程式的 URI 配置(xamarinformsapplesignin://),並附加具有令牌的 URI 片段(例如 xamarinformsapplesignin://#access_token=...&refresh_token=...&id_token=...)。
  8. 行動裝置應用程式會將 URI 片段剖析為 , AppleAccount 並驗證 nonce 收到的宣告符合 nonce 流程開始時所產生的 宣告。
  9. 行動應用程式現在已驗證!

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_callback
  • APPLE_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 所需的步驟。