Блокирующие функции позволяют выполнять пользовательский код, изменяющий результат регистрации или входа пользователя в ваше приложение. Например, вы можете запретить пользователю аутентификацию, если он не соответствует определенным критериям, или обновить информацию о пользователе перед ее возвратом в ваше клиентское приложение.
Прежде чем начать
Для использования блокирующих функций необходимо обновить ваш проект Firebase до Firebase Authentication with Identity Platform . Если вы еще этого не сделали, выполните обновление в первую очередь.
Понимание блокирующих функций
Для этих событий можно зарегистрировать функции блокировки:
beforeCreate: Этот метод срабатывает перед сохранением нового пользователя в базу данных Firebase Authentication и перед возвратом токена в клиентское приложение.beforeSignIn: Эта функция срабатывает после проверки учетных данных пользователя, но до того, как Firebase Authentication вернет токен ID вашему клиентскому приложению. Если ваше приложение использует многофакторную аутентификацию, функция срабатывает после того, как пользователь подтвердит свой второй фактор. Обратите внимание, что создание нового пользователя также запускаетbeforeSignIn, помимоbeforeCreate.beforeEmail(только для Node.js) : запускает отправку электронного письма (например,
Пользователю отправляется электронное письмо с подтверждением входа или запросом на сброс пароля.beforeSms(только для Node.js) : Запускается перед отправкой SMS-сообщения пользователю, например, при многофакторной аутентификации.
При использовании блокирующих функций следует учитывать следующее:
Ваша функция должна ответить в течение 7 секунд. По истечении 7 секунд Firebase Authentication возвращает ошибку, и операция клиента завершается с ошибкой.
В клиентские приложения передаются HTTP-ответы с кодами, отличными от
200Убедитесь, что ваш клиентский код обрабатывает все ошибки, которые может вернуть ваша функция.Функции применяются ко всем пользователям в вашем проекте, включая тех, кто входит в состав арендатора . Firebase Authentication предоставляет вашей функции информацию о пользователях, включая арендаторов, к которым они принадлежат, чтобы вы могли соответствующим образом реагировать.
Привязка другого поставщика идентификационных данных к учетной записи повторно запускает все зарегистрированные функции
beforeSignIn.Анонимная и пользовательская аутентификация не активируют функции блокировки.
Внедрите блокирующую функцию.
Для вставки вашего пользовательского кода в процессы аутентификации пользователей разверните блокирующие функции. После развертывания блокирующих функций ваш пользовательский код должен успешно завершиться, чтобы аутентификация и создание пользователя прошли успешно.
Блокирующую функцию развертывают так же, как и любую другую функцию (подробнее см. страницу « Начало работы Cloud Functions »). Вкратце:
Напишите функцию, которая обрабатывает целевое событие.
Например, для начала можно добавить в файл
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.
Развертывайте свои функции с помощью 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 в функции блокировки от различных поставщиков идентификации. В следующей таблице показано, какие учетные данные и данные поддерживаются каждым поставщиком идентификации:
| Поставщик идентификационных данных | Идентификационный токен | Токен доступа | Срок действия | Секретный токен | Токен обновления | Заявки на вход |
|---|---|---|---|---|---|---|
| Да | Да | Да | Нет | Да | Нет | |
| Фейсбук | Нет | Да | Да | Нет | Нет | Нет |
| Твиттер | Нет | Да | Нет | Да | Нет | Нет |
| GitHub | Нет | Да | Нет | Нет | Нет | Нет |
| Microsoft | Да | Да | Да | Нет | Да | Нет |
| Нет | Да | Да | Нет | Нет | Нет | |
| Яху | Да | Да | Да | Нет | Да | Нет |
| Яблоко | Да | Да | Да | Нет | Да | Нет |
| 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 передаются следующие учетные данные:
- ID токен
- токен доступа
- Токен обновления : Предоставляется только в том случае, если запрошены следующие пользовательские параметры:
-
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
При входе пользователя в систему через Microsoft будут переданы следующие учетные данные:
- ID токен
- токен доступа
- Токен обновления : передается в блокирующую функцию, если выбрана область действия
offline_access.
Пример:
const provider = new firebase.auth.OAuthProvider('microsoft.com');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);
Яху
При входе пользователя в систему через Yahoo будут передаваться следующие учетные данные без каких-либо пользовательских параметров или областей действия:
- ID токен
- токен доступа
- Токен обновления
При входе пользователя через LinkedIn будут переданы следующие учетные данные:
- токен доступа
Яблоко
При входе пользователя в систему с помощью Apple будут переданы следующие учетные данные без каких-либо пользовательских параметров или областей действия:
- ID токен
- токен доступа
- Токен обновления
Типичные сценарии
Следующие примеры демонстрируют некоторые распространенные варианты использования блокирующих функций:
Регистрация разрешена только с определенного домена.
В следующем примере показано, как предотвратить регистрацию в вашем приложении пользователей, не являющихся пользователями домена 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-адреса принадлежат разным географическим регионам — можно попросить пользователя войти в систему снова.
Используйте утверждения сессии для отслеживания IP-адреса, с которого пользователь входит в систему:
Node.js
exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => { return { sessionClaims: { signInIpAddress: context.ipAddress, }, }; });Когда пользователь пытается получить доступ к ресурсам, требующим аутентификации с помощью 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 с Firebase Authentication обратитесь к разделу «Включение reCAPTCHA Enterprise» .
Функции блокировки позволяют разрешать или блокировать потоки на основе заданных параметров, тем самым отменяя результат, предоставляемый 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',
}
});