Триггеры блокировки аутентификации


Функции блокировки позволяют выполнять собственный код, который изменяет результат регистрации или входа пользователя в ваше приложение. Например, вы можете запретить пользователю проходить аутентификацию, если он не соответствует определенным критериям, или обновить информацию пользователя перед возвратом ее в клиентское приложение.

Прежде чем начать

Чтобы использовать функции блокировки, вам необходимо обновить проект Firebase до Firebase Authentication with Identity Platform . Если вы еще не обновились, сделайте это в первую очередь.

Понимание функций блокировки

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

  • beforeCreate : срабатывает перед сохранением нового пользователя в базе данных Firebase Authentication и перед возвратом токена в ваше клиентское приложение.

  • beforeSignIn : срабатывает после проверки учетных данных пользователя, но до того, как Firebase Authentication вернет токен идентификатора вашему клиентскому приложению. Если ваше приложение использует многофакторную аутентификацию, функция срабатывает после того, как пользователь подтвердит свой второй фактор. Обратите внимание, что при создании нового пользователя в дополнение к beforeCreate также активируется beforeSignIn .

  • beforeEmail (только Node.js) : срабатывает перед электронным письмом (например,
    электронное письмо для входа или сброса пароля) отправляется пользователю.

  • beforeSms (только Node.js) : срабатывает перед отправкой SMS-сообщения пользователю, в таких случаях, как многофакторная аутентификация.

При использовании функций блокировки имейте в виду следующее:

  • Ваша функция должна ответить в течение 7 секунд. Через 7 секунд Firebase Authentication возвращает ошибку, и операция клиента завершается с ошибкой.

  • Коды ответов HTTP, отличные от 200 передаются вашим клиентским приложениям. Убедитесь, что ваш клиентский код обрабатывает любые ошибки, которые может вернуть ваша функция.

  • Функции применяются ко всем пользователям вашего проекта, включая всех, содержащихся в клиенте . Firebase Authentication предоставляет информацию о пользователях вашей функции, включая всех арендаторов, которым они принадлежат, чтобы вы могли отреагировать соответствующим образом.

  • Привязка другого поставщика удостоверений к учетной записи повторно запускает все зарегистрированные функции beforeSignIn .

  • Анонимная и пользовательская аутентификация не активируют функции блокировки.

Разверните функцию блокировки

Чтобы вставить свой собственный код в потоки аутентификации пользователей, разверните функции блокировки. После развертывания функций блокировки ваш пользовательский код должен успешно завершиться для успешной аутентификации и создания пользователя.

Вы развертываете функцию блокировки так же, как и любую другую функцию. (подробности см. на странице « Начало работы Cloud Functions ). В итоге:

  1. Напишите функцию, которая обрабатывает целевое событие.

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

    const functions = require('firebase-functions/v1');
    
    exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
      // TODO
    });
    
    The above example has omitted the implementation of custom auth logic. See
    the following sections to learn how to implement your blocking functions and
    [Common scenarios](#common-scenarios) for specific examples.
    
  1. Разверните свои функции с помощью Firebase CLI:

    firebase deploy --only functions
    

    Вы должны повторно развертывать свои функции каждый раз, когда вы их обновляете.

Получение информации о пользователе и контексте

События beforeSignIn и beforeCreate предоставляют объекты User и EventContext , содержащие информацию о входе пользователя в систему. Используйте эти значения в своем коде, чтобы определить, следует ли разрешить выполнение операции.

Список свойств, доступных для объекта User , см. в справочнике по API UserRecord .

Объект EventContext содержит следующие свойства:

Имя Описание Пример
locale Язык приложения. Вы можете установить языковой стандарт с помощью клиентского SDK или передав заголовок языкового стандарта в REST API. fr или sv-SE
ipAddress IP-адрес устройства, с которого конечный пользователь регистрируется или входит в систему. 114.14.200.1
userAgent Пользовательский агент, запускающий функцию блокировки. Mozilla/5.0 (X11; Linux x86_64)
eventId Уникальный идентификатор события. rWsyPtolplG2TBFoOkkgyg
eventType Тип события. Это предоставляет информацию об имени события, например beforeSignIn или beforeCreate , и связанном с ним методе входа, например Google или адрес электронной почты/пароль. providers/cloud.auth/eventTypes/user.beforeSignIn:password
authType Всегда USER . USER
resource Проект или клиент Firebase Authentication . projects/ project-id /tenants/ tenant-id
timestamp Время инициирования события в формате строки RFC 3339 . Tue, 23 Jul 2019 21:10:57 GMT
additionalUserInfo Объект, содержащий информацию о пользователе. AdditionalUserInfo
credential Объект, содержащий информацию об учетных данных пользователя. AuthCredential

Блокировка регистрации или входа в систему

Чтобы заблокировать попытку регистрации или входа, добавьте HttpsError в свою функцию. Например:

Node.js

throw new functions.auth.HttpsError('permission-denied');

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

Имя Код Сообщение
invalid-argument 400 Клиент указал недопустимый аргумент.
failed-precondition 400 Запрос не может быть выполнен в текущем состоянии системы.
out-of-range 400 Клиент указал неверный диапазон.
unauthenticated 401 Токен OAuth отсутствует, недействителен или срок действия которого истек.
permission-denied 403 Клиент не имеет достаточного разрешения.
not-found 404 Указанный ресурс не найден.
aborted 409 Конфликт параллелизма, например конфликт чтения-изменения-записи.
already-exists 409 Ресурс, который пытался создать клиент, уже существует.
resource-exhausted 429 Либо исчерпана квота ресурсов, либо достигнуто ограничение скорости.
cancelled 499 Запрос отменен клиентом.
data-loss 500 Невосстановимая потеря или повреждение данных.
unknown 500 Неизвестная ошибка сервера.
internal 500 Внутренняя ошибка сервера.
not-implemented 501 Метод API не реализован сервером.
unavailable 503 Сервис недоступен.
deadline-exceeded 504 Срок запроса истек.

Вы также можете указать собственное сообщение об ошибке:

Node.js

throw new functions.auth.HttpsError('permission-denied', 'Unauthorized request origin!');

В следующем примере показано, как заблокировать регистрацию в вашем приложении пользователей, которые не входят в определенный домен:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  // (If the user is authenticating within a tenant context, the tenant ID can be determined from
  // user.tenantId or from context.resource, e.g. 'projects/project-id/tenant/tenant-id-1')

  // Only users of a specific domain can sign up.
  if (user.email.indexOf('@acme.com') === -1) {
    throw new functions.auth.HttpsError('invalid-argument', `Unauthorized email "${user.email}"`);
  }
});

Независимо от того, используете ли вы сообщение по умолчанию или собственное, Cloud Functions упаковывает ошибку и возвращает ее клиенту как внутреннюю ошибку. Например:

throw new functions.auth.HttpsError('invalid-argument', `Unauthorized email user@evil.com}`);

Ваше приложение должно обнаружить ошибку и обработать ее соответствующим образом. Например:

JavaScript

// Blocking functions can also be triggered in a multi-tenant context before user creation.
// firebase.auth().tenantId = 'tenant-id-1';
firebase.auth().createUserWithEmailAndPassword('johndoe@example.com', 'password')
  .then((result) => {
    result.user.getIdTokenResult()
  })
  .then((idTokenResult) => {
    console.log(idTokenResult.claim.admin);
  })
  .catch((error) => {
    if (error.code !== 'auth/internal-error' && error.message.indexOf('Cloud Function') !== -1) {
      // Display error.
    } else {
      // Registration succeeds.
    }
  });

Изменение пользователя

Вместо блокировки попытки регистрации или входа в систему вы можете разрешить продолжить операцию, но изменить объект User , который сохраняется в базе данных Firebase Authentication и возвращается клиенту.

Чтобы изменить пользователя, верните объект из обработчика событий, содержащий поля, которые нужно изменить. Вы можете изменить следующие поля:

  • displayName
  • disabled
  • emailVerified
  • photoUrl
  • customClaims
  • sessionClaims (только beforeSignIn )

За исключением sessionClaims , все измененные поля сохраняются в базе данных Firebase Authentication , что означает, что они включаются в токен ответа и сохраняются между сеансами пользователя.

В следующем примере показано, как установить отображаемое имя по умолчанию:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  return {
    // If no display name is provided, set it to "Guest".
    displayName: user.displayName || 'Guest';
  };
});

Если вы регистрируете обработчик событий как для beforeCreate , так и beforeSignIn , обратите внимание, что beforeSignIn выполняется после beforeCreate . Пользовательские поля, обновленные в beforeCreate , видны в beforeSignIn . Если вы установите поле, отличное от sessionClaims , в обоих обработчиках событий, значение, установленное в beforeSignIn перезапишет значение, установленное в beforeCreate . Только для sessionClaims они распространяются на утверждения маркеров текущего сеанса, но не сохраняются и не сохраняются в базе данных.

Например, если установлены какие-либо sessionClaims , beforeSignIn вернет их с любыми утверждениями beforeCreate , и они будут объединены. Если при их объединении ключ sessionClaims соответствует ключу в customClaims , соответствующие customClaims будут перезаписаны в утверждениях маркеров ключом sessionClaims . Однако перезаписанный ключ customClaims по-прежнему будет сохраняться в базе данных для будущих запросов.

Поддерживаемые учетные данные и данные OAuth

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

Поставщик удостоверений Идентификационный токен Токен доступа Срок годности Секрет токена Обновить токен Заявки на вход в систему
Google Да Да Да Нет Да Нет
Фейсбук Нет Да Да Нет Нет Нет
Твиттер Нет Да Нет Да Нет Нет
GitHub Нет Да Нет Нет Нет Нет
Майкрософт Да Да Да Нет Да Нет
LinkedIn Нет Да Да Нет Нет Нет
Yahoo Да Да Да Нет Да Нет
Яблоко Да Да Да Нет Да Нет
SAML Нет Нет Нет Нет Нет Да
ОИДК Да Да Да Нет Да Да

Обновить токены

Чтобы использовать токен обновления в функции блокировки, сначала необходимо установить флажок на странице «Функции блокировки» консоли Firebase .

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

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

Общие поставщики OIDC

Когда пользователь входит в систему с помощью общего поставщика OIDC, ему передаются следующие учетные данные:

  • Токен идентификатора : предоставляется, если выбран поток id_token .
  • Токен доступа : предоставляется, если выбран поток кода. Обратите внимание, что поток кода в настоящее время поддерживается только через REST API.
  • Токен обновления : предоставляется, если выбрана область offline_access .

Пример:

const provider = new firebase.auth.OAuthProvider('oidc.my-provider');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);

Google

Когда пользователь входит в систему с помощью Google, ему передаются следующие учетные данные:

  • идентификационный токен
  • Токен доступа
  • Токен обновления : предоставляется только в том случае, если запрошены следующие пользовательские параметры:
    • access_type=offline
    • prompt=consent , если пользователь ранее дал согласие и новая область действия не запрашивалась.

Пример:

const provider = new firebase.auth.GoogleAuthProvider();
provider.setCustomParameters({
  'access_type': 'offline',
  'prompt': 'consent'
});
firebase.auth().signInWithPopup(provider);

Узнайте больше о токенах обновления Google .

Фейсбук

Когда пользователь входит в систему через Facebook, ему передаются следующие учетные данные:

  • Токен доступа : возвращается токен доступа, который можно обменять на другой токен доступа. Узнайте больше о различных типах токенов доступа , поддерживаемых Facebook, и о том, как их можно обменять на долгосрочные токены .

GitHub

Когда пользователь входит в систему с помощью GitHub, ему передаются следующие учетные данные:

  • Токен доступа : срок действия не истекает, если его не отозвать.

Майкрософт

Когда пользователь входит в систему с помощью Microsoft, ему передаются следующие учетные данные:

  • идентификационный токен
  • Токен доступа
  • Токен обновления : передается функции блокировки, если выбрана область offline_access .

Пример:

const provider = new firebase.auth.OAuthProvider('microsoft.com');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);

Yahoo

Когда пользователь входит в систему с помощью Yahoo, следующие учетные данные будут переданы без каких-либо пользовательских параметров или областей:

  • идентификационный токен
  • Токен доступа
  • Обновить токен

LinkedIn

Когда пользователь входит в систему через LinkedIn, ему передаются следующие учетные данные:

  • Токен доступа

Яблоко

Когда пользователь входит в систему с помощью Apple, следующие учетные данные будут переданы без каких-либо настраиваемых параметров или областей:

  • идентификационный токен
  • Токен доступа
  • Обновить токен

Распространенные сценарии

Следующие примеры демонстрируют некоторые распространенные случаи использования функций блокировки:

Разрешение регистрации только с определенного домена

В следующем примере показано, как запретить пользователям, не являющимся частью домена example.com , регистрироваться в вашем приложении:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (!user.email || user.email.indexOf('@example.com') === -1) {
    throw new functions.auth.HttpsError(
      'invalid-argument', `Unauthorized email "${user.email}"`);
  }
});

Блокировка регистрации пользователей с непроверенным адресом электронной почты

В следующем примере показано, как запретить пользователям с непроверенными адресами электронной почты регистрироваться в вашем приложении:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (user.email && !user.emailVerified) {
    throw new functions.auth.HttpsError(
      'invalid-argument', `Unverified email "${user.email}"`);
  }
});

Требование подтверждения электронной почты при регистрации

В следующем примере показано, как потребовать от пользователя подтвердить адрес электронной почты после регистрации:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  const locale = context.locale;
  if (user.email && !user.emailVerified) {
    // Send custom email verification on sign-up.
    return admin.auth().generateEmailVerificationLink(user.email).then((link) => {
      return sendCustomVerificationEmail(user.email, link, locale);
    });
  }
});

exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
 if (user.email && !user.emailVerified) {
   throw new functions.auth.HttpsError(
     'invalid-argument', `"${user.email}" needs to be verified before access is granted.`);
  }
});

Считать определенные электронные письма поставщика удостоверений подтвержденными

В следующем примере показано, как считать электронные письма пользователей от определенных поставщиков удостоверений проверенными:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (user.email && !user.emailVerified && context.eventType.indexOf(':facebook.com') !== -1) {
    return {
      emailVerified: true,
    };
  }
});

Блокировка входа с определенных IP-адресов

В следующем примере показано, как заблокировать вход из определенных диапазонов IP-адресов:

Node.js

exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
  if (isSuspiciousIpAddress(context.ipAddress)) {
    throw new functions.auth.HttpsError(
      'permission-denied', 'Unauthorized access!');
  }
});

Установка пользовательских и сеансовых утверждений

В следующем примере показано, как установить пользовательские утверждения и утверждения сеанса:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (context.credential &&
      context.credential.providerId === 'saml.my-provider-id') {
    return {
      // Employee ID does not change so save in persistent claims (stored in
      // Auth DB).
      customClaims: {
        eid: context.credential.claims.employeeid,
      },
      // Copy role and groups to token claims. These will not be persisted.
      sessionClaims: {
        role: context.credential.claims.role,
        groups: context.credential.claims.groups,
      }
    }
  }
});

Отслеживание IP-адресов для отслеживания подозрительной активности

Вы можете предотвратить кражу токенов, отслеживая IP-адрес, с которого входит пользователь, и сравнивая его с IP-адресом при последующих запросах. Если запрос кажется подозрительным (например, IP-адреса принадлежат разным географическим регионам), вы можете попросить пользователя снова войти в систему.

  1. Используйте утверждения сеанса для отслеживания IP-адреса, с помощью которого пользователь входит в систему:

    Node.js

    exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
      return {
        sessionClaims: {
          signInIpAddress: context.ipAddress,
        },
      };
    });
    
  2. Когда пользователь пытается получить доступ к ресурсам, требующим аутентификации с помощью Firebase Authentication , сравните IP-адрес в запросе с IP-адресом, используемым для входа:

    Node.js

    app.post('/getRestrictedData', (req, res) => {
      // Get the ID token passed.
      const idToken = req.body.idToken;
      // Verify the ID token, check if revoked and decode its payload.
      admin.auth().verifyIdToken(idToken, true).then((claims) => {
        // Get request IP address
        const requestIpAddress = req.connection.remoteAddress;
        // Get sign-in IP address.
        const signInIpAddress = claims.signInIpAddress;
        // Check if the request IP address origin is suspicious relative to
        // the session IP addresses. The current request timestamp and the
        // auth_time of the ID token can provide additional signals of abuse,
        // especially if the IP address suddenly changed. If there was a sudden
        // geographical change in a short period of time, then it will give
        // stronger signals of possible abuse.
        if (!isSuspiciousIpAddressChange(signInIpAddress, requestIpAddress)) {
          // Suspicious IP address change. Require re-authentication.
          // You can also revoke all user sessions by calling:
          // admin.auth().revokeRefreshTokens(claims.sub).
          res.status(401).send({error: 'Unauthorized access. Please login again!'});
        } else {
          // Access is valid. Try to return data.
          getData(claims).then(data => {
            res.end(JSON.stringify(data);
          }, error => {
            res.status(500).send({ error: 'Server error!' })
          });
        }
      });
    });
    

Проверка фотографий пользователей

В следующем примере показано, как очистить фотографии профиля пользователей:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (user.photoURL) {
    return isPhotoAppropriate(user.photoURL)
      .then((status) => {
        if (!status) {
          // Sanitize inappropriate photos by replacing them with guest photos.
          // Users could also be blocked from sign-up, disabled, etc.
          return {
            photoUrl: PLACEHOLDER_GUEST_PHOTO_URL,
          };
        }
      });
});

Чтобы узнать больше о том, как обнаруживать и очищать изображения, см. документацию Cloud Vision .

Доступ к учетным данным OAuth поставщика удостоверений пользователя

В следующем примере показано, как получить токен обновления для пользователя, вошедшего в систему Google, и использовать его для вызова API Календаря Google. Токен обновления сохраняется для автономного доступа.

Node.js

const {OAuth2Client} = require('google-auth-library');
const {google} = require('googleapis');
// ...
// Initialize Google OAuth client.
const keys = require('./oauth2.keys.json');
const oAuth2Client = new OAuth2Client(
  keys.web.client_id,
  keys.web.client_secret
);

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (context.credential &&
      context.credential.providerId === 'google.com') {
    // Store the refresh token for later offline use.
    // These will only be returned if refresh tokens credentials are included
    // (enabled by Cloud console).
    return saveUserRefreshToken(
        user.uid,
        context.credential.refreshToken,
        'google.com'
      )
      .then(() => {
        // Blocking the function is not required. The function can resolve while
        // this operation continues to run in the background.
        return new Promise((resolve, reject) => {
          // For this operation to succeed, the appropriate OAuth scope should be requested
          // on sign in with Google, client-side. In this case:
          // https://www.googleapis.com/auth/calendar
          // You can check granted_scopes from within:
          // context.additionalUserInfo.profile.granted_scopes (space joined list of scopes).

          // Set access token/refresh token.
          oAuth2Client.setCredentials({
            access_token: context.credential.accessToken,
            refresh_token: context.credential.refreshToken,
          });
          const calendar = google.calendar('v3');
          // Setup Onboarding event on user's calendar.
          const event = {/** ... */};
          calendar.events.insert({
            auth: oauth2client,
            calendarId: 'primary',
            resource: event,
          }, (err, event) => {
            // Do not fail. This is a best effort approach.
            resolve();
          });
      });
    })
  }
});

Переопределение вердикта reCAPTCHA Enterprise для действий пользователя

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

См. раздел «Включение reCAPTCHA Enterprise» , чтобы узнать больше об интеграции reCAPTCHA Enterprise с аутентификацией Firebase.

Функции блокировки можно использовать для разрешения или блокировки потоков на основе пользовательских факторов, тем самым переопределяя результат, предоставляемый reCAPTCHA Enterprise.

Node.js

const functions = require("firebase-functions/v1");
exports.beforesmsv1 = functions.auth.user().beforeSms((context) => {
 if (
   context.smsType === "SIGN_IN_OR_SIGN_UP" &&
   context.additionalUserInfo.phoneNumber.includes('+91')
 ) {
   return {
     recaptchaActionOverride: "ALLOW",
   };
 }

 // Allow users to sign in with recaptcha score greater than 0.5
 if (event.additionalUserInfo.recaptchaScore > 0.5) {
   return {
     recaptchaActionOverride: 'ALLOW',
   };
 }

 // Block all others.
 return  {
   recaptchaActionOverride: 'BLOCK',
 }
});
,


Функции блокировки позволяют выполнять собственный код, который изменяет результат регистрации или входа пользователя в ваше приложение. Например, вы можете запретить пользователю проходить аутентификацию, если он не соответствует определенным критериям, или обновить информацию пользователя перед возвратом ее в клиентское приложение.

Прежде чем начать

Чтобы использовать функции блокировки, вам необходимо обновить проект Firebase до Firebase Authentication with Identity Platform . Если вы еще не обновились, сделайте это в первую очередь.

Понимание функций блокировки

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

  • beforeCreate : Триггеры до того, как новый пользователь сохранен в базе данных Firebase Authentication , и перед возвращением токена в ваше клиентское приложение.

  • beforeSignIn : триггеры после проверки учетных данных пользователя, но до того, как Firebase Authentication вернет токен идентификатора в ваше клиентское приложение. Если ваше приложение использует многофакторную аутентификацию, функция запускается после того, как пользователь проверяет свой второй фактор. Обратите внимание, что создание нового пользователя также запускает beforeSignIn , в дополнение к beforeCreate .

  • beforeEmail (только node.js) : триггеры перед электронной почтой (например,
    Вход или сброс пароля) отправляется пользователю.

  • beforeSms (только node.js) : триггеры до того, как сообщение SMS будет отправлено пользователю, для таких случаев, как многофакторная аутентификация.

Имейте в виду следующее при использовании функций блокировки:

  • Ваша функция должна отвечать в течение 7 секунд. Через 7 секунд Firebase Authentication возвращает ошибку, и операция клиента не выполняется.

  • Коды ответов HTTP, кроме 200 передаются в ваши клиентские приложения. Убедитесь, что ваш клиентский код обрабатывает любые ошибки, которые может вернуть вашу функцию.

  • Функции применяются ко всем пользователям в вашем проекте, включая любые содержащие в арендаторе . Firebase Authentication предоставляет информацию о пользователях вашей функции, включая любых арендаторов, к которым они принадлежат, чтобы вы могли ответить соответствующим образом.

  • Связывание другого поставщика личности с учетной записью переигрывает любые зарегистрированные функции beforeSignIn .

  • Анонимная и пользовательская аутентификация не запускает функции блокировки.

Развернуть функцию блокировки

Чтобы вставить свой пользовательский код в потоки аутентификации пользователя, разверните функции блокировки. После того, как ваши функции блокировки будут развернуты, ваш пользовательский код должен успешно завершить аутентификацию и создание пользователей, чтобы добиться успеха.

Вы развертываете функцию блокировки так же, как развертывание любой функции. (Смотрите страницу Cloud Functions Начало работы для получения подробной информации). В итоге:

  1. Напишите функцию, которая обрабатывает целевое событие.

    Например, чтобы начать, вы можете добавить функцию NO-OP, например, следующую в index.js :

    const functions = require('firebase-functions/v1');
    
    exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
      // TODO
    });
    
    The above example has omitted the implementation of custom auth logic. See
    the following sections to learn how to implement your blocking functions and
    [Common scenarios](#common-scenarios) for specific examples.
    
  1. Развернуть свои функции, используя CLI Firebase :

    firebase deploy --only functions
    

    Вы должны повторно развернуть свои функции каждый раз, когда вы их обновляете.

Получение информации пользователя и контекста

События beforeSignIn и beforeCreate предоставляют объекты User и EventContext , которые содержат информацию о входе пользователя. Используйте эти значения в вашем коде, чтобы определить, разрешить ли операцию выполнять операцию.

Список свойств, доступных в User объекте, см. Ссылку на API UserRecord .

Объект EventContext содержит следующие свойства:

Имя Описание Пример
locale Локаль приложения. Вы можете установить локаль, используя клиент SDK или пропустив заголовок локали в REST API. fr или sv-SE
ipAddress IP -адрес устройства, с которого регистрирует или входит конечный пользователь. 114.14.200.1
userAgent Пользовательский агент, запускающий функцию блокировки. Mozilla/5.0 (X11; Linux x86_64)
eventId Уникальный идентификатор события. rWsyPtolplG2TBFoOkkgyg
eventType Тип события. Это предоставляет информацию об имени события, такой как beforeSignIn или beforeCreate , и используемый метод входа, такой как Google или электронная почта/пароль. providers/cloud.auth/eventTypes/user.beforeSignIn:password
authType Всегда USER . USER
resource Проект Firebase Authentication или арендатор. projects/ project-id /tenants/ tenant-id
timestamp Время, когда событие было вызвано, отформатировано как строка RFC 3339 . Tue, 23 Jul 2019 21:10:57 GMT
additionalUserInfo Объект, содержащий информацию о пользователе. AdditionalUserInfo
credential Объект, содержащий информацию о учетных данных пользователя. AuthCredential

Блокировка регистрации или входа

Чтобы заблокировать попытку регистрации или входа, добавьте HttpsError в свою функцию. Например:

Node.js

throw new functions.auth.HttpsError('permission-denied');

В следующей таблице перечислены ошибки, которые вы можете собрать, вместе с сообщением об ошибке по умолчанию:

Имя Код Сообщение
invalid-argument 400 Клиент указал неверный аргумент.
failed-precondition 400 Запрос не может быть выполнен в текущем состоянии системы.
out-of-range 400 Клиент указал неверный диапазон.
unauthenticated 401 Отсутствует, недействителен или истек срок действия токена OAuth.
permission-denied 403 Клиент не имеет достаточного разрешения.
not-found 404 Указанный ресурс не найден.
aborted 409 Конфликт параллелизма, такой как конфликт с модификацией чтения.
already-exists 409 Ресурс, который клиент пытался создать, уже существует.
resource-exhausted 429 Либо из квоты ресурсов, либо достижение ограничения скорости.
cancelled 499 Запрос отменен клиентом.
data-loss 500 НЕПРАВИЛЬНАЯ НЕОБХОДИМОСТЬ ДАННЫХ ДАННЫХ ИЛИ КРУКРУКЦИИ.
unknown 500 Неизвестная ошибка сервера.
internal 500 Внутренняя ошибка сервера.
not-implemented 501 Метод API не реализован сервером.
unavailable 503 Сервис недоступен.
deadline-exceeded 504 Крайний срок запроса превышен.

Вы также можете указать пользовательское сообщение об ошибке:

Node.js

throw new functions.auth.HttpsError('permission-denied', 'Unauthorized request origin!');

В следующем примере показано, как блокировать пользователей, которые не находятся в определенном домене от регистрации для вашего приложения:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  // (If the user is authenticating within a tenant context, the tenant ID can be determined from
  // user.tenantId or from context.resource, e.g. 'projects/project-id/tenant/tenant-id-1')

  // Only users of a specific domain can sign up.
  if (user.email.indexOf('@acme.com') === -1) {
    throw new functions.auth.HttpsError('invalid-argument', `Unauthorized email "${user.email}"`);
  }
});

Независимо от того, используете ли вы сообщение по умолчанию или пользовательское сообщение, Cloud Functions завершает ошибку и возвращает его клиенту в качестве внутренней ошибки. Например:

throw new functions.auth.HttpsError('invalid-argument', `Unauthorized email user@evil.com}`);

Ваше приложение должно поймать ошибку и обрабатывать ее соответственно. Например:

JavaScript

// Blocking functions can also be triggered in a multi-tenant context before user creation.
// firebase.auth().tenantId = 'tenant-id-1';
firebase.auth().createUserWithEmailAndPassword('johndoe@example.com', 'password')
  .then((result) => {
    result.user.getIdTokenResult()
  })
  .then((idTokenResult) => {
    console.log(idTokenResult.claim.admin);
  })
  .catch((error) => {
    if (error.code !== 'auth/internal-error' && error.message.indexOf('Cloud Function') !== -1) {
      // Display error.
    } else {
      // Registration succeeds.
    }
  });

Изменение пользователя

Вместо того, чтобы блокировать попытку регистрации или входа, вы можете позволить операции продолжаться, но изменить объект User , который сохраняется в базе данных Firebase Authentication и возвращается клиенту.

Чтобы изменить пользователя, верните объект из вашего обработчика событий, содержащих поля для изменения. Вы можете изменить следующие поля:

  • displayName
  • disabled
  • emailVerified
  • photoUrl
  • customClaims
  • sessionClaims (только beforeSignIn )

За исключением sessionClaims , все модифицированные поля сохраняются в базе данных Firebase Authentication , что означает, что они включены в токен ответа и сохраняются между сеансами пользователей.

В следующем примере показано, как установить имя отображения по умолчанию:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  return {
    // If no display name is provided, set it to "Guest".
    displayName: user.displayName || 'Guest';
  };
});

Если вы зарегистрируете обработчик событий как для beforeCreate , так и beforeSignIn , обратите внимание, что beforeSignIn выполняется после beforeCreate . Пользовательские поля, обновленные в beforeCreate , видны в beforeSignIn . Если вы установите поле, отличное от sessionClaims в обоих обработчиках событий, значение, установленное в beforeSignIn , перезаписывает значение, установленное в beforeCreate . Только для sessionClaims они распространяются на претензии по токену текущей сессии, но не сохраняются или не хранятся в базе данных.

Например, если какие -либо sessionClaims будут установлены, beforeSignIn вернет их с любыми претензиями beforeCreate , и они будут объединены. Когда они объединяются, если ключ sessionClaims соответствует ключу в customClaims , соответствующие customClaims будут перезаписаны в заявлениях о токенах ключа sessionClaims . Тем не менее, переоборудованный ключ customClaims все равно будет сохраняться в базе данных для будущих запросов.

Поддерживаемые учетные данные и данные OAuth

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

Поставщик удостоверений Токен удостоверения личности Токен доступа Срок годности Токен секрет Обновить токен Зарегистрированные претензии
Google Да Да Да Нет Да Нет
Фейсбук Нет Да Да Нет Нет Нет
Твиттер Нет Да Нет Да Нет Нет
GitHub Нет Да Нет Нет Нет Нет
Майкрософт Да Да Да Нет Да Нет
LinkedIn Нет Да Да Нет Нет Нет
Yahoo Да Да Да Нет Да Нет
Яблоко Да Да Да Нет Да Нет
SAML Нет Нет Нет Нет Нет Да
ОИДК Да Да Да Нет Да Да

Обновлять токены

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

Токены обновления не будут возвращены какими -либо поставщиками личности при входе непосредственно с учетными средствами OAuth, такими как токен идентификатора или токен доступа. В этой ситуации та же клиентская учетная запись OAuth будет передана функции блокировки.

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

Общие поставщики OIDC

Когда пользователь входит в систему с общим поставщиком OIDC, будут переданы следующие учетные данные:

  • Идентификационный токен : Предоставлено, если выбран поток id_token .
  • Токен доступа : предоставлено, если выбран поток кода. Обратите внимание, что поток кода в настоящее время поддерживается только через API REST.
  • Токен обновления : предоставлена, если выбрана сфера offline_access .

Пример:

const provider = new firebase.auth.OAuthProvider('oidc.my-provider');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);

Google

Когда пользователь входит в Google, будут переданы следующие учетные данные:

  • Токен удостоверения личности
  • Токен доступа
  • Обновление токена : предоставлено только в случае запроса следующих пользовательских параметров:
    • access_type=offline
    • prompt=consent , если пользователь ранее согласился, и не было запрошено никаких новых возможностей

Пример:

const provider = new firebase.auth.GoogleAuthProvider();
provider.setCustomParameters({
  'access_type': 'offline',
  'prompt': 'consent'
});
firebase.auth().signInWithPopup(provider);

Узнайте больше о Google Refresh Tokens .

Фейсбук

Когда пользователь войдет в Facebook, будут переданы следующие учетные данные:

  • Токен доступа : возвращается токен доступа, который можно обменять на другой токен доступа. Узнайте больше о различных типах токенов доступа , поддерживаемых Facebook, и о том, как вы можете обменять их на долгоживые токены .

GitHub

Когда пользователь войдет в GitHub, будут переданы следующие учетные данные:

  • Токен доступа : не истекает, если не отозвано.

Майкрософт

Когда пользователь войдет в Microsoft, будут переданы следующие учетные данные:

  • Токен удостоверения личности
  • Токен доступа
  • Отключите токен : передается в функцию блокировки, если выбрана область offline_access .

Пример:

const provider = new firebase.auth.OAuthProvider('microsoft.com');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);

Yahoo

Когда пользователь войдет в Yahoo, будут переданы следующие учетные данные без каких -либо пользовательских параметров или областей:

  • Токен удостоверения личности
  • Токен доступа
  • Обновить токен

LinkedIn

Когда пользователь войдет в LinkedIn, будут переданы следующие учетные данные:

  • Токен доступа

Яблоко

Когда пользователь входит в Apple, будут переданы следующие учетные данные без каких -либо пользовательских параметров или областей:

  • Токен удостоверения личности
  • Токен доступа
  • Обновить токен

Общие сценарии

Следующие примеры демонстрируют некоторые общие варианты использования для блокировки:

Только разрешение регистрации из конкретного домена

В следующем примере показано, как предотвратить регистрацию пользователей, которые не являются частью example.com .

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (!user.email || user.email.indexOf('@example.com') === -1) {
    throw new functions.auth.HttpsError(
      'invalid-argument', `Unauthorized email "${user.email}"`);
  }
});

Блокирование пользователей неверными электронными письмами от регистрации

В следующем примере показано, как предотвратить регистрацию пользователей с незавершенными электронными письмами в вашем приложении:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (user.email && !user.emailVerified) {
    throw new functions.auth.HttpsError(
      'invalid-argument', `Unverified email "${user.email}"`);
  }
});

Требование проверки электронной почты при регистрации

В следующем примере показано, как потребовать пользователя проверить свою электронную почту после регистрации:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  const locale = context.locale;
  if (user.email && !user.emailVerified) {
    // Send custom email verification on sign-up.
    return admin.auth().generateEmailVerificationLink(user.email).then((link) => {
      return sendCustomVerificationEmail(user.email, link, locale);
    });
  }
});

exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
 if (user.email && !user.emailVerified) {
   throw new functions.auth.HttpsError(
     'invalid-argument', `"${user.email}" needs to be verified before access is granted.`);
  }
});

Обработка определенных электронных писем поставщика идентификации как проверено

В следующем примере показано, как рассматривать пользовательские электронные письма от определенных поставщиков личности как проверенные:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (user.email && !user.emailVerified && context.eventType.indexOf(':facebook.com') !== -1) {
    return {
      emailVerified: true,
    };
  }
});

Блокировка входа с определенных IP-адресов

Следующий пример, как вход блоков из определенных диапазонов IP-адреса:

Node.js

exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
  if (isSuspiciousIpAddress(context.ipAddress)) {
    throw new functions.auth.HttpsError(
      'permission-denied', 'Unauthorized access!');
  }
});

Установка пользовательских и сессионных претензий

В следующем примере показано, как установить пользовательские и сессионные претензии:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (context.credential &&
      context.credential.providerId === 'saml.my-provider-id') {
    return {
      // Employee ID does not change so save in persistent claims (stored in
      // Auth DB).
      customClaims: {
        eid: context.credential.claims.employeeid,
      },
      // Copy role and groups to token claims. These will not be persisted.
      sessionClaims: {
        role: context.credential.claims.role,
        groups: context.credential.claims.groups,
      }
    }
  }
});

Отслеживание IP -адресов для мониторинга подозрительной активности

Вы можете предотвратить кражу токенов, отслеживая IP -адрес, от которого выходит пользовательский, и сравнивая его с IP -адресом по последующим запросам. Если запрос выглядит подозрительным - например, IPS из разных географических регионов - вы можете попросить пользователя снова войти в систему.

  1. Используйте претензии сеанса, чтобы отслеживать IP -адрес, в который входит пользовательский пользователь:

    Node.js

    exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
      return {
        sessionClaims: {
          signInIpAddress: context.ipAddress,
        },
      };
    });
    
  2. Когда пользователь пытается получить доступ к ресурсам, которые требуют аутентификации с Firebase Authentication , сравните IP -адрес в запросе с IP, используемым для входа:

    Node.js

    app.post('/getRestrictedData', (req, res) => {
      // Get the ID token passed.
      const idToken = req.body.idToken;
      // Verify the ID token, check if revoked and decode its payload.
      admin.auth().verifyIdToken(idToken, true).then((claims) => {
        // Get request IP address
        const requestIpAddress = req.connection.remoteAddress;
        // Get sign-in IP address.
        const signInIpAddress = claims.signInIpAddress;
        // Check if the request IP address origin is suspicious relative to
        // the session IP addresses. The current request timestamp and the
        // auth_time of the ID token can provide additional signals of abuse,
        // especially if the IP address suddenly changed. If there was a sudden
        // geographical change in a short period of time, then it will give
        // stronger signals of possible abuse.
        if (!isSuspiciousIpAddressChange(signInIpAddress, requestIpAddress)) {
          // Suspicious IP address change. Require re-authentication.
          // You can also revoke all user sessions by calling:
          // admin.auth().revokeRefreshTokens(claims.sub).
          res.status(401).send({error: 'Unauthorized access. Please login again!'});
        } else {
          // Access is valid. Try to return data.
          getData(claims).then(data => {
            res.end(JSON.stringify(data);
          }, error => {
            res.status(500).send({ error: 'Server error!' })
          });
        }
      });
    });
    

Экран фотографий пользователей

В следующем примере показано, как дезинфицировать фотографии профиля пользователей:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (user.photoURL) {
    return isPhotoAppropriate(user.photoURL)
      .then((status) => {
        if (!status) {
          // Sanitize inappropriate photos by replacing them with guest photos.
          // Users could also be blocked from sign-up, disabled, etc.
          return {
            photoUrl: PLACEHOLDER_GUEST_PHOTO_URL,
          };
        }
      });
});

Чтобы узнать больше о том, как обнаружить и дезинфицировать изображения, см. Документацию Cloud Vision .

Доступ к поставщику идентификации пользователя OAuth

В следующем примере демонстрируется, как получить токен обновления для пользователя, который вписался с Google, и использует его для вызова API Google Calendar. Токен обновления хранится для автономного доступа.

Node.js

const {OAuth2Client} = require('google-auth-library');
const {google} = require('googleapis');
// ...
// Initialize Google OAuth client.
const keys = require('./oauth2.keys.json');
const oAuth2Client = new OAuth2Client(
  keys.web.client_id,
  keys.web.client_secret
);

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (context.credential &&
      context.credential.providerId === 'google.com') {
    // Store the refresh token for later offline use.
    // These will only be returned if refresh tokens credentials are included
    // (enabled by Cloud console).
    return saveUserRefreshToken(
        user.uid,
        context.credential.refreshToken,
        'google.com'
      )
      .then(() => {
        // Blocking the function is not required. The function can resolve while
        // this operation continues to run in the background.
        return new Promise((resolve, reject) => {
          // For this operation to succeed, the appropriate OAuth scope should be requested
          // on sign in with Google, client-side. In this case:
          // https://www.googleapis.com/auth/calendar
          // You can check granted_scopes from within:
          // context.additionalUserInfo.profile.granted_scopes (space joined list of scopes).

          // Set access token/refresh token.
          oAuth2Client.setCredentials({
            access_token: context.credential.accessToken,
            refresh_token: context.credential.refreshToken,
          });
          const calendar = google.calendar('v3');
          // Setup Onboarding event on user's calendar.
          const event = {/** ... */};
          calendar.events.insert({
            auth: oauth2client,
            calendarId: 'primary',
            resource: event,
          }, (err, event) => {
            // Do not fail. This is a best effort approach.
            resolve();
          });
      });
    })
  }
});

Переопределение вердикта Recaptcha Enterprise для работы пользователя

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

Обратитесь к включению Recaptcha Enterprise , чтобы узнать больше об интеграции предприятия Recaptcha с аутентификацией Firebase.

Функции блокировки могут использоваться для разрешения или блокировать потоки на основе пользовательских факторов, тем самым переопределяя результат, предоставленный Recaptcha Enterprise.

Node.js

const functions = require("firebase-functions/v1");
exports.beforesmsv1 = functions.auth.user().beforeSms((context) => {
 if (
   context.smsType === "SIGN_IN_OR_SIGN_UP" &&
   context.additionalUserInfo.phoneNumber.includes('+91')
 ) {
   return {
     recaptchaActionOverride: "ALLOW",
   };
 }

 // Allow users to sign in with recaptcha score greater than 0.5
 if (event.additionalUserInfo.recaptchaScore > 0.5) {
   return {
     recaptchaActionOverride: 'ALLOW',
   };
 }

 // Block all others.
 return  {
   recaptchaActionOverride: 'BLOCK',
 }
});