Аутентификация с помощью Apple с использованием JavaScript

Вы можете разрешить пользователям выполнять аутентификацию в Firebase с помощью Apple ID, используя Firebase SDK для реализации сквозного процесса входа OAuth 2.0.

Подготовка

Чтобы пользователи могли входить в систему с помощью Apple, сначала настройте функцию "Войти с Apple" на сайте для разработчиков Apple, а затем включите Apple в качестве поставщика учетных данных для входа в проект Firebase.

Как присоединиться к программе Apple Developer Program

Настроить функцию "Войти с Apple" могут только участники программы Apple Developer Program.

Как настроить вход через аккаунт Apple

На сайте Apple Developer выполните следующие действия:

  1. Свяжите сайт с приложением, как описано в первом разделе статьи Настройка входа с Apple ID для сайта. Когда появится запрос, зарегистрируйте следующий URL в качестве URL возврата:

    https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler

    Идентификатор проекта Firebase можно найти в консоли Firebase на вкладке Настройки > Общие.

    Запишите идентификатор сервиса, который вы только что создали. Он понадобится вам в следующем разделе.

  2. Создайте закрытый ключ для функции "Войти с Apple". В следующем разделе вам понадобятся новый закрытый ключ и идентификатор ключа.
  3. Если вы используете какие-либо функции Firebase Authentication, которые отправляют электронные письма пользователям, включая вход по ссылке в электронном письме, подтверждение адреса электронной почты, отмену изменения аккаунта и другие, настройте службу ретрансляции частной электронной почты Apple и зарегистрируйте noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com (или ваш настроенный домен шаблона электронной почты), чтобы Apple могла ретранслировать электронные письма, отправленные Firebase Authentication, на анонимные адреса электронной почты Apple.

Как включить Apple в качестве поставщика идентификационной информации

  1. Добавьте Firebase в проект.
  2. В консоли Firebase выберите Безопасность > Аутентификация.
  3. На вкладке Sign-in method (Способ входа) включите поставщика входа Apple. Укажите идентификатор сервиса, созданный в предыдущем разделе. В разделе OAuth code flow configuration (Конфигурация потока кода OAuth) укажите идентификатор команды Apple, а также закрытый ключ и его идентификатор, созданные в предыдущем разделе.

Соблюдайте требования Apple к анонимизированным данным

При входе с Apple ID пользователи могут анонимизировать свои данные, в том числе адрес электронной почты. Пользователи, которые выберут этот вариант, получат адреса электронной почты с доменом privaterelay.appleid.com. При использовании функции "Войти с Apple" в приложении вы должны соблюдать все применимые правила для разработчиков и условия использования Apple, касающиеся анонимизированных идентификаторов Apple.

В частности, вы должны получить согласие пользователя, прежде чем связывать какую-либо информацию, позволяющую идентифицировать личность, с анонимным идентификатором Apple ID. При использовании Firebase Authentication могут выполняться следующие действия:

  • Связывать адрес электронной почты с анонимным Apple ID или наоборот.
  • Как связать номер телефона с анонимизированным Apple ID или наоборот
  • Связывать неанонимные учетные данные социальных сетей (Facebook, Google и т. д.) с анонимизированным Apple ID или наоборот.

Этот список не является исчерпывающим. Чтобы убедиться, что ваше приложение соответствует требованиям Apple, ознакомьтесь с лицензионным соглашением Apple Developer Program в разделе Membership (Участие) аккаунта разработчика.

Как управлять процессом входа с помощью Firebase SDK

Если вы создаете веб-приложение, проще всего реализовать аутентификацию пользователей с помощью аккаунтов Apple, используя Firebase JavaScript SDK.

Чтобы настроить процесс входа с помощью Firebase JavaScript SDK, выполните следующие действия:

  1. Создайте экземпляр OAuthProvider, используя соответствующий идентификатор поставщика apple.com.

    Web

    import { OAuthProvider } from "firebase/auth";
    
    const provider = new OAuthProvider('apple.com');

    Web

    var provider = new firebase.auth.OAuthProvider('apple.com');
  2. Необязательно. Укажите дополнительные области действия OAuth 2.0, которые вы хотите запросить у поставщика аутентификации.

    Web

    provider.addScope('email');
    provider.addScope('name');

    Web

    provider.addScope('email');
    provider.addScope('name');

    По умолчанию, когда включен параметр Один аккаунт на адрес электронной почты, Firebase запрашивает области действия для адреса электронной почты и имени. Если вы выберете вариант Несколько аккаунтов на один адрес электронной почты, Firebase не будет запрашивать у Apple области действия, если вы не укажете их.

  3. Необязательно. Если вы хотите, чтобы экран входа в аккаунт Apple отображался не на английском языке, задайте параметр locale. Поддерживаемые языки перечислены в документации по функции "Войти с Apple".

    Web

    provider.setCustomParameters({
      // Localize the Apple authentication screen in French.
      locale: 'fr'
    });

    Web

    provider.setCustomParameters({
      // Localize the Apple authentication screen in French.
      locale: 'fr'
    });
  4. Выполните аутентификацию в Firebase, используя объект поставщика OAuth. Вы можете предложить пользователям войти в систему, используя свои аккаунты Apple, открыв всплывающее окно или перенаправив их на страницу входа. На мобильных устройствах рекомендуется использовать метод переадресации.

    • Чтобы войти в систему с помощью всплывающего окна, вызовите функцию signInWithPopup():

      Web

      import { getAuth, signInWithPopup, OAuthProvider } from "firebase/auth";
      
      const auth = getAuth();
      signInWithPopup(auth, provider)
        .then((result) => {
          // The signed-in user info.
          const user = result.user;
      
          // Apple credential
          const credential = OAuthProvider.credentialFromResult(result);
          const accessToken = credential.accessToken;
          const idToken = credential.idToken;
      
          // IdP data available using getAdditionalUserInfo(result)
          // ...
        })
        .catch((error) => {
          // Handle Errors here.
          const errorCode = error.code;
          const errorMessage = error.message;
          // The email of the user's account used.
          const email = error.customData.email;
          // The credential that was used.
          const credential = OAuthProvider.credentialFromError(error);
      
          // ...
        });

      Web

      firebase
        .auth()
        .signInWithPopup(provider)
        .then((result) => {
          /** @type {firebase.auth.OAuthCredential} */
          var credential = result.credential;
      
          // The signed-in user info.
          var user = result.user;
      
          // You can also get the Apple OAuth Access and ID Tokens.
          var accessToken = credential.accessToken;
          var idToken = credential.idToken;
      
          // IdP data available using getAdditionalUserInfo(result)
        // ...
        })
        .catch((error) => {
          // Handle Errors here.
          var errorCode = error.code;
          var errorMessage = error.message;
          // The email of the user's account used.
          var email = error.email;
          // The firebase.auth.AuthCredential type that was used.
          var credential = error.credential;
      
          // ...
        });
    • Чтобы войти в аккаунт, перейдя на страницу входа, вызовите функцию signInWithRedirect():

    При использовании signInWithRedirect, linkWithRedirect или reauthenticateWithRedirect следуйте рекомендациям.

    Web

    import { getAuth, signInWithRedirect } from "firebase/auth";
    
    const auth = getAuth();
    signInWithRedirect(auth, provider);

    Web

    firebase.auth().signInWithRedirect(provider);

    После того как пользователь выполнит вход и вернется на страницу, вы можете получить результат входа, вызвав getRedirectResult():

    Web

    import { getAuth, getRedirectResult, OAuthProvider } from "firebase/auth";
    
    // Result from Redirect auth flow.
    const auth = getAuth();
    getRedirectResult(auth)
      .then((result) => {
        const credential = OAuthProvider.credentialFromResult(result);
        if (credential) {
          // You can also get the Apple OAuth Access and ID Tokens.
          const accessToken = credential.accessToken;
          const idToken = credential.idToken;
        }
        // The signed-in user info.
        const user = result.user;
      })
      .catch((error) => {
        // Handle Errors here.
        const errorCode = error.code;
        const errorMessage = error.message;
        // The email of the user's account used.
        const email = error.customData.email;
        // The credential that was used.
        const credential = OAuthProvider.credentialFromError(error);
    
        // ...
      });

    Web

    // Result from Redirect auth flow.
    firebase
      .auth()
      .getRedirectResult()
      .then((result) => {
        if (result.credential) {
          /** @type {firebase.auth.OAuthCredential} */
          var credential = result.credential;
    
          // You can get the Apple OAuth Access and ID Tokens.
          var accessToken = credential.accessToken;
          var idToken = credential.idToken;
    
          // IdP data available in result.additionalUserInfo.profile.
          // ...
        }
        // The signed-in user info.
        var user = result.user;
      })
      .catch((error) => {
        // Handle Errors here.
        var errorCode = error.code;
        var errorMessage = error.message;
        // The email of the user's account used.
        var email = error.email;
        // The firebase.auth.AuthCredential type that was used.
        var credential = error.credential;
    
        // ...
      });

    Здесь же можно отслеживать и обрабатывать ошибки. Список кодов ошибок приведен в справочной документации по API.

    В отличие от других поставщиков, поддерживаемых Firebase Auth, Apple не предоставляет URL фотографии.

    Если пользователь не хочет делиться своим адресом электронной почты с приложением, Apple предоставляет ему уникальный адрес (в формате xyz@privaterelay.appleid.com), который передается приложению. Если вы настроили сервис ретрансляции частных электронных адресов, Apple пересылает письма, отправленные на анонимный адрес, на настоящий адрес пользователя.

    Apple передает приложениям информацию о пользователе, например отображаемое имя, только при первом входе. Обычно Firebase сохраняет отображаемое имя при первом входе пользователя с помощью Apple ID. Получить его можно с помощью firebase.auth().currentUser.displayName. Однако если вы ранее использовали Apple для входа пользователя в приложение без Firebase, Apple не предоставит Firebase отображаемое имя пользователя.

Повторная аутентификация и связывание аккаунтов

Аналогичный шаблон можно использовать с reauthenticateWithPopup() и reauthenticateWithRedirect(), чтобы получать новые учетные данные для конфиденциальных операций, требующих недавнего входа в аккаунт:

Web

import { getAuth, reauthenticateWithPopup, OAuthProvider } from "firebase/auth";

// Result from Redirect auth flow.
const auth = getAuth();
const provider = new OAuthProvider('apple.com');

reauthenticateWithPopup(auth.currentUser, provider)
  .then((result) => {
    // User is re-authenticated with fresh tokens minted and can perform
    // sensitive operations like account deletion, or updating their email
    // address or password.

    // The signed-in user info.
    const user = result.user;

    // You can also get the Apple OAuth Access and ID Tokens.
    const credential = OAuthProvider.credentialFromResult(result);
    const accessToken = credential.accessToken;
    const idToken = credential.idToken;

    // ...
  })
  .catch((error) => {
    // Handle Errors here.
    const errorCode = error.code;
    const errorMessage = error.message;
    // The email of the user's account used.
    const email = error.customData.email;
    // The credential that was used.
    const credential = OAuthProvider.credentialFromError(error);

    // ...
  });

Web

const provider = new firebase.auth.OAuthProvider('apple.com');

firebase
  .auth()
  .currentUser
  .reauthenticateWithPopup(provider)
  .then((result) => {
    // User is re-authenticated with fresh tokens minted and can perform
    // sensitive operations like account deletion, or updating their email
    // address or password.
    /** @type {firebase.auth.OAuthCredential} */
    var credential = result.credential;

    // The signed-in user info.
    var user = result.user;
     // You can also get the Apple OAuth Access and ID Tokens.
    var accessToken = credential.accessToken;
    var idToken = credential.idToken;

    // IdP data available in result.additionalUserInfo.profile.
      // ...
  })
  .catch((error) => {
    // Handle Errors here.
    var errorCode = error.code;
    var errorMessage = error.message;
    // The email of the user's account used.
    var email = error.email;
    // The firebase.auth.AuthCredential type that was used.
    var credential = error.credential;

    // ...
  });

Кроме того, вы можете использовать linkWithPopup() и linkWithRedirect(), чтобы связать разных поставщиков идентификационной информации с существующими аккаунтами.

Обратите внимание, что Apple требует получать явное согласие пользователей, прежде чем связывать их аккаунты Apple с другими данными.

Например, чтобы связать аккаунт Facebook с текущим аккаунтом Firebase, используйте токен доступа, полученный при входе пользователя в Facebook:

Web

import { getAuth, linkWithPopup, FacebookAuthProvider } from "firebase/auth";

const auth = getAuth();
const provider = new FacebookAuthProvider();
provider.addScope('user_birthday');

// Assuming the current user is an Apple user linking a Facebook provider.
linkWithPopup(auth.currentUser, provider)
    .then((result) => {
      // Facebook credential is linked to the current Apple user.
      // ...

      // The user can now sign in to the same account
      // with either Apple or Facebook.
    })
    .catch((error) => {
      // Handle error.
    });

Web

const provider = new firebase.auth.FacebookAuthProvider();
provider.addScope('user_birthday');

// Assuming the current user is an Apple user linking a Facebook provider.
firebase.auth().currentUser.linkWithPopup(provider)
    .then((result) => {
      // Facebook credential is linked to the current Apple user.
      // Facebook additional data available in result.additionalUserInfo.profile,

      // Additional Facebook OAuth access token can also be retrieved.
      // result.credential.accessToken

      // The user can now sign in to the same account
      // with either Apple or Facebook.
    })
    .catch((error) => {
      // Handle error.
    });

Как пройти аутентификацию с помощью Firebase в расширении Chrome

Если вы создаете расширение Chrome, ознакомьтесь с руководством по документам вне экрана.

Обратите внимание, что вам все равно нужно будет подтвердить пользовательский домен в Apple, как и домен firebaseapp.com по умолчанию:

http://auth.custom.example.com/.well-known/apple-developer-domain-association.txt

Отзыв токена

Согласно требованиям App Store, если в приложении можно создать аккаунт, то в нем должна быть и возможность запросить удаление аккаунта.

Чтобы выполнить это требование, сделайте следующее:

  1. Убедитесь, что вы заполнили разделы Services ID (Идентификатор сервисов) и OAuth code flow configuration (Конфигурация потока кода OAuth) в настройках поставщика Sign in with Apple, как описано в разделе Настройка Sign in with Apple.

  2. Поскольку Firebase не хранит токены пользователей, созданных с помощью функции "Войти с Apple", вам необходимо попросить пользователя войти в систему ещё раз, прежде чем отзывать его токен и удалять аккаунт.

    Затем получите токен доступа OAuth от Apple из OAuthCredential и используйте его, чтобы вызвать revokeAccessToken(auth, token) и отозвать токен доступа OAuth от Apple.

    const provider = new OAuthProvider('apple.com');
    provider.addScope('email');
    provider.addScope('name');
    
    const auth = getAuth();
    signInWithPopup(auth, provider).then(result => {
      // Get the Apple OAuth access token.
      const credential = OAuthProvider.credentialFromResult(result);
      const accessToken = credential.accessToken;
    
      // Revoke the Apple OAuth access token.
      revokeAccessToken(auth, accessToken)
        .then(() => {
          // Token revoked.
    
          // Delete the user account.
          // ...
        })
        .catch(error => {
          // An error happened.
          // ...
        });
    });
    
  3. Наконец, удалите аккаунт пользователя (и все связанные с ним данные).

Для опытных пользователей: аутентификация с помощью Firebase в Node.js

Чтобы выполнить аутентификацию с помощью Firebase в приложении Node.js, сделайте следующее:

  1. Выполните вход пользователя с помощью его аккаунта Apple и получите токен Apple ID пользователя. Это можно сделать несколькими способами. Например, если у вашего приложения Node.js есть интерфейс браузера:

    1. На сервере создайте случайную строку (одноразовый код) и вычислите ее хеш SHA256. Одноразовый код – это значение, которое используется один раз для проверки одного цикла обмена данными между вашим сервером и серверами аутентификации Apple.

      Web

      const crypto = require("crypto");
      const string_decoder = require("string_decoder");
      
      // Generate a new random string for each sign-in
      const generateNonce = (length) => {
        const decoder = new string_decoder.StringDecoder("ascii");
        const buf = Buffer.alloc(length);
        let nonce = "";
        while (nonce.length < length) {
          crypto.randomFillSync(buf);
          nonce = decoder.write(buf);
        }
        return nonce.slice(0, length);
      };
      
      const unhashedNonce = generateNonce(10);
      
      // SHA256-hashed nonce in hex
      const hashedNonceHex = crypto.createHash('sha256')
        .update(unhashedNonce).digest().toString('hex');

      Web

      const crypto = require("crypto");
      const string_decoder = require("string_decoder");
      
      // Generate a new random string for each sign-in
      const generateNonce = function(length) {
        const decoder = new string_decoder.StringDecoder("ascii");
        const buf = Buffer.alloc(length);
        var nonce = "";
        while (nonce.length < length) {
          crypto.randomFillSync(buf);
          nonce = decoder.write(buf);
        }
        return nonce.slice(0, length);
      };
      
      const unhashedNonce = generateNonce(10);
      
      // SHA256-hashed nonce in hex
      const hashedNonceHex = crypto.createHash('sha256')
        .update(unhashedNonce).digest().toString('hex');
    2. На странице входа укажите хешированный одноразовый код в конфигурации функции "Войти с Apple":

      <script src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script>
      <div id="appleid-signin" data-color="black" data-border="true" data-type="sign in"></div>
      <script>
          AppleID.auth.init({
              clientId: YOUR_APPLE_CLIENT_ID,
              scope: 'name email',
              redirectURI: URL_TO_YOUR_REDIRECT_HANDLER,  // See the next step.
              state: '[STATE]',  // Optional value that Apple will send back to you
                                 // so you can return users to the same context after
                                 // they sign in.
              nonce: HASHED_NONCE  // The hashed nonce you generated in the previous step.
          });
      </script>
      
    3. Получите токен Apple ID из ответа на POST-запрос на стороне сервера:

      app.post('/redirect', (req, res) => {
        const savedState = req.cookies.__session;
        const code = req.body.code;
        const state = req.body.state;
        const appleIdToken = req.body.id_token;
        if (savedState !== state || !code) {
          res.status(403).send('403: Permission denied');
        } else {
          // Sign in with Firebase using appleIdToken. (See next step).
        }
      });
      

    Также ознакомьтесь со статьей Configuring Your Webpage for Sign In with Apple (Как настроить веб-страницу для входа с Apple).

  2. Получив токен Apple ID пользователя, используйте его для создания объекта Credential, а затем выполните вход пользователя с помощью этих учетных данных:

    Web

    import { getAuth, signInWithCredential, OAuthProvider } from "firebase/auth";
    
    const auth = getAuth();
    
    // Build Firebase credential with the Apple ID token.
    const provider = new OAuthProvider('apple.com');
    const authCredential = provider.credential({
      idToken: appleIdToken,
      rawNonce: unhashedNonce,
    });
    
    // Sign in with credential form the Apple user.
    signInWithCredential(auth, authCredential)
      .then((result) => {
        // User signed in.
      })
      .catch((error) => {
        // An error occurred. If error.code == 'auth/missing-or-invalid-nonce',
        // make sure you're sending the SHA256-hashed nonce as a hex string
        // with your request to Apple.
        console.log(error);
      });

    Web

    // Build Firebase credential with the Apple ID token.
    const provider = new firebase.auth.OAuthProvider('apple.com');
    const authCredential = provider.credential({
      idToken: appleIdToken,
      rawNonce: unhashedNonce,
    });
    
    // Sign in with credential form the Apple user.
    firebase.auth().signInWithCredential(authCredential)
      .then((result) => {
        // User signed in.
      })
      .catch((error) => {
        // An error occurred. If error.code == 'auth/missing-or-invalid-nonce',
        // make sure you're sending the SHA256-hashed nonce as a hex string
        // with your request to Apple.
        console.log(error);
      });

Дальнейшие действия

После первого входа пользователя создается новый аккаунт, который связывается с учетными данными, использованными для входа (именем пользователя и паролем, номером телефона или информацией от поставщика аутентификации). Этот аккаунт сохраняется в проекте Firebase и может использоваться для идентификации пользователя во всех приложениях проекта, независимо от того, как пользователь входит в систему.

  • В приложениях рекомендуется отслеживать статус аутентификации пользователя, установив наблюдатель для объекта Auth. После этого вы сможете получить основную информацию профиля пользователя из объекта User. Подробнее о том, как управлять пользователями…

  • В Firebase Realtime Database и Cloud Storage правилах безопасности можно получить уникальный идентификатор пользователя, выполнившего вход, из переменной auth и использовать его, чтобы контролировать, к каким данным у пользователя есть доступ.

Вы можете разрешить пользователям входить в приложение с помощью нескольких поставщиков аутентификации, связав учетные данные поставщика аутентификации с существующим аккаунтом пользователя.

Чтобы выйти из аккаунта пользователя, вызовите функцию signOut:

Web

import { getAuth, signOut } from "firebase/auth";

const auth = getAuth();
signOut(auth).then(() => {
  // Sign-out successful.
}).catch((error) => {
  // An error happened.
});

Web

firebase.auth().signOut().then(() => {
  // Sign-out successful.
}).catch((error) => {
  // An error happened.
});