Поделиться через


Использование входа в Apple в Xamarin.Forms

Вход с помощью Apple предназначен для всех новых приложений в iOS 13, использующих сторонние службы проверки подлинности. Сведения о реализации между iOS и Android отличаются. В этом руководстве описывается, как это можно сделать сегодня Xamarin.Forms.

В этом руководстве и примере определенные службы платформы используются для обработки входа с помощью Apple:

  • Android с помощью универсальной веб-службы, разговаривая с Функции Azure с OpenID/OpenAuth
  • iOS использует собственный API для проверки подлинности в iOS 13 и возвращается к универсальной веб-службе для iOS 12 и ниже

Пример потока входа Apple

В этом примере представлена реализация единого входа Apple для работы в приложении Xamarin.Forms .

Мы используем два Функции Azure, чтобы помочь в потоке проверки подлинности:

  1. applesignin_auth — создает URL-адрес авторизации для входа Apple и перенаправляет его. Мы делаем это на стороне сервера вместо мобильного приложения, чтобы мы могли кэшировать state и проверить его, когда серверы Apple отправляют обратный вызов.
  2. applesignin_callback — обрабатывает обратный вызов POST из Apple и безопасно обменивается кодом авторизации для маркера доступа и маркера идентификатора. Наконец, он перенаправляет обратно в схему URI приложения, передавая маркеры в фрагменте URL-адреса.

Мобильное приложение регистрирует себя для обработки пользовательской схемы URI, выбранной (в данном случае xamarinformsapplesignin://), чтобы applesignin_callback функция могли ретрансляцию маркеров обратно в него.

Когда пользователь запускает проверку подлинности, выполняются следующие действия.

  1. Мобильное приложение создает и state значение nonce и передает их в функцию applesignin_auth Azure.
  2. Функция applesignin_auth Azure создает URL-адрес авторизации для входа Apple (используя предоставленный state и) и nonceперенаправляет браузер мобильного приложения на него.
  3. Пользователь безопасно вводит свои учетные данные на странице авторизации входа Apple, размещенной на серверах Apple.
  4. После завершения потока входа Apple на серверах Apple Apple перенаправляется на redirect_uri те, которые будут функцией applesignin_callback Azure.
  5. Запрос от Apple, отправляемый applesignin_callback в функцию, проверяется, чтобы убедиться, что возвращается правильность state , и что утверждения маркера идентификатора действительны.
  6. Функция applesignin_callback Azure обменивается code данными, размещенными в Apple, для маркера доступа, маркера обновления и маркера идентификатора (который содержит утверждения об идентификаторе пользователя, имени и электронной почте).
  7. Функция applesignin_callback Azure, наконец, перенаправляется обратно в схему URI приложения (xamarinformsapplesignin://) добавление фрагмента URI с маркерами (например. xamarinformsapplesignin://#access_token=...&refresh_token=...&id_token=...
  8. Мобильное приложение анализирует фрагмент URI в фрагмент AppleAccount и проверяет nonce полученное утверждение соответствует nonce созданному в начале потока.
  9. Мобильное приложение теперь проходит проверку подлинности!

Функции Azure

В этом примере используется Функции Azure. Кроме того, ASP.NET core Controller или аналогичное решение веб-сервера может обеспечить одинаковые функциональные возможности.

Настройка

При использовании Функции Azure необходимо настроить несколько параметров приложения:

  • 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— URL-адрес перенаправления, который вы настраиваете при создании идентификатора служб в разделе "Конфигурация входа Apple". Чтобы проверить, это может выглядеть примерно так: http://local.test:7071/api/applesignin_callback
  • APPLE_SIGNIN_P8_KEY — Текстовое содержимое .p8 файла со всеми \n новыми строками удалено, поэтому это одна длинная строка.

Вопросы безопасности

Никогда не храните ключ P8 в коде приложения. Код приложения легко скачать и дизассемблировать.

Также считается плохой практикой размещения WebView потока проверки подлинности и перехвата событий навигации ПО URL-адреса для получения кода авторизации. В настоящее время в настоящее время нет полностью безопасного способа обработки входа с помощью Apple на устройствах, отличных от iOS13+ без размещения кода на сервере для обработки обмена токенами. Мы рекомендуем разместить код создания URL-адреса авторизации на сервере, чтобы кэшировать состояние и проверить его, когда Apple выдает обратный вызов POST на сервер.

Кроссплатформенная служба входа

Xamarin.Forms С помощью DependencyService можно создать отдельные службы проверки подлинности, использующие службы платформы в iOS, а также универсальную веб-службу для Android и других платформ, отличных от iOS, на основе общего интерфейса.

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, а также устаревших версий, которые падают в универсальную веб-службу.

В Android используется универсальная веб-служба с Функции Azure:

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 .