Funkcje blokujące pozwalają uruchamiać niestandardowy kod, który modyfikuje wynik rejestrujących się lub logujących w aplikacji. Możesz na przykład uniemożliwić użytkownikowi możliwość uwierzytelniania, jeśli użytkownik nie spełnia określonych kryteriów, lub zaktualizować informacji przed zwróceniem ich do aplikacji klienckiej.
Zanim zaczniesz
Aby używać funkcji blokowania, musisz uaktualnić projekt Firebase do wersji Firebase Authentication with Identity Platform. Jeśli nie korzystasz jeszcze z tej usługi, zrób to w pierwszej kolejności.
Funkcje blokowania
Funkcje blokowania możesz zarejestrować dla 2 zdarzeń:
beforeCreate
: reguły aktywujące, zanim nowy użytkownik zostanie zapisany w Firebase Authentication, ale przed zwróceniem tokena do aplikacji klienckiej.beforeSignIn
: uruchamia się po zweryfikowaniu danych logowania użytkownika, ale zanim Firebase Authentication zwróci token identyfikatora do aplikacji klienckiej. Jeśli Aplikacja korzysta z uwierzytelniania wielopoziomowego, uruchamia się, gdy użytkownik zweryfikuje drugi składnik. Pamiętaj, że utworzenie nowej użytkownik wywołał też zdarzeniebeforeSignIn
, opróczbeforeCreate
.
Podczas korzystania z funkcji blokowania pamiętaj o tych kwestiach:
Twoja funkcja musi odpowiedzieć w ciągu 7 sekund. Po 7 sekundach Firebase Authentication zwraca błąd, a operacja klienta kończy się niepowodzeniem.
Kody odpowiedzi HTTP inne niż
200
są przekazywane do aplikacji klienckich. Upewnij się, że kod klienta obsługuje wszystkie błędy zwracane przez funkcję.Funkcje mają zastosowanie do wszystkich użytkowników w projekcie, w tym do wszystkich najemca. Firebase Authentication udostępnia informacje o użytkownikach funkcji, w tym z lokalnymi najemcami, więc możesz odpowiednio odpowiedzieć.
Połączenie innego dostawcy tożsamości z kontem powoduje ponowne wywołanie każdego zarejestrowanego dostawcy funkcji
beforeSignIn
.Uwierzytelnianie anonimowe i niestandardowe nie uruchamiają funkcji blokujących.
Wdrażanie funkcji blokującej
Aby wstawić kod niestandardowy do procesów uwierzytelniania użytkowników, wdróż blokowanie funkcji. Gdy funkcje blokowania zostaną wdrożone, niestandardowy kod musi zostać uruchomiony, aby uwierzytelnianie i tworzenie użytkownika przebiegło pomyślnie.
Funkcję blokującą wdrażasz w taki sam sposób jak dowolną funkcję. (zobacz stronę Cloud Functions Pierwsze kroki) ). W skrócie:
Zapisz obiekt Cloud Functions, który obsługuje zdarzenie
beforeCreate
,beforeSignIn
lub oba.Na początek możesz na przykład dodać następujące funkcje no-op do
index.js
:const functions = require('firebase-functions/v1'); exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => { // TODO }); exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => { // TODO });
W powyższych przykładach pominięto implementację niestandardowej logiki uwierzytelniania. Zobacz w poniższych sekcjach dowiesz się, jak wdrożyć funkcje blokowania Typowe scenariusze dla konkretnych przykładów.
Wdróż funkcje za pomocą interfejsu wiersza poleceń Firebase:
firebase deploy --only functions
przy każdej aktualizacji funkcji musisz wdrażać je ponownie.
Uzyskiwanie informacji o użytkowniku i kontekstu
Zdarzenia beforeSignIn
i beforeCreate
udostępniają obiekty User
i EventContext
, które zawierają informacje o logowaniu użytkownika. Użyj tych wartości
w kodzie, aby umożliwić kontynuowanie operacji.
Listę właściwości dostępnych w obiekcie User
znajdziesz w
Dokumentacja API UserRecord
Obiekt EventContext
ma te właściwości:
Nazwa | Opis | Przykład |
---|---|---|
locale |
Język aplikacji. Możesz ustawić język za pomocą SDK klienta lub przez przekazanie nagłówka języka w interfejsie API REST. | fr lub sv-SE |
ipAddress
| adres IP urządzenia, które użytkownik rejestruje lub loguje; . | 114.14.200.1 |
userAgent
| Klient użytkownika aktywujący funkcję blokowania. | Mozilla/5.0 (X11; Linux x86_64) |
eventId
| Unikalny identyfikator zdarzenia. | rWsyPtolplG2TBFoOkkgyg |
eventType
|
Typ zdarzenia. Zawiera on informacje o nazwie zdarzenia, takie jak:
beforeSignIn lub beforeCreate oraz
powiązanej metody logowania, np. przez Google lub adres e-mail/hasło.
|
providers/cloud.auth/eventTypes/user.beforeSignIn:password
|
authType
| Zawsze USER . |
USER
|
resource
| Projekt lub najemca Firebase Authentication. |
projects/project-id/tenants/tenant-id
|
timestamp
| Czas wywołania zdarzenia podany w formacie Ciąg RFC 3339. | Tue, 23 Jul 2019 21:10:57 GMT
|
additionalUserInfo
| Obiekt zawierający informacje o użytkowniku. |
AdditionalUserInfo
|
credential
| Obiekt zawierający informacje o danych logowania użytkownika. |
AuthCredential
|
Blokowanie rejestracji lub logowania
Aby zablokować rejestrację lub próbę logowania, wrzuć HttpsError
. Przykład:
Node.js
throw new functions.auth.HttpsError('permission-denied');
W tabeli poniżej znajdziesz błędy, które możesz zgłosić, wraz z ich wartościami domyślnymi komunikat o błędzie:
Nazwa | Kod | Wiadomość |
---|---|---|
invalid-argument |
400 |
Klient podał nieprawidłowy argument. |
failed-precondition |
400 |
Żądania nie można wykonać przy aktualnym stanie systemu. |
out-of-range |
400 |
Klient podał nieprawidłowy zakres. |
unauthenticated |
401 |
Brakujący, nieprawidłowy lub wygasły token OAuth. |
permission-denied |
403 |
Klient nie ma wystarczających uprawnień. |
not-found |
404 |
Nie udało się znaleźć podanego zasobu. |
aborted |
409 |
Konflikt równoczesności, na przykład konflikt odczytu, modyfikacji i zapisu. |
already-exists |
409 |
Zasób, który próbował utworzyć klient, już istnieje. |
resource-exhausted |
429 |
Limit zasobu został wyczerpany lub usługa aktywuje ograniczanie liczby żądań. |
cancelled |
499 |
Żądanie anulowane przez klienta. |
data-loss |
500 |
Nieodwracalna utrata danych lub ich uszkodzenie. |
unknown |
500 |
Nieznany błąd serwera. |
internal |
500 |
Wewnętrzny błąd serwera. |
not-implemented |
501 |
Metoda interfejsu API nie została zaimplementowana przez serwer. |
unavailable |
503 |
Usługa niedostępna |
deadline-exceeded |
504 |
Upłynął termin realizacji żądania. |
Możesz też określić niestandardowy komunikat o błędzie:
Node.js
throw new functions.auth.HttpsError('permission-denied', 'Unauthorized request origin!');
Przykład poniżej pokazuje, jak blokować użytkowników, którzy nie znajdują się w określonym miejscu domeny podczas rejestracji w aplikacji:
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}"`);
}
});
Niezależnie od tego, czy wybierzesz komunikat domyślny czy niestandardowy, Cloud Functions pakuje błąd i zwraca go klientowi jako błąd wewnętrzny. Przykład:
throw new functions.auth.HttpsError('invalid-argument', `Unauthorized email user@evil.com}`);
Aplikacja powinna wykryć błąd i odpowiednio go obsłużyć. Przykład:
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.
}
});
Modyfikowanie użytkownika
Zamiast blokować rejestrację lub próbę logowania, możesz zezwolić na
aby kontynuować, ale zmodyfikować obiekt User
zapisany w
z bazy danych Firebase Authentication i wróciła do klienta.
Aby zmodyfikować użytkownika, zwracaj obiekt z obsługi zdarzenia zawierający pola, które mają zostać zmodyfikowane. Możesz modyfikować następujące pola:
displayName
disabled
emailVerified
photoUrl
customClaims
sessionClaims
(tylkobeforeSignIn
)
Z wyjątkiem pola sessionClaims
wszystkie zmodyfikowane pola są zapisywane w
do bazy danych Firebase Authentication, co oznacza, że są one uwzględnione w odpowiedzi
token i są zachowywane między sesjami użytkownika.
Ten przykład pokazuje, jak ustawić domyślną wyświetlaną nazwę:
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';
};
});
Jeśli zarejestrujesz moduł obsługi zdarzeń zarówno w przypadku beforeCreate
, jak i beforeSignIn
,
pamiętaj, że beforeSignIn
jest uruchamiany po beforeCreate
. Pola użytkownika zaktualizowane w:
beforeCreate
są widoczne w domenie beforeSignIn
. Jeśli ustawisz pole inne niż
sessionClaims
w obu modułach obsługi zdarzeń – wartość ustawiona w polu beforeSignIn
.
zastępuje wartość ustawioną w beforeCreate
. Tylko w przypadku sessionClaims
są to
przekazywane do deklaracji tokena bieżącej sesji, ale nie są zachowywane lub
przechowywane w bazie danych.
Jeśli na przykład są ustawione dowolne sessionClaims
, beforeSignIn
zwróci je.
z wszelkimi roszczeniami typu beforeCreate
. Zostaną one scalone. Kiedy są scalone,
klucz sessionClaims
pasuje do klucza w customClaims
, dopasowanie
Pole customClaims
zostanie zastąpione w żądaniach tokenów przez sessionClaims
. Jednak nadrzędny klucz customClaims
zostanie zachowany w
do obsługi przyszłych żądań.
Obsługiwane dane uwierzytelniające i dane OAuth
Dane logowania i dane OAuth można przekazywać funkcjom blokującym z różnych dostawców tożsamości. W poniższej tabeli przedstawiono dane logowania i dane obsługiwanych dla każdego dostawcy tożsamości:
Dostawca tożsamości | Identyfikator tokena | Token dostępu | Data ważności | Tajny token | Token odświeżania | Żądania logowania |
---|---|---|---|---|---|---|
Tak | Tak | Tak | Nie | Tak | Nie | |
Nie | Tak | Tak | Nie | Nie | Nie | |
Nie | Tak | Nie | Tak | Nie | Nie | |
GitHub | Nie | Tak | Nie | Nie | Nie | Nie |
Microsoft | Tak | Tak | Tak | Nie | Tak | Nie |
Nie | Tak | Tak | Nie | Nie | Nie | |
Yahoo | Tak | Tak | Tak | Nie | Tak | Nie |
Apple | Tak | Tak | Tak | Nie | Tak | Nie |
SAML | Nie | Nie | Nie | Nie | Nie | Tak |
OIDC | Tak | Tak | Tak | Nie | Tak | Tak |
Tokeny odświeżania
Aby użyć tokena odświeżania w funkcji blokującej, musisz najpierw wybrać na stronie Funkcje blokowania w konsoli Firebase.
Tokeny odświeżania nie będą zwracane przez dostawców tożsamości podczas logowania bezpośrednio za pomocą danych logowania OAuth, takich jak token identyfikatora lub token dostępu. W tej sytuacji do funkcji blokującej zostaną przekazane te same dane uwierzytelniające OAuth po stronie klienta.
W poniższych sekcjach opisano poszczególne typy dostawców tożsamości i ich obsługiwane i danych logowania.
Ogólni dostawcy OIDC
Gdy użytkownik loguje się u ogólnego dostawcy OIDC, te dane logowania zostanie przekazana:
- Token identyfikatora: podaje się, jeśli wybrano przepływ
id_token
. - Token dostępu: podawany, jeśli wybrano przepływ kodu. Pamiętaj, że kod przepływ jest obecnie obsługiwany tylko przez interfejs API REST.
- Token odświeżania: podawany, jeśli
offline_access
zakres zaznaczono.
Przykład:
const provider = new firebase.auth.OAuthProvider('oidc.my-provider');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);
Gdy użytkownik zaloguje się przez Google, zostaną przekazane te dane logowania:
- Token tożsamości
- Token dostępu
- Token odświeżania: jest dostępny tylko wtedy, gdy żądasz tych niestandardowych parametrów:
access_type=offline
prompt=consent
, jeśli użytkownik wcześniej wyraził zgodę, ale nie. wysłano prośbę o nowy zakres
Przykład:
const provider = new firebase.auth.GoogleAuthProvider();
provider.setCustomParameters({
'access_type': 'offline',
'prompt': 'consent'
});
firebase.auth().signInWithPopup(provider);
Dowiedz się więcej o tokenach odświeżania Google.
Gdy użytkownik zaloguje się przez Facebooka, zostaną przekazane następujące dane logowania:
- Token dostępu: zwracany jest token dostępu, który można wymienić na inny token dostępu. Dowiedz się więcej o różnych typach tagów tokeny dostępu obsługiwane przez Facebooka oraz sposoby ich wymiany tokenów długotrwałych.
GitHub
Gdy użytkownik zaloguje się przez GitHuba, otrzyma te dane logowania:
- Token dostępu: nie wygasa, dopóki nie zostanie unieważniony.
Microsoft
Gdy użytkownik loguje się w usłudze Microsoft, przekazywane są te dane logowania:
- Token tożsamości
- Token dostępu
- Token odświeżania: przekazywany do funkcji blokującej, jeśli
offline_access
zakres zaznaczono.
Przykład:
const provider = new firebase.auth.OAuthProvider('microsoft.com');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);
Yahoo
Gdy użytkownik zaloguje się przez Yahoo, zostaną przekazane te dane logowania bez żadnych niestandardowych parametrów ani zakresów:
- Token tożsamości
- Token dostępu
- Token odświeżania
Gdy użytkownik zaloguje się przez LinkedIn, zostaną przekazane te dane logowania:
- Token dostępu
Apple
Gdy użytkownik zaloguje się przez Apple, zostaną przekazane te dane logowania bez żadnych niestandardowych parametrów ani zakresów:
- Token identyfikacyjny
- Token dostępu
- Token odświeżania
Typowe scenariusze
Poniższe przykłady pokazują typowe przypadki użycia funkcji blokujących:
Zezwalanie na rejestrację tylko z konkretnej domeny
Poniższy przykład pokazuje, jak uniemożliwić użytkownikom, którzy nie biorą udziału w programie
example.com
domeny od rejestracji w aplikacji:
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}"`);
}
});
Uniemożliwianie rejestracji użytkownikom z niezweryfikowanymi adresami e-mail
Poniższy przykład pokazuje, jak uniemożliwić użytkownikom z niezweryfikowanymi adresami e-mail podczas rejestracji w aplikacji:
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}"`);
}
});
Wymaganie potwierdzenia adresu e-mail podczas rejestracji
Ten przykład pokazuje, jak wymagać od użytkownika potwierdzenia adresu e-mail po rejestracja:
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.`);
}
});
Traktowanie adresów e-mail niektórych dostawców tożsamości jako zweryfikowanych
Przykład poniżej pokazuje, jak traktować adresy e-mail użytkowników dotyczące określonej tożsamości dostawcy jako zweryfikowani:
Node.js
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
if (user.email && !user.emailVerified && context.eventType.indexOf(':facebook.com') !== -1) {
return {
emailVerified: true,
};
}
});
Blokowanie logowania z określonych adresów IP
Ten przykład pokazuje, jak blokować logowanie z określonych zakresów adresów IP:
Node.js
exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
if (isSuspiciousIpAddress(context.ipAddress)) {
throw new functions.auth.HttpsError(
'permission-denied', 'Unauthorized access!');
}
});
Ustawianie deklaracji niestandardowych i deklaracji sesji
Ten przykład pokazuje, jak ustawić oświadczenia niestandardowe i oświadczenia sesji:
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,
}
}
}
});
śledzenie adresów IP w celu monitorowania podejrzanej aktywności;
Aby zapobiec kradzieży tokenów, możesz śledzić adres IP, z którego użytkownik się loguje, i porównywać go z adresem IP w kolejnych żądaniach. Jeśli prośba wydaje się podejrzana – na przykład adresy IP pochodzą z różnych lokalizacji geograficznych regionów – możesz poprosić użytkownika o ponowne zalogowanie się.
Użyj roszczeń sesji, aby śledzić adres IP, z którego użytkownik się loguje:
Node.js
exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => { return { sessionClaims: { signInIpAddress: context.ipAddress, }, }; });
Gdy użytkownik próbuje uzyskać dostęp do zasobów wymagających uwierzytelniania za pomocą Firebase Authentication, porównaj adres IP w żądaniu z adresem IP użytym aby się zalogować:
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!' }) }); } }); });
Filtruję zdjęcia użytkowników
Ten przykład pokazuje, jak odizolować zdjęcia profilowe użytkowników:
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,
};
}
});
});
Więcej informacji o wykrywaniu i dezynfekcji obrazów znajdziesz tutaj: Cloud Vision.
Uzyskiwanie dostępu do danych logowania OAuth dostawcy tożsamości użytkownika
Ten przykład pokazuje, jak uzyskać token odświeżania dla użytkownika zalogowanego w Google i użyć go do wywołania interfejsów Google Calendar API. Token odświeżania jest przechowywany na potrzeby dostępu offline.
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();
});
});
})
}
});
Zastąp ocenę reCAPTCHA Enterprise dla operacji użytkownika
Przykład poniżej pokazuje, jak zastąpić ocenę reCAPTCHA Enterprise w obsługiwanych przepływach użytkowników.
Więcej informacji o integrowaniu reCAPTCHA Enterprise z Uwierzytelnianiem Firebase znajdziesz w artykule Włączanie reCAPTCHA Enterprise.
Funkcji blokowania można używać do zezwalania na przepływy lub do ich blokowania na podstawie czynników niestandardowych, zastępując wynik uzyskany przez reCAPTCHA Enterprise.
Node.js
const {
auth,
} = require("firebase-functions/v1");
exports.checkrecaptchaV1 = auth.user().beforeSignIn((userRecord, context) => {
// Allow users with a specific email domain to sign in regardless of their recaptcha score.
if (userRecord.email && userRecord.email.indexOf('@acme.com') === -1) {
return {
recaptchaActionOverride: 'ALLOW',
};
}
// Allow users to sign in with recaptcha score greater than 0.5
if (context.additionalUserInfo.recaptchaScore > 0.5) {
return {
recaptchaActionOverride: 'ALLOW',
};
}
// Block all others.
return {
recaptchaActionOverride: 'BLOCK',
};
});