Функции блокировки позволяют выполнять пользовательский код, который изменяет результат регистрации или входа пользователя в ваше приложение. Например, вы можете запретить пользователю проходить аутентификацию, если он не соответствует определенным критериям, или обновить информацию о пользователе, прежде чем вернуть ее в клиентское приложение.
Прежде чем вы начнете
Чтобы использовать функции блокировки, вы должны обновить свой проект Firebase до Firebase Authentication with Identity Platform. Если вы еще не обновились, сделайте это в первую очередь.
Понимание функций блокировки
Вы можете зарегистрировать блокирующие функции для двух событий:
beforeCreate
: срабатывает до того, как новый пользователь будет сохранен в базе данных Firebase Authentication и до того, как токен будет возвращен вашему клиентскому приложению.beforeSignIn
: срабатывает после проверки учетных данных пользователя, но до того, как проверка подлинности Firebase вернет токен идентификатора в ваше клиентское приложение. Если ваше приложение использует многофакторную аутентификацию, функция срабатывает после того, как пользователь подтвердит свой второй фактор. Обратите внимание, что создание нового пользователя также вызывает действиеbeforeSignIn
в дополнение кbeforeCreate
.
При использовании блокирующих функций помните следующее:
Ваша функция должна ответить в течение 7 секунд. Через 7 секунд проверка подлинности Firebase возвращает ошибку, и операция клиента завершается сбоем.
Коды ответов HTTP, отличные от
200
, передаются вашим клиентским приложениям. Убедитесь, что ваш клиентский код обрабатывает любые ошибки, которые может возвращать ваша функция.Функции применяются ко всем пользователям в вашем проекте, включая всех, содержащихся в арендаторе . Firebase Authentication предоставляет информацию о пользователях вашей функции, в том числе обо всех арендаторах, которым они принадлежат, чтобы вы могли реагировать соответствующим образом.
Привязка другого поставщика удостоверений к учетной записи повторно запускает все зарегистрированные функции
beforeSignIn
.Анонимная и пользовательская аутентификация не запускают функции блокировки.
Разверните и зарегистрируйте функцию блокировки
Чтобы вставить собственный код в потоки аутентификации пользователей, разверните и зарегистрируйте блокирующие функции. После развертывания и регистрации функций блокировки ваш пользовательский код должен успешно завершиться для успешной аутентификации и создания пользователя.
Разверните функцию блокировки
Вы развертываете блокирующую функцию так же, как и любую другую функцию. (подробности см. на странице «Приступая к работе с облачными функциями»). В итоге:
Напишите облачные функции, которые обрабатывают событие
beforeCreate
, событиеbeforeSignIn
или и то, и другое.Например, для начала вы можете добавить в
index.js
следующие недействующие функции:const functions = require('firebase-functions'); exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => { // TODO }); exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => { // TODO });
В приведенных выше примерах отсутствует реализация пользовательской логики аутентификации. См. следующие разделы, чтобы узнать, как реализовать функции блокировки, и Общие сценарии для конкретных примеров.
Разверните свои функции с помощью интерфейса командной строки Firebase:
firebase deploy --only functions
Вы должны повторно развертывать свои функции каждый раз, когда вы их обновляете.
Зарегистрируйте функцию блокировки
Перейдите на страницу настроек аутентификации Firebase в консоли Firebase.
Выберите вкладку Блокирующие функции .
Зарегистрируйте функцию блокировки, выбрав ее в раскрывающемся меню перед созданием учетной записи (beforeCreate) или перед входом в систему (beforeSignIn) .
Сохраните изменения.
Получение информации о пользователе и контексте
События 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 блокирующим функциям от различных поставщиков удостоверений. В следующей таблице показано, какие учетные данные и данные поддерживаются для каждого поставщика удостоверений.
Поставщик удостоверений | Идентификационный токен | Токен доступа | Время истечения | Секрет токена | Обновить токен | Заявки на вход |
---|---|---|---|---|---|---|
Да | Да | Да | Нет | Да | Нет | |
Фейсбук | Нет | Да | Да | Нет | Нет | Нет |
Твиттер | Нет | Да | Нет | Да | Нет | Нет |
Гитхаб | Нет | Да | Нет | Нет | Нет | Нет |
Майкрософт | Да | Да | Да | Нет | Да | Нет |
Нет | Да | Да | Нет | Нет | Нет | |
Yahoo | Да | Да | Да | Нет | Да | Нет |
Яблоко | Да | Да | Да | Нет | Да | Нет |
SAML | Нет | Нет | Нет | Нет | Нет | Да |
ОИДК | Да | Да | Да | Нет | Да | Да |
Обновить токены
Чтобы использовать токен обновления в функции блокировки, необходимо сначала установить флажок на странице функций блокировки консоли Firebase.
Маркеры обновления не будут возвращаться поставщиками удостоверений при прямом входе с использованием учетных данных OAuth, например маркера идентификатора или маркера доступа. В этой ситуации те же учетные данные OAuth на стороне клиента будут переданы блокирующей функции.
В следующих разделах описаны все типы поставщиков удостоверений, а также поддерживаемые ими учетные данные и данные.
Универсальные поставщики OIDC
Когда пользователь входит в систему с помощью универсального поставщика OIDC, будут переданы следующие учетные данные:
- Токен ID : Предоставляется, если выбран поток
id_token
. - Маркер доступа : предоставляется, если выбран поток кода. Обратите внимание, что поток кода в настоящее время поддерживается только через REST API.
- Токен обновления : предоставляется, если выбрана область
offline_access
.
Пример:
const provider = new firebase.auth.OAuthProvider('oidc.my-provider');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);
Когда пользователь входит в систему с помощью 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, ему передаются следующие учетные данные:
- Маркер доступа : не истекает, если не отозван.
Майкрософт
Когда пользователь входит в систему Microsoft, будут переданы следующие учетные данные:
- Идентификационный токен
- Токен доступа
- Токен обновления : передается функции блокировки, если выбрана область
offline_access
.
Пример:
const provider = new firebase.auth.OAuthProvider('microsoft.com');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);
Yahoo
Когда пользователь входит в систему Yahoo, следующие учетные данные будут переданы без каких-либо настраиваемых параметров или областей действия:
- Идентификационный токен
- Токен доступа
- Обновить токен
Когда пользователь входит в 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-адреса из разных географических регионов — вы можете попросить пользователя снова войти в систему.
Используйте утверждения сеанса для отслеживания 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();
});
});
})
}
});