Funkcje blokujące umożliwiają wykonanie niestandardowego kodu, który modyfikuje wynik rejestracji użytkownika lub logowania się do aplikacji. Możesz na przykład uniemożliwić uwierzytelnienie użytkownika, jeśli nie spełnia on określonych kryteriów, lub zaktualizować informacje o użytkowniku przed zwróceniem ich do aplikacji klienckiej.
Zanim zaczniesz
Aby korzystać z funkcji blokowania, musisz uaktualnić swój projekt Firebase do Firebase Authentication with Identity Platform. Jeśli jeszcze nie dokonałeś aktualizacji, zrób to najpierw.
Zrozumienie funkcji blokujących
Możesz zarejestrować funkcje blokujące dla dwóch zdarzeń:
beforeCreate
: Wyzwala zanim nowy użytkownik zostanie zapisany w bazie danych Firebase Authentication i zanim token zostanie zwrócony do Twojej aplikacji klienckiej.beforeSignIn
: wyzwala po zweryfikowaniu poświadczeń użytkownika, ale zanim uwierzytelnianie Firebase zwróci token identyfikatora do aplikacji klienckiej. Jeśli Twoja aplikacja korzysta z uwierzytelniania wieloskładnikowego, funkcja uruchamia się, gdy użytkownik zweryfikuje swój drugi składnik. Zauważ, że tworzenie nowego użytkownika wyzwala równieżbeforeSignIn
, opróczbeforeCreate
.
Podczas korzystania z funkcji blokujących należy pamiętać o następujących kwestiach:
Twoja funkcja musi odpowiedzieć w ciągu 7 sekund. Po 7 sekundach uwierzytelnianie Firebase 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 wszelkie błędy, które funkcja może zwrócić.Funkcje mają zastosowanie do wszystkich użytkowników w Twoim projekcie, w tym do użytkowników zawartych w dzierżawie . Uwierzytelnianie Firebase dostarcza informacji o użytkownikach Twojej funkcji, w tym o wszystkich dzierżawcach, do których należą, dzięki czemu możesz odpowiednio zareagować.
Połączenie innego dostawcy tożsamości z kontem powoduje ponowne wyzwolenie wszystkich zarejestrowanych funkcji
beforeSignIn
.Uwierzytelnianie anonimowe i niestandardowe nie uruchamia funkcji blokowania.
Wdróż i zarejestruj funkcję blokującą
Aby wstawić niestandardowy kod do przepływów uwierzytelniania użytkownika, wdróż i zarejestruj funkcje blokujące. Po wdrożeniu i zarejestrowaniu funkcji blokujących kod niestandardowy musi zakończyć się pomyślnie, aby uwierzytelnianie i tworzenie użytkowników powiodło się.
Wdróż funkcję blokującą
Funkcję blokującą wdraża się w taki sam sposób, jak każdą inną. (szczegółowe informacje można znaleźć na stronie Rozpoczęcie pracy z Cloud Functions). W podsumowaniu:
Napisz Cloud Functions, które obsługują zdarzenie
beforeCreate
, zdarzeniebeforeSignIn
lub oba.Na przykład, aby rozpocząć, możesz dodać następujące funkcje no-op do
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 });
W powyższych przykładach pominięto implementację niestandardowej logiki uwierzytelniania. Zapoznaj się z poniższymi sekcjami, aby dowiedzieć się, jak zaimplementować funkcje blokujące i Typowe scenariusze dla konkretnych przykładów.
Wdróż swoje funkcje za pomocą Firebase CLI:
firebase deploy --only functions
Musisz ponownie wdrożyć swoje funkcje za każdym razem, gdy je aktualizujesz.
Zarejestruj funkcję blokującą
Przejdź do strony Ustawienia uwierzytelniania Firebase w konsoli Firebase.
Wybierz zakładkę Funkcje blokujące .
Zarejestruj swoją funkcję blokowania, wybierając ją z menu rozwijanego w sekcji Przed utworzeniem konta (beforeCreate) lub Przed zalogowaniem (beforeSignIn) .
Zapisz zmiany.
Uzyskiwanie informacji o użytkowniku i kontekście
Zdarzenia beforeSignIn
i beforeCreate
udostępniają obiekty User
i EventContext
, które zawierają informacje o logowaniu się użytkownika. Użyj tych wartości w swoim kodzie, aby określić, czy zezwolić na kontynuację operacji.
Aby zapoznać się z listą właściwości dostępnych w obiekcie User
, zapoznaj się z dokumentacją interfejsu API UserRecord
.
Obiekt EventContext
zawiera następujące właściwości:
Nazwa | Opis | Przykład |
---|---|---|
locale | Ustawienia regionalne aplikacji. Ustawienia regionalne można ustawić za pomocą zestawu SDK klienta lub przekazując nagłówek ustawień regionalnych w interfejsie API REST. | fr lub sv-SE |
ipAddress | Adres IP urządzenia, z którego użytkownik końcowy się rejestruje lub loguje. | 114.14.200.1 |
userAgent | Agent użytkownika wyzwalający funkcję blokowania. | Mozilla/5.0 (X11; Linux x86_64) |
eventId | Unikalny identyfikator wydarzenia. | rWsyPtolplG2TBFoOkkgyg |
eventType | Typ zdarzenia. Zawiera informacje na temat nazwy zdarzenia, takiej jak beforeSignIn lub beforeCreate , oraz powiązanej metody logowania, takiej jak Google lub adres e-mail/hasło. | providers/cloud.auth/eventTypes/user.beforeSignIn:password |
authType | Zawsze USER . | USER |
resource | Projekt lub dzierżawa uwierzytelniania Firebase. | projects/ project-id /tenants/ tenant-id |
timestamp | Czas wyzwolenia zdarzenia, sformatowany jako ciąg znaków 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 poświadczeniach użytkownika. | AuthCredential |
Blokowanie rejestracji lub logowania
Aby zablokować próbę rejestracji lub logowania, zgłoś błąd HttpsError
w swojej funkcji. Na przykład:
Node.js
throw new functions.auth.HttpsError('permission-denied');
Poniższa tabela zawiera listę błędów, które możesz zgłosić, wraz z ich domyślnym komunikatem o błędzie:
Nazwa | Kod | Wiadomość |
---|---|---|
invalid-argument | 400 | Klient podał nieprawidłowy argument. |
failed-precondition | 400 | Żądanie nie może zostać wykonane w bieżącym stanie systemu. |
out-of-range | 400 | Klient określił 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 znaleziono określonego zasobu. |
aborted | 409 | Konflikt współbieżności, taki jak konflikt odczytu, modyfikacji i zapisu. |
already-exists | 409 | Zasób, który klient próbował utworzyć, już istnieje. |
resource-exhausted | 429 | Brak limitu zasobów lub osiągnięcie limitu szybkości. |
cancelled | 499 | Żądanie anulowane przez klienta. |
data-loss | 500 | Nieodwracalna utrata lub uszkodzenie danych. |
unknown | 500 | Nieznany błąd serwera. |
internal | 500 | Wewnętrzny błąd serwera. |
not-implemented | 501 | Metoda API nie zaimplementowana przez serwer. |
unavailable | 503 | Serwis niedostępny. |
deadline-exceeded | 504 | Przekroczono termin składania wniosków. |
Możesz także określić niestandardowy komunikat o błędzie:
Node.js
throw new functions.auth.HttpsError('permission-denied', 'Unauthorized request origin!');
Poniższy przykład pokazuje, jak zablokować użytkownikom spoza określonej domeny możliwość rejestracji w Twojej 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 używasz komunikatu domyślnego, czy niestandardowego, Cloud Functions opakowuje błąd i zwraca go klientowi jako błąd wewnętrzny. Na przykład:
throw new functions.auth.HttpsError('invalid-argument', `Unauthorized email user@evil.com}`);
Twoja aplikacja powinna wychwycić błąd i odpowiednio go obsłużyć. Na 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ć próbę rejestracji lub logowania, możesz zezwolić na kontynuację operacji, ale zmodyfikować obiekt User
, który jest zapisywany w bazie danych Firebase Authentication i zwracany do klienta.
Aby zmodyfikować użytkownika, zwróć obiekt z procedury obsługi zdarzeń zawierający pola do modyfikacji. Możesz modyfikować następujące pola:
-
displayName
-
disabled
-
emailVerified
-
photoURL
-
customClaims
-
sessionClaims
(tylkobeforeSignIn
)
Z wyjątkiem sessionClaims
, wszystkie zmodyfikowane pola są zapisywane w bazie danych Firebase Authentication, co oznacza, że są uwzględniane w tokenie odpowiedzi i utrzymują się między sesjami użytkownika.
Poniższy 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 procedurę obsługi zdarzeń zarówno dla beforeCreate
, jak i beforeSignIn
, pamiętaj, że beforeSignIn
jest wykonywany po beforeCreate
. Pola użytkownika zaktualizowane w beforeCreate
są widoczne w beforeSignIn
. Jeśli ustawisz pole inne niż sessionClaims
w obu procedurach obsługi zdarzeń, wartość ustawiona w beforeSignIn
zastępuje wartość ustawioną w beforeCreate
. Tylko w przypadku sessionClaims
są one propagowane do oświadczeń tokenów bieżącej sesji, ale nie są utrwalane ani przechowywane w bazie danych.
Na przykład, jeśli ustawione są jakiekolwiek sessionClaims
, beforeSignIn
zwróci je z wszelkimi roszczeniami beforeCreate
i zostaną one scalone. Gdy zostaną scalone, jeśli klucz sessionClaims
pasuje do klucza w customClaims
, pasujące customClaims
zostaną zastąpione w oświadczeniach tokenu przez klucz sessionClaims
. Jednak nadpisany klucz customClaims
będzie nadal zachowywany w bazie danych na potrzeby przyszłych żądań.
Obsługiwane poświadczenia i dane OAuth
Poświadczenia i dane OAuth można przekazywać funkcjom blokowania od różnych dostawców tożsamości. W poniższej tabeli przedstawiono, jakie poświadczenia i dane są obsługiwane przez każdego dostawcę tożsamości:
Dostawca tożsamości | Token identyfikacyjny | Token dostępu | Data ważności | Sekret Tokena | Odśwież token | Roszczenia dotyczące logowania |
---|---|---|---|---|---|---|
Tak | Tak | Tak | NIE | Tak | NIE | |
NIE | Tak | Tak | NIE | NIE | NIE | |
Świergot | NIE | Tak | NIE | Tak | NIE | NIE |
GitHub | NIE | Tak | NIE | NIE | NIE | NIE |
Microsoftu | Tak | Tak | Tak | NIE | Tak | NIE |
NIE | Tak | Tak | NIE | NIE | NIE | |
Wieśniak | Tak | Tak | Tak | NIE | Tak | NIE |
Jabłko | Tak | Tak | Tak | NIE | Tak | NIE |
SAML | NIE | NIE | NIE | NIE | NIE | Tak |
OIDC | Tak | Tak | Tak | NIE | Tak | Tak |
Odśwież tokeny
Aby użyć tokena odświeżania w funkcji blokującej, musisz najpierw zaznaczyć pole wyboru na stronie Funkcje blokujące w konsoli Firebase.
Żadni dostawcy tożsamości nie zwracają tokenów odświeżania podczas bezpośredniego logowania przy użyciu poświadczeń OAuth, takich jak token identyfikatora lub token dostępu. W tej sytuacji to samo poświadczenie OAuth po stronie klienta zostanie przekazane do funkcji blokującej.
W poniższych sekcjach opisano poszczególne typy dostawców tożsamości oraz ich obsługiwane poświadczenia i dane.
Ogólni dostawcy OIDC
Gdy użytkownik zaloguje się przy użyciu ogólnego dostawcy OIDC, zostaną przekazane następujące poświadczenia:
- Token identyfikatora : podany, jeśli wybrano przepływ
id_token
. - Token dostępu : dostarczany, jeśli wybrano przepływ kodu. Należy pamiętać, że przepływ kodu jest obecnie obsługiwany tylko przez interfejs API REST.
- Odśwież token : podany, jeśli wybrano zakres
offline_access
.
Przykład:
const provider = new firebase.auth.OAuthProvider('oidc.my-provider');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);
Gdy użytkownik zaloguje się w Google, zostaną przekazane następujące dane uwierzytelniające:
- token identyfikacyjny
- Token dostępu
- Token odświeżania : dostarczany tylko wtedy, gdy wymagane są następujące parametry niestandardowe:
-
access_type=offline
-
prompt=consent
, jeśli użytkownik wcześniej wyraził zgodę i nie zażądano nowego zakresu
-
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ę za pomocą Facebooka, zostaną przekazane następujące dane uwierzytelniające:
- 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 tokenów dostępu obsługiwanych przez Facebook i o tym, jak możesz je wymienić na tokeny o długim okresie ważności .
GitHub
Gdy użytkownik zaloguje się w usłudze GitHub, zostaną przekazane następujące dane uwierzytelniające:
- Token dostępu : nie wygasa, chyba że zostanie odwołany.
Microsoftu
Gdy użytkownik zaloguje się w firmie Microsoft, zostaną przekazane następujące poświadczenia:
- token identyfikacyjny
- Token dostępu
- Token odświeżania : przekazywany do funkcji blokującej, jeśli wybrano zakres
offline_access
.
Przykład:
const provider = new firebase.auth.OAuthProvider('microsoft.com');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);
Wieśniak
Gdy użytkownik loguje się za pomocą Yahoo, następujące poświadczenia zostaną przekazane bez żadnych niestandardowych parametrów ani zakresów:
- token identyfikacyjny
- Token dostępu
- Odśwież token
Gdy użytkownik zaloguje się na LinkedIn, zostaną przekazane następujące dane uwierzytelniające:
- Token dostępu
Jabłko
Gdy użytkownik zaloguje się w Apple, następujące poświadczenia zostaną przekazane bez żadnych niestandardowych parametrów ani zakresów:
- token identyfikacyjny
- Token dostępu
- Odśwież token
Typowe scenariusze
Poniższe przykłady ilustrują niektóre typowe przypadki użycia funkcji blokujących:
Zezwalanie tylko na rejestrację z określonej domeny
Poniższy przykład pokazuje, jak uniemożliwić użytkownikom, którzy nie należą do domeny example.com
, rejestrowanie się w Twojej 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}"`);
}
});
Blokowanie rejestracji użytkowników z niezweryfikowanymi adresami e-mail
Poniższy przykład pokazuje, jak uniemożliwić użytkownikom z niezweryfikowanymi adresami e-mail rejestrację w Twojej 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 weryfikacji adresu e-mail przy rejestracji
Poniższy przykład pokazuje, jak wymagać od użytkownika weryfikacji adresu e-mail po rejestracji:
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 niektórych adresów e-mail dostawców tożsamości jako zweryfikowanych
Poniższy przykład pokazuje, jak traktować wiadomości e-mail użytkowników od określonych dostawców tożsamości jako zweryfikowane:
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
Poniższy przykład blokuje 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 oświadczeń niestandardowych i dotyczących sesji
Poniższy przykład pokazuje, jak ustawić oświadczenia niestandardowe i dotyczące 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
Możesz zapobiec kradzieży tokena, śledząc adres IP, z którego loguje się użytkownik, i porównując go z adresem IP przy kolejnych żądaniach. Jeśli żądanie wydaje się podejrzane — na przykład adresy IP pochodzą z różnych regionów geograficznych — możesz poprosić użytkownika o ponowne zalogowanie.
Użyj oświadczeń dotyczących sesji, aby śledzić adres IP, za pomocą 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 uwierzytelnienia za pomocą uwierzytelniania Firebase, porównaj adres IP w żądaniu z adresem IP użytym do zalogowania się:
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!' }) }); } }); });
Przeglądanie zdjęć użytkowników
Poniższy przykład pokazuje, jak oczyścić 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,
};
}
});
});
Aby dowiedzieć się więcej o wykrywaniu i oczyszczaniu obrazów, zapoznaj się z dokumentacją Cloud Vision .
Uzyskiwanie dostępu do poświadczeń OAuth dostawcy tożsamości użytkownika
Poniższy przykład pokazuje, jak uzyskać token odświeżania dla użytkownika, który zalogował się w Google, i używać go do wywoływania interfejsów API Kalendarza Google. Token odświeżania jest przechowywany w celu uzyskania dostępu w trybie 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();
});
});
})
}
});