יצירת דף כניסה בהתאמה אישית

במאמר הזה מוסבר איך ליצור דף אימות משלכם באמצעות זהויות חיצוניות ו-IAP. אם אתם יוצרים את הדף הזה בעצמכם, אתם מקבלים שליטה מלאה בתהליך האימות ובחוויית המשתמש.

אם אתם לא צריכים להתאים אישית את ממשק המשתמש באופן מלא, אתם יכולים לאפשר ל-IAP לארח בשבילכם דף כניסה, או להשתמש ב-FirebaseUI כדי ליהנות מחוויה יעילה יותר.

סקירה כללית

כדי ליצור דף אימות משלכם, פועלים לפי השלבים הבאים:

  1. הפעלת זהויות חיצוניות. במהלך ההגדרה, בוחרים באפשרות I'll provide my own UI option (אני אציין אפשרות משלי לממשק המשתמש).
  2. מתקינים את הספרייה gcip-iap.
  3. מגדירים את ממשק המשתמש באמצעות הטמעה של הממשק AuthenticationHandler. דף האימות צריך לטפל בתרחישים הבאים:
    • בחירת דייר
    • הרשאת משתמש
    • כניסה של משתמשים
    • טיפול בשגיאות
  4. אופציונלי: התאמה אישית של דף האימות עם תכונות נוספות, כמו סרגלי התקדמות, דפי יציאה ועיבוד משתמשים.
  5. בדיקת ממשק המשתמש

התקנת הספרייה gcip-iap

כדי להתקין את הספרייה gcip-iap, מריצים את הפקודה הבאה:

npm install gcip-iap --save

מודול ה-gcip-iap NPM מבצע הפשטה של התקשורת בין האפליקציה, IAP ו-Identity Platform. כך אפשר להתאים אישית את כל תהליך האימות בלי לנהל את חילופי הנתונים הבסיסיים בין ממשק המשתמש לבין IAP.

משתמשים בהצהרות הייבוא הנכונות לגרסת ה-SDK:

‫gcip-iap v0.1.4 או גרסאות קודמות

// Import Firebase/GCIP dependencies. These are installed on npm install.
import * as firebase from 'firebase/app';
import 'firebase/auth';
// Import GCIP/IAP module.
import * as ciap from 'gcip-iap';

‫gcip-iap v1.0.0 to v1.1.0

החל מגרסה v1.0.0, ‏ gcip-iap דורש את התלות של firebase בגרסה 9 ואילך. אם אתם עוברים לגרסה gcip-iap v1.0.0 ומעלה, אתם צריכים לבצע את הפעולות הבאות:

  • מעדכנים את גרסת firebase בקובץ package.json לגרסה v9.6.0 ומעלה.
  • מעדכנים את הצהרות הייבוא firebase באופן הבא:
// Import Firebase modules.
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
// Import the gcip-iap module.
import * as ciap from 'gcip-iap';

אין צורך לבצע שינויים נוספים בקוד.

gcip-iap v2.0.0

החל מגרסה v2.0.0, ‏ gcip-iap מחייב שכתוב של אפליקציית ממשק המשתמש המותאם אישית באמצעות פורמט ה-SDK המודולרי. אם אתם מעבירים לגרסה gcip-iap v2.0.0 ומעלה, צריך לבצע את הפעולות הבאות:

  • מעדכנים את גרסת firebase בקובץ package.json לגרסה v9.8.3 ומעלה.
  • מעדכנים את הצהרות הייבוא firebase באופן הבא:
  // Import Firebase modules.
  import { initializeApp } from 'firebase/app';
  import { getAuth, GoogleAuthProvider } 'firebase/auth';
  // Import the gcip-iap module.
  import * as ciap from 'gcip-iap';

הגדרת ממשק המשתמש

כדי להגדיר את ממשק המשתמש, יוצרים מחלקה בהתאמה אישית שמטמיעה את הממשק AuthenticationHandler:

interface AuthenticationHandler {
  languageCode?: string | null;
  getAuth(apiKey: string, tenantId: string | null): FirebaseAuth;
  startSignIn(auth: FirebaseAuth, match?: SelectedTenantInfo): Promise<UserCredential>;
  selectTenant?(projectConfig: ProjectConfig, tenantIds: string[]): Promise<SelectedTenantInfo>;
  completeSignOut(): Promise<void>;
  processUser?(user: User): Promise<User>;
  showProgressBar?(): void;
  hideProgressBar?(): void;
  handleError?(error: Error | CIAPError): void;
}

במהלך האימות, הספרייה קוראת באופן אוטומטי לשיטות של AuthenticationHandler.

בחירת דיירים (tenants)

כדי לבחור דייר, מטמיעים את selectTenant(). אפשר להטמיע את השיטה הזו כדי לבחור דייר באופן פרוגרמטי, או להציג ממשק משתמש כדי שהמשתמש יוכל לבחור דייר בעצמו.

בכל מקרה, הספרייה משתמשת באובייקט SelectedTenantInfo שמוחזר כדי להשלים את תהליך האימות. הוא מכיל את המזהה של הדייר שנבחר, את המזהים של הספקים וכתובת האימייל שהמשתמש הזין.

אם יש לכם כמה דיירים בפרויקט, אתם צריכים לבחור אחד מהם לפני שתוכלו לאמת משתמש. אם יש לכם רק דייר אחד, או אם אתם משתמשים באימות ברמת הפרויקט, אתם לא צריכים להטמיע את selectTenant().

‫IAP תומך באותם ספקים כמו Identity Platform, למשל:

  • כתובת אימייל וסיסמה
  • ‫OAuth (Google,‏ Facebook,‏ Twitter,‏ GitHub,‏ Microsoft וכו')
  • SAML
  • OIDC
  • מספר טלפון
  • בהתאמה אישית
  • אנונימי

אין תמיכה בסוגי אימות של מספר טלפון, אימות מותאם אישית ואימות אנונימי במערכות מרובות דיירים.

בחירת דיירים באופן פרוגרמטי

כדי לבחור דייר באופן פרוגרמטי, צריך להשתמש בהקשר הנוכחי. המחלקות Authentication מכילות את getOriginalURL() שמחזירה את כתובת ה-URL שהמשתמש ניסה לגשת אליה לפני האימות.

כדי למצוא התאמה מתוך רשימה של דיירים משויכים:

// Select provider programmatically.
selectTenant(projectConfig, tenantIds) {
  return new Promise((resolve, reject) => {
    // Show UI to select the tenant.
    auth.getOriginalURL()
      .then((originalUrl) => {
        resolve({
          tenantId: getMatchingTenantBasedOnVisitedUrl(originalUrl),
          // If associated provider IDs can also be determined,
          // populate this list.
          providerIds: [],
        });
      })
      .catch(reject);
  });
}

מתן אפשרות למשתמשים לבחור דיירים

כדי לאפשר למשתמש לבחור דייר, מציגים רשימה של דיירים ומבקשים מהמשתמש לבחור אחד מהם, או מבקשים ממנו להזין את כתובת האימייל שלו ואז מאתרים התאמה על סמך הדומיין:

// Select provider by showing UI.
selectTenant(projectConfig, tenantIds) {
  return new Promise((resolve, reject) => {
    // Show UI to select the tenant.
    renderSelectTenant(
        tenantIds,
        // On tenant selection.
        (selectedTenantId) => {
          resolve({
            tenantId: selectedTenantId,
            // If associated provider IDs can also be determined,
            // populate this list.
            providerIds: [],
            // If email is available, populate this field too.
            email: undefined,
          });
        });
  });
}

אימות משתמשים

אחרי שבוחרים ספק, מטמיעים את getAuth() כדי להחזיר מופע של Auth, שמתאים למפתח ה-API ולמזהה הדייר שסופקו. אם לא מצוין מזהה דייר, משתמשים בספקי זהויות ברמת הפרויקט.

getAuth() עוקב אחרי המקום שבו מאוחסן המשתמש שתואם להגדרה שצוינה. בנוסף, אפשר לרענן בשקט את אסימון ה-ID של משתמש שאומת בעבר ב-Identity Platform, בלי שהמשתמש יצטרך להזין מחדש את פרטי הכניסה שלו.

אם אתם משתמשים בכמה משאבי IAP עם דיירים שונים, מומלץ להשתמש במופע אימות ייחודי לכל משאב. כך אפשר להשתמש באותו דף אימות לכמה משאבים עם הגדרות שונות. בנוסף, כמה משתמשים יכולים להיכנס בו-זמנית בלי שהמשתמש הקודם יתנתק.

דוגמה להטמעה של getAuth():

gcip-iap v1.0.0

getAuth(apiKey, tenantId) {
  let auth = null;
  // Make sure the expected API key is being used.
  if (apiKey !== expectedApiKey) {
    throw new Error('Invalid project!');
  }
  try {
    auth = firebase.app(tenantId || undefined).auth();
    // Tenant ID should be already set on initialization below.
  } catch (e) {
    // Use different App names for every tenant so that
    // multiple users can be signed in at the same time (one per tenant).
    const app = firebase.initializeApp(this.config, tenantId || '[DEFAULT]');
    auth = app.auth();
    // Set the tenant ID on the Auth instance.
    auth.tenantId = tenantId || null;
  }
  return auth;
}

gcip-iap v2.0.0

import {initializeApp, getApp} from 'firebase/app';
import {getAuth} from 'firebase/auth';

getAuth(apiKey, tenantId) {
  let auth = null;
  // Make sure the expected API key is being used.
  if (apiKey !== expectedApiKey) {
    throw new Error('Invalid project!');
  }
  try {
    auth = getAuth(getApp(tenantId || undefined));
    // Tenant ID should be already set on initialization below.
  } catch (e) {
    // Use different App names for every tenant so that
    // multiple users can be signed in at the same time (one per tenant).
    const app = initializeApp(this.config, tenantId || '[DEFAULT]');
    auth = getAuth(app);
    // Set the tenant ID on the Auth instance.
    auth.tenantId = tenantId || null;
  }
  return auth;
}

כניסה של משתמשים לחשבון

כדי לטפל בכניסה לחשבון, צריך להטמיע את startSignIn(), להציג ממשק משתמש לאימות המשתמש ואז להחזיר את UserCredential עבור המשתמש שנכנס לחשבון בסיום.

בסביבה מרובת דיירים, אפשר לקבוע את שיטות האימות הזמינות מתוך SelectedTenantInfo, אם הוא סופק. המשתנה הזה מכיל את אותם פרטים שמוחזרים על ידי selectTenant().

בדוגמה הבאה מוצגת הטמעה של startSignIn() עבור משתמש קיים עם כתובת אימייל וסיסמה:

gcip-iap v1.0.0

startSignIn(auth, selectedTenantInfo) {
  return new Promise((resolve, reject) => {
    // Show the UI to sign-in or sign-up a user.
    $('#sign-in-form').on('submit', (e) => {
      const email = $('#email').val();
      const password = $('#password').val();
      // Example: Ask the user for an email and password.
      // Note: The method of sign in may have already been determined from the
      // selectedTenantInfo object.
      auth.signInWithEmailAndPassword(email, password)
        .then((userCredential) => {
          resolve(userCredential);
        })
        .catch((error) => {
          // Show the error message.
        });
    });
  });
}

gcip-iap v2.0.0

import {signInWithEmailAndPassword} from 'firebase/auth';

startSignIn(auth, selectedTenantInfo) {
  return new Promise((resolve, reject) => {
    // Show the UI to sign-in or sign-up a user.
    $('#sign-in-form').on('submit', (e) => {
      const email = $('#email').val();
      const password = $('#password').val();
      // Example: Ask the user for an email and password.
      // Note: The method of sign in may have already been determined from the
      // selectedTenantInfo object.
        signInWithEmailAndPassword(auth, email, password)
        .then((userCredential) => {
          resolve(userCredential);
        })
        .catch((error) => {
          // Show the error message.
        });
    });
  });
}

אפשר גם להשתמש בחלון קופץ או בהפניה כדי להיכנס לחשבון משתמש באמצעות ספק מאוחד, כמו SAML או OIDC:

gcip-iap v1.0.0

startSignIn(auth, selectedTenantInfo) {
  // Show the UI to sign-in or sign-up a user.
  return new Promise((resolve, reject) => {
    // Provide the user multiple buttons to sign-in.
    // For example sign-in with popup using a SAML provider.
    // Note: The method of sign in may have already been determined from the
    // selectedTenantInfo object.
    const provider = new firebase.auth.SAMLAuthProvider('saml.myProvider');
    auth.signInWithPopup(provider)
      .then((userCredential) => {
        resolve(userCredential);
      })
      .catch((error) => {
        // Show the error message.
      });
    // Using redirect flow. When the page redirects back and sign-in completes,
    // ciap will detect the result and complete sign-in without any additional
    // action.
    auth.signInWithRedirect(provider);
  });
}

gcip-iap v2.0.0

import {signInWithPopup, SAMLAuthProvider} from 'firebase/auth';

startSignIn(auth, selectedTenantInfo) {
  // Show the UI to sign-in or sign-up a user.
  return new Promise((resolve, reject) => {
    // Provide the user multiple buttons to sign-in.
    // For example sign-in with popup using a SAML provider.
    // Note: The method of sign in might have already been determined from the
    // selectedTenantInfo object.
    const provider = new SAMLAuthProvider('saml.myProvider');
    signInWithPopup(auth, provider)
      .then((userCredential) => {
        resolve(userCredential);
      })
      .catch((error) => {
        // Show the error message.
      });
    // Using redirect flow. When the page redirects back and sign-in completes,
    // ciap will detect the result and complete sign-in without any additional
    // action.
    signInWithRedirect(auth, provider);
  });
}

חלק מספקי OAuth תומכים בהעברת רמז לכניסה לחשבון לצורך כניסה:

gcip-iap v1.0.0

startSignIn(auth, selectedTenantInfo) {
  // Show the UI to sign-in or sign-up a user.
  return new Promise((resolve, reject) => {
    // Use selectedTenantInfo to determine the provider and pass the login hint
    // if that provider supports it and the user specified an email address.
    if (selectedTenantInfo &&
        selectedTenantInfo.providerIds &&
        selectedTenantInfo.providerIds.indexOf('microsoft.com') !== -1) {
      const provider = new firebase.auth.OAuthProvider('microsoft.com');
      provider.setCustomParameters({
        login_hint: selectedTenantInfo.email || undefined,
      });
    } else {
      // Figure out the provider used...
    }
    auth.signInWithPopup(provider)
      .then((userCredential) => {
        resolve(userCredential);
      })
      .catch((error) => {
        // Show the error message.
      });
    });
}

gcip-iap v2.0.0

import {signInWithPopup, OAuthProvider} from 'firebase/auth';

startSignIn(auth, selectedTenantInfo) {
  // Show the UI to sign in or sign up a user.
  return new Promise((resolve, reject) => {
    // Use selectedTenantInfo to determine the provider and pass the login hint
    // if that provider supports it and the user specified an email address.
    if (selectedTenantInfo &&
        selectedTenantInfo.providerIds &&
        selectedTenantInfo.providerIds.indexOf('microsoft.com') !== -1) {
      const provider = new OAuthProvider('microsoft.com');
      provider.setCustomParameters({
        login_hint: selectedTenantInfo.email || undefined,
      });
    } else {
      // Figure out the provider used...
    }
    signInWithPopup(auth, provider)
      .then((userCredential) => {
        resolve(userCredential);
      })
      .catch((error) => {
        // Show the error message.
      });
    });
}

מידע נוסף זמין במאמר אימות באמצעות ריבוי דיירים.

טיפול בשגיאות

כדי להציג הודעות שגיאה למשתמשים או לנסות לשחזר שגיאות כמו פסק זמן ברשת, צריך להטמיע את handleError().

בדוגמה הבאה מוטמעת הפונקציה handleError():

handleError(error) {
  showAlert({
    code: error.code,
    message: error.message,
    // Whether to show the retry button. This is only available if the error is
    // recoverable via retrial.
    retry: !!error.retry,
  });
  // When user clicks retry, call error.retry();
  $('.alert-link').on('click', (e) => {
    error.retry();
    e.preventDefault();
    return false;
  });
}

בטבלה הבאה מפורטים קודי שגיאה ספציפיים ל-IAP שיכולים להיות מוחזרים. יכול להיות ש-Identity Platform יחזיר שגיאות. אפשר לעיין בתיעוד של firebase.auth.Auth.

קוד שגיאה תיאור
invalid-argument הלקוח ציין ארגומנט לא חוקי.
failed-precondition לא ניתן לבצע את הבקשה במצב הנוכחי של המערכת.
out-of-range הלקוח ציין טווח לא חוקי.
unauthenticated הבקשה לא אומתה עקב טוקן OAuth חסר, לא חוקי או שתוקפו פג.
permission-denied