אימות באמצעות ריבוי דיירים
במאמר הזה מוסבר איך לאמת משתמשים בסביבת Identity Platform מרובת דיירים.
לפני שמתחילים
מוודאים שהפעלתם את האפשרות 'ריבוי דיירים' בפרויקט והגדרתם את הדיירים. במאמר תחילת העבודה עם ריבוי דיירים מוסבר איך עושים את זה.
בנוסף, צריך להוסיף את Client SDK לאפליקציה:
עוברים לדף Identity Platform במסוף Google Cloud .
עוברים לדף המשתמשים ב-Identity Platformבפינה השמאלית העליונה, לוחצים על פרטי הגדרת האפליקציה.
מעתיקים את הקוד לאפליקציית האינטרנט. לדוגמה:
גרסה 9 לאינטרנט
import { initializeApp } from "firebase/app"; const firebaseConfig = { apiKey: "...", // By default, authDomain is '[YOUR_APP].firebaseapp.com'. // You may replace it with a custom domain. authDomain: '[YOUR_CUSTOM_DOMAIN]' }; const firebaseApp = initializeApp(firebaseConfig);
גרסה 8 לאינטרנט
firebase.initializeApp({ apiKey: '...', // By default, authDomain is '[YOUR_APP].firebaseapp.com'. // You may replace it with a custom domain. authDomain: '[YOUR_CUSTOM_DOMAIN]' });
כניסה באמצעות דיירים
כדי להיכנס לדייר, צריך להעביר את מזהה הדייר לאובייקט auth.
הערה: הערך של tenantId לא נשמר כשמבצעים טעינה מחדש של הדף.
גרסה 9 לאינטרנט
import { getAuth } from "firebase/auth"; const auth = getAuth(); const tenantId = "TENANT_ID1"; auth.tenantId = tenantId;
גרסה 8 לאינטרנט
const tenantId = "TENANT_ID1"; firebase.auth().tenantId = tenantId;
כל בקשות הכניסה העתידיות ממופע auth הזה יכללו את מזהה הדייר (TENANT_ID1 בדוגמה הקודמת) עד שתשנו או תאפסו את מזהה הדייר.
אתם יכולים לעבוד עם כמה דיירים באמצעות מופע auth אחד או כמה מופעים.
כדי להשתמש במופע auth יחיד, משנים את המאפיין tenantId בכל פעם שרוצים לעבור בין דיירים. כדי לחזור להגדרת ספקי IdP ברמת הפרויקט, מגדירים את tenantId ל-null:
גרסה 9 לאינטרנט
// One Auth instance // Switch to tenant1 auth.tenantId = "TENANT_ID1"; // Switch to tenant2 auth.tenantId = "TENANT_ID2"; // Switch back to project level IdPs auth.tenantId = null;
גרסה 8 לאינטרנט
// One Auth instance // Switch to tenant1 firebase.auth().tenantId = "TENANT_ID1"; // Switch to tenant2 firebase.auth().tenantId = "TENANT_ID2"; // Switch back to project level IdPs firebase.auth().tenantId = null;
כדי להשתמש בכמה מופעים, צריך ליצור מופע auth חדש לכל דייר ולהקצות להם מזהים שונים:
גרסה 9 לאינטרנט
// Multiple Auth instances import { initializeApp } from "firebase/app"; import { getAuth } from "firebase/auth"; const firebaseApp1 = initializeApp(firebaseConfig1, 'app1_for_tenantId1'); const firebaseApp2 = initializeApp(firebaseConfig2, 'app2_for_tenantId2'); const auth1 = getAuth(firebaseApp1); const auth2 = getAuth(firebaseApp2); auth1.tenantId = "TENANT_ID1"; auth2.tenantId = "TENANT_ID2";
גרסה 8 לאינטרנט
// Multiple Auth instances firebase.initializeApp(config, 'app1_for_tenantId1'); firebase.initializeApp(config, 'app2_for_tenantId2'); const auth1 = firebase.app('app1').auth(); const auth2 = firebase.app('app2').auth(); auth1.tenantId = "TENANT_ID1"; auth2.tenantId = "TENANT_ID2";
אחרי הכניסה באמצעות דייר, יוחזר משתמש דייר עם user.tenantId שמוגדר לדייר הזה. שימו לב: אם תפעילו את tenantId במופע auth מאוחר יותר, הנכס currentUser לא ישתנה, והוא עדיין יצביע על אותו משתמש כמו הדייר הקודם.
גרסה 9 לאינטרנט
import { signInWithEmailAndPassword, onAuthStateChanged } from "firebase/auth"; // Switch to TENANT_ID1 auth.tenantId = 'TENANT_ID1'; // Sign in with tenant signInWithEmailAndPassword(auth, email, password) .then((userCredential) => { // User is signed in. const user = userCredential.user; // user.tenantId is set to 'TENANT_ID1'. // Switch to 'TENANT_ID2'. auth.tenantId = 'TENANT_ID2'; // auth.currentUser still points to the user. // auth.currentUser.tenantId is 'TENANT_ID1'. }); // You could also get the current user from Auth state observer. onAuthStateChanged(auth, (user) => { if (user) { // User is signed in. // user.tenantId is set to 'TENANT_ID1'. } else { // No user is signed in. } });
גרסה 8 לאינטרנט
// Switch to TENANT_ID1 firebase.auth().tenantId = 'TENANT_ID1'; // Sign in with tenant firebase.auth().signInWithEmailAndPassword(email, password) .then((result) => { const user = result.user; // user.tenantId is set to 'TENANT_ID1'. // Switch to 'TENANT_ID2'. firebase.auth().tenantId = 'TENANT_ID2'; // firebase.auth().currentUser still point to the user. // firebase.auth().currentUser.tenantId is 'TENANT_ID1'. }); // You could also get the current user from Auth state observer. firebase.auth().onAuthStateChanged((user) => { if (user) { // User is signed in. // user.tenantId is set to 'TENANT_ID1'. } else { // No user is signed in. } });
חשבונות עם אימייל וסיסמה
בדוגמה הבאה אפשר לראות איך רושמים משתמש חדש:
גרסה 9 לאינטרנט
import { createUserWithEmailAndPassword } from "firebase/auth"; auth.tenantId = 'TENANT_ID'; createUserWithEmailAndPassword(auth, email, password) .then((userCredential) => { // User is signed in. // userCredential.user.tenantId is 'TENANT_ID'. }).catch((error) => { // Handle / display error. // ... });
גרסה 8 לאינטרנט
firebase.auth().tenantId = 'TENANT_ID'; firebase.auth().createUserWithEmailAndPassword(email, password) .then((result) => { // result.user.tenantId is 'TENANT_ID'. }).catch((error) => { // Handle error. });
כדי להוסיף משתמש קיים:
גרסה 9 לאינטרנט
import { signInWithEmailAndPassword } from "firebase/auth"; auth.tenantId = 'TENANT_ID'; signInWithEmailAndPassword(auth, email, password) .then((userCredential) => { // User is signed in. // userCredential.user.tenantId is 'TENANT_ID'. }).catch((error) => { // Handle / display error. // ... });
גרסה 8 לאינטרנט
firebase.auth().tenantId = 'TENANT_ID'; firebase.auth().signInWithEmailAndPassword(email, password) .then((result) => { // result.user.tenantId is 'TENANT_ID'. }).catch((error) => { // Handle error. });
SAML
כדי להיכנס באמצעות ספק SAML, יוצרים מופע SAMLAuthProvider עם מזהה הספק מ Google Cloud המסוף:
גרסה 9 לאינטרנט
import { SAMLAuthProvider } from "firebase/auth"; const provider = new SAMLAuthProvider("saml.myProvider");
גרסה 8 לאינטרנט
const provider = new firebase.auth.SAMLAuthProvider('saml.myProvider');
אחר כך אפשר להשתמש בחלון קופץ או בתהליך הפניה אוטומטית כדי להיכנס לספק SAML.
חלון קופץ
גרסה 9 לאינטרנט
import { signInWithPopup } from "firebase/auth"; // Switch to TENANT_ID1. auth.tenantId = 'TENANT_ID1'; // Sign-in with popup. signInWithPopup(auth, provider) .then((userCredential) => { // User is signed in. const user = userCredential.user; // user.tenantId is set to 'TENANT_ID1'. // Provider data available from the result.user.getIdToken() // or from result.user.providerData }) .catch((error) => { // Handle / display error. // ... });
גרסה 8 לאינטרנט
// Switch to TENANT_ID1. firebase.auth().tenantId = 'TENANT_ID1'; // Sign-in with popup. firebase.auth().signInWithPopup(provider) .then((result) => { // User is signed in. // tenant ID is available in result.user.tenantId. // Identity provider data is available in result.additionalUserInfo.profile. }) .catch((error) => { // Handle error. });
הפניה לכתובת URL אחרת
גרסה 9 לאינטרנט
import { signInWithRedirect, getRedirectResult } from "firebase/auth"; // Switch to TENANT_ID1. auth.tenantId = 'TENANT_ID1'; // Sign-in with redirect. signInWithRedirect(auth, provider); // After the user completes sign-in and returns to the app, you can get // the sign-in result by calling getRedirectResult. However, if they sign out // and sign in again with an IdP, no tenant is used. getRedirectResult(auth) .then((result) => { // User is signed in. // The tenant ID available in result.user.tenantId. // Provider data available from the result.user.getIdToken() // or from result.user.providerData }) .catch((error) => { // Handle / display error. // ... });
גרסה 8 לאינטרנט
// Switch to TENANT_ID1. firebase.auth().tenantId = 'TENANT_ID1'; // Sign-in with redirect. firebase.auth().signInWithRedirect(provider); // After the user completes sign-in and returns to the app, you can get // the sign-in result by calling getRedirectResult. However, if they sign out // and sign in again with an IdP, no tenant is used. firebase.auth().getRedirectResult() .then((result) => { // User is signed in. // The tenant ID available in result.user.tenantId. // Identity provider data is available in result.additionalUserInfo.profile. }) .catch((error) => { // Handle error. });
בשני המקרים, חשוב להגדיר את מזהה הדייר הנכון במופע auth.
קישור לאימייל
כדי להתחיל את תהליך האימות, מציגים למשתמש ממשק שבו הוא מתבקש לספק את כתובת האימייל שלו, ואז קוראים ל-sendSignInLinkToEmail כדי לשלוח לו קישור לאימות. חשוב להגדיר את מזהה הדייר הנכון במופע auth
לפני שליחת האימייל.
גרסה 9 לאינטרנט
import { sendSignInLinkToEmail } from "firebase/auth"; // Switch to TENANT_ID1 auth.tenantId = 'TENANT_ID1'; sendSignInLinkToEmail(auth, email, actionCodeSettings) .then(() => { // The link was successfully sent. Inform the user. // Save the email locally so you don't need to ask the user for it again // if they open the link on the same device. window.localStorage.setItem('emailForSignIn', email); }) .catch((error) => { // Handle / display error. // ... });
גרסה 8 לאינטרנט
// Switch to TENANT_ID1 firebase.auth().tenantId = 'TENANT_ID1'; firebase.auth().sendSignInLinkToEmail(email, actionCodeSettings) .then(() => { // The link was successfully sent. Inform the user. // Save the email locally so you don't need to ask the user for it again // if they open the link on the same device. window.localStorage.setItem('emailForSignIn', email); }) .catch((error) => { // Some error occurred, you can inspect the code: error.code });
כדי להשלים את הכניסה לדף הנחיתה, קודם צריך לנתח את מזהה הדייר מקישור האימייל ולהגדיר אותו במופע auth. לאחר מכן קוראים לפונקציה signInWithEmailLink
עם כתובת האימייל של המשתמש והקישור בפועל לאימייל שמכיל את הקוד החד-פעמי.
גרסה 9 לאינטרנט
import { isSignInWithEmailLink, parseActionCodeURL, signInWithEmailLink } from "firebase/auth"; if (isSignInWithEmailLink(auth, window.location.href)) { const actionCodeUrl = parseActionCodeURL(window.location.href); if (actionCodeUrl.tenantId) { auth.tenantId = actionCodeUrl.tenantId; } let email = window.localStorage.getItem('emailForSignIn'); if (!email) { // User opened the link on a different device. To prevent session fixation // attacks, ask the user to provide the associated email again. For example: email = window.prompt('Please provide your email for confirmation'); } // The client SDK will parse the code from the link for you. signInWithEmailLink(auth, email, window.location.href) .then((result) => { // User is signed in. // tenant ID available in result.user.tenantId. // Clear email from storage. window.localStorage.removeItem('emailForSignIn'); }); }
גרסה 8 לאינטרנט
if (firebase.auth().isSignInWithEmailLink(window.location.href)) { const actionCodeUrl = firebase.auth.ActionCodeURL.parseLink(window.location.href); if (actionCodeUrl.tenantId) { firebase.auth().tenantId = actionCodeUrl.tenantId; } let email = window.localStorage.getItem('emailForSignIn'); if (!email) { // User opened the link on a different device. To prevent session fixation // attacks, ask the user to provide the associated email again. For example: email = window.prompt('Please provide your email for confirmation'); } firebase.auth().signInWithEmailLink(email, window.location.href) .then((result) => { // User is signed in. // tenant ID available in result.user.tenantId. }); }
יצירת טוקנים בהתאמה אישית
יצירת טוקן מותאם אישית שמתאים לריבוי דיירים זהה ליצירת טוקן מותאם אישית רגיל. כל עוד מזהה הדייר הנכון מוגדר במופע auth, הצהרת tenant_id ברמה העליונה תתווסף ל-JWT שיתקבל.
הוראות מפורטות ליצירה ולשימוש בטוקנים מותאמים אישית זמינות במאמר יצירת טוקנים מותאמים אישית.
בדוגמה הבאה אפשר לראות איך יוצרים אסימון בהתאמה אישית באמצעות SDK לאדמינים:
גרסה 9 לאינטרנט
// Ensure you're using a tenant-aware auth instance const tenantManager = admin.auth().tenantManager(); const tenantAuth = tenantManager.authForTenant('TENANT_ID1'); // Create a custom token in the usual manner tenantAuth.createCustomToken(uid) .then((customToken) => { // Send token back to client }) .catch((error) => { console.log('Error creating custom token:', error); });
גרסה 8 לאינטרנט
// Ensure you're using a tenant-aware auth instance const tenantManager = admin.auth().tenantManager(); const tenantAuth = tenantManager.authForTenant('TENANT_ID1'); // Create a custom token in the usual manner tenantAuth.createCustomToken(uid) .then((customToken) => { // Send token back to client }) .catch((error) => { console.log('Error creating custom token:', error); });
הקוד הבא מראה איך מתחברים באמצעות אסימון בהתאמה אישית:
גרסה 9 לאינטרנט
import { signInWithCustomToken } from "firebase/auth"; auth.tenantId = 'TENANT_ID1'; signInWithCustomToken(auth, token) .catch((error) => { // Handle / display error. // ... });
גרסה 8 לאינטרנט
firebase.auth().tenantId = 'TENANT_ID1'; firebase.auth().signInWithCustomToken(token) .catch((error) => { // Handle Errors here. const errorCode = error.code; const errorMessage = error.message; // ... });
שימו לב: אם מזהי הדיירים לא תואמים, השיטה signInWithCustomToken() תיכשל.
קישור פרטי כניסה של משתמשים עם מספר דיירים
אפשר לקשר סוגים אחרים של פרטי כניסה למשתמש קיים עם מספר דיירים. לדוגמה, אם משתמש עבר אימות בעבר באמצעות פלאגין שמתממשק עם שירותים חיצוניים SAML בדייר (tenant), אפשר להוסיף כניסה באמצעות אימייל/סיסמה לחשבון הקיים שלו, כדי שהוא יוכל להשתמש בכל אחת מהשיטות כדי להיכנס לדייר (tenant).
גרסה 9 לאינטרנט
import { signInWithPopup, EmailAuthProvider, linkWithCredential, SAMLAuthProvider, signInWithCredential } from "firebase/auth"; // Switch to TENANT_ID1 auth.tenantId = 'TENANT_ID1'; // Sign-in with popup signInWithPopup(auth, provider) .then((userCredential) => { // Existing user with e.g. SAML provider. const prevUser = userCredential.user; const emailCredential = EmailAuthProvider.credential(email, password); return linkWithCredential(prevUser, emailCredential) .then((linkResult) => { // Sign in with the newly linked credential const linkCredential = SAMLAuthProvider.credentialFromResult(linkResult); return signInWithCredential(auth, linkCredential); }) .then((signInResult) => { // Handle sign in of merged user // ... }); }) .catch((error) => { // Handle / display error. // ... });
גרסה 8 לאינטרנט
// Switch to TENANT_ID1 firebase.auth().tenantId = 'TENANT_ID1'; // Sign-in with popup firebase.auth().signInWithPopup(provider) .then((result) => { // Existing user with e.g. SAML provider. const user = result.user; const emailCredential = firebase.auth.EmailAuthProvider.credential(email, password); return user.linkWithCredential(emailCredential); }) .then((linkResult) => { // The user can sign in with both SAML and email/password now. });
כשמקשרים משתמש קיים עם כמה דיירים או מאמתים אותו מחדש, המערכת מתעלמת מ-auth.tenantId. צריך להשתמש ב-user.tenantId כדי לציין את הדייר שבו רוצים להשתמש. הדבר נכון גם לגבי ממשקי API אחרים לניהול משתמשים, כמו updateProfile
ו-updatePassword.
טיפול בשגיאות account-exists-with-different-credential
אם הפעלתם את ההגדרה קישור חשבונות שמשתמשים באותה כתובת אימייל ב-Google Cloud console, כשמשתמש מנסה להיכנס לספק (למשל SAML) עם כתובת אימייל שכבר קיימת אצל ספק אחר (למשל Google), מוצגת השגיאה auth/account-exists-with-different-credential (יחד עם אובייקט AuthCredential).
כדי לסיים את הכניסה באמצעות הספק הרצוי, המשתמש צריך קודם להיכנס לספק הקיים (Google), ואז לקשר לספק הקודם AuthCredential(SAML).
כדי לטפל בשגיאה הזו, אפשר להשתמש בתהליך קופץ או בתהליך הפניה אוטומטית.
חלון קופץ
גרסה 9 לאינטרנט
import { signInWithPopup, fetchSignInMethodsForEmail, linkWithCredential } from "firebase/auth"; // Step 1. // User tries to sign in to the SAML provider in that tenant. auth.tenantId = 'TENANT_ID'; signInWithPopup(auth, samlProvider) .catch((error) => { // An error happened. if (error.code === 'auth/account-exists-with-different-credential') { // Step 2. // User's email already exists. // The pending SAML credential. const pendingCred = error.credential; // The credential's tenantId if needed: error.tenantId // The provider account's email address. const email = error.customData.email; // Get sign-in methods for this email. fetchSignInMethodsForEmail(email, auth) .then((methods) => { // Step 3. // Ask the user to sign in with existing Google account. if (methods[0] == 'google.com') { signInWithPopup(auth, googleProvider) .then((result) => { // Step 4 // Link the SAML AuthCredential to the existing user. linkWithCredential(result.user, pendingCred) .then((linkResult) => { // SAML account successfully linked to the existing // user. goToApp(); }); }); } }); } });
גרסה 8 לאינטרנט
// Step 1. // User tries to sign in to the SAML provider in that tenant. firebase.auth().tenantId = 'TENANT_ID'; firebase.auth().signInWithPopup(samlProvider) .catch((error) => { // An error happened. if (error.code === 'auth/account-exists-with-different-credential') { // Step 2. // User's email already exists. // The pending SAML credential. const pendingCred = error.credential; // The credential's tenantId if needed: error.tenantId // The provider account's email address. const email = error.email; // Get sign-in methods for this email. firebase.auth().fetchSignInMethodsForEmail(email) .then((methods) => { // Step 3. // Ask the user to sign in with existing Google account. if (methods[0] == 'google.com') { firebase.auth().signInWithPopup(googleProvider) .then((result) => { // Step 4 // Link the SAML AuthCredential to the existing user. result.user.linkWithCredential(pendingCred) .then((linkResult) => { // SAML account successfully linked to the existing // user. goToApp(); }); }); } }); } });
הפניה לכתובת URL אחרת
כשמשתמשים ב-
signInWithRedirect, שגיאותauth/account-exists-with-different-credentialיופיעו ב-getRedirectResultבסיום תהליך ההפניה לכתובת אחרת.אובייקט השגיאה מכיל את המאפיין
error.tenantId. מכיוון שמזהה הדייר במופעauthלא נשמר אחרי ההפניה האוטומטית, צריך להגדיר את מזהה הדייר מאובייקט השגיאה למופעauth.בדוגמה הבאה אפשר לראות איך לטפל בשגיאה:
גרסה 9 לאינטרנט
import { signInWithRedirect, getRedirectResult, fetchSignInMethodsForEmail, linkWithCredential } from "firebase/auth"; // Step 1. // User tries to sign in to SAML provider. auth.tenantId = 'TENANT_ID'; signInWithRedirect(auth, samlProvider); var pendingCred; // Redirect back from SAML IDP. auth.tenantId is null after redirecting. getRedirectResult(auth).catch((error) => { if (error.code === 'auth/account-exists-with-different-credential') { // Step 2. // User's email already exists. const tenantId = error.tenantId; // The pending SAML credential. pendingCred = error.credential; // The provider account's email address. const email = error.customData.email; // Need to set the tenant ID again as the page was reloaded and the // previous setting was reset. auth.tenantId = tenantId; // Get sign-in methods for this email. fetchSignInMethodsForEmail(auth, email) .then((methods) => { // Step 3. // Ask the user to sign in with existing Google account. if (methods[0] == 'google.com') { signInWithRedirect(auth, googleProvider); } }); } }); // Redirect back from Google. auth.tenantId is null after redirecting. getRedirectResult(auth).then((result) => { // Step 4 // Link the SAML AuthCredential to the existing user. // result.user.tenantId is 'TENANT_ID'. linkWithCredential(result.user, pendingCred) .then((linkResult) => { // SAML account successfully linked to the existing // user. goToApp(); }); });
גרסה 8 לאינטרנט
// Step 1. // User tries to sign in to SAML provider. firebase.auth().tenantId = 'TENANT_ID'; firebase.auth().signInWithRedirect(samlProvider); var pendingCred; // Redirect back from SAML IDP. auth.tenantId is null after redirecting. firebase.auth().getRedirectResult().catch((error) => { if (error.code === 'auth/account-exists-with-different-credential') { // Step 2. // User's email already exists. const tenantId = error.tenantId; // The pending SAML credential. pendingCred = error.credential; // The provider account's email address. const email = error.email; // Need to set the tenant ID again as the page was reloaded and the // previous setting was reset. firebase.auth().tenantId = tenantId; // Get sign-in methods for this email. firebase.auth().fetchSignInMethodsForEmail(email) .then((methods) => { // Step 3. // Ask the user to sign in with existing Google account. if (methods[0] == 'google.com') { firebase.auth().signInWithRedirect(googleProvider); } }); } }); // Redirect back from Google. auth.tenantId is null after redirecting. firebase.auth().getRedirectResult().then((result) => { // Step 4 // Link the SAML AuthCredential to the existing user. // result.user.tenantId is 'TENANT_ID'. result.user.linkWithCredential(pendingCred) .then((linkResult) => { // SAML account successfully linked to the existing // user. goToApp(); }); });
השבתה של יצירה ומחיקה של חשבונות משתמשי קצה
יש מצבים שבהם רוצים שאדמינים ייצרו חשבונות משתמשים במקום שהחשבונות ייווצרו באמצעות פעולות של משתמשים. במקרים כאלה, אפשר להשבית את פעולות המשתמשים באמצעות API בארכיטקטורת REST:
curl --location --request PATCH 'https://identitytoolkit.googleapis.com/v2/projects/PROJECT_ID/tenants/TENANT_ID?updateMask=client' \
--header 'Authorization: Bearer AUTH_TOKEN' \
--header 'Content-Type: application/json' \
--data-raw '{
"client": {
"permissions": {
"disabled_user_signup": true,
"disabled_user_deletion": true
}
}
}'
מחליפים את מה שכתוב בשדות הבאים:
-
AUTH_TOKEN: טוקן האימות. -
PROJECT_ID: מזהה הפרויקט. -
TENANT_ID: מזהה הדייר.