FirebaseUI로 로그인 페이지 만들기

IAP(Identity-Aware Proxy)로 외부 ID를 사용하려면 앱에 로그인 페이지가 필요합니다. IAP는 사용자를 보안 리소스에 액세스하기 전에 이 페이지로 리디렉션하여 인증합니다.

이 문서에서는 오픈소스 자바스크립트 라이브러리인 FirebaseUI를 사용하여 인증 페이지를 빌드하는 방법을 보여줍니다. FirebaseUI는 상용구 코드를 줄일 수 있는 맞춤설정 가능한 요소를 제공하고, 다양한 ID 공급업체를 통해 사용자 로그인 과정을 처리합니다.

더 빠른 시작을 위해 IAP가 UI를 호스팅하도록 합니다. 이렇게 하면 코드를 추가로 작성하지 않고도 외부 ID를 사용해 볼 수 있습니다. 고급 시나리오의 경우 자체 로그인 페이지를 처음부터 빌드할 수도 있습니다. 이 옵션은 더 복잡하지만 인증 흐름 및 사용자 환경을 완전히 제어할 수 있습니다.

시작하기 전에

외부 ID를 사용설정하고 설정 중에 자체 UI 제공 옵션을 선택합니다.

라이브러리 설치

gcip-iap, firebase, firebaseui 라이브러리를 설치합니다. gcip-iap 모듈은 앱, IAP, Identity Platform 간의 통신을 추상화합니다. firebasefirebaseui 라이브러리는 인증 UI의 구성 요소를 제공합니다.

npm install firebase --save
npm install firebaseui --save
npm install gcip-iap --save

CDN을 사용하여 gcip-iap 모듈을 사용할 수 없습니다.

그런 다음 소스 파일에서 모듈을 import할 수 있습니다. SDK 버전에 맞는 가져오기를 사용합니다.

gcip-iap v0.1.4 이하

// Import firebase modules.
import * as firebase from "firebase/app";
import "firebase/auth";
// Import firebaseui module.
import * as firebaseui from 'firebaseui'
// Import gcip-iap module.
import * as ciap from 'gcip-iap';

gcip-iap v1.0.0 이상

버전 v1.0.0부터는 gcip-iapfirebase v9 이상의 피어 종속 항목이 필요합니다. gcip-iap v1.0.0 이상으로 마이그레이션하는 경우 다음 작업을 완료합니다.

  • package.json 파일의 firebasefirebaseui 버전을 각각 v9.6.0 이상 및 v6.0.0 이상으로 업데이트합니다.
  • 다음과 같이 firebase import 문을 업데이트합니다.
// Import firebase modules.
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
// Import firebaseui module.
import * as firebaseui from 'firebaseui'
// Import gcip-iap module.

추가 코드 변경은 필요하지 않습니다.

현지화된 버전의 라이브러리 사용을 포함한 추가 설치 옵션은 GitHub에서 안내를 참조하세요.

애플리케이션 구성

FirebaseUI는 인증에 사용할 테넌트 및 공급업체를 지정하는 구성 객체를 사용합니다. 전체 구성은 매우 길 수 있으며 다음과 같이 표시될 수 있습니다.

// The project configuration.
const configs = {
  // Configuration for project identified by API key API_KEY1.
  API_KEY1: {
    authDomain: 'project-id1.firebaseapp.com',
    // Decide whether to ask user for identifier to figure out
    // what tenant to select or whether to present all the tenants to select from.
    displayMode: 'optionFirst', // Or identifierFirst
    // The terms of service URL and privacy policy URL for the page
    // where the user select tenant or enter email for tenant/provider
    // matching.
    tosUrl: 'http://localhost/tos',
    privacyPolicyUrl: 'http://localhost/privacypolicy',
    callbacks: {
      // The callback to trigger when the selection tenant page
      // or enter email for tenant matching page is shown.
      selectTenantUiShown: () => {
        // Show title and additional display info.
      },
      // The callback to trigger when the sign-in page
      // is shown.
      signInUiShown: (tenantId) => {
        // Show tenant title and additional display info.
      },
      beforeSignInSuccess: (user) => {
        // Do additional processing on user before sign-in is
        // complete.
        return Promise.resolve(user);
      }
    },
    tenants: {
      // Tenant configuration for tenant ID tenantId1.
      tenantId1: {
        // Full label, display name, button color and icon URL of the
        // tenant selection button. Only needed if you are
        // using the option first option.
        fullLabel: 'ACME Portal',
        displayName: 'ACME',
        buttonColor: '#2F2F2F',
        iconUrl: '<icon-url-of-sign-in-button>',
         // Sign-in providers enabled for tenantId1.
        signInOptions: [
          // Microsoft sign-in.
          {
            provider: 'microsoft.com',
            providerName: 'Microsoft',
            buttonColor: '#2F2F2F',
            iconUrl: '<icon-url-of-sign-in-button>',
            loginHintKey: 'login_hint'
          },
          // Email/password sign-in.
          {
            provider: 'password',
            // Do not require display name on sign up.
            requireDisplayName: false,
            disableSignUp: {
              // Disable user from signing up with email providers.
              status: true,
              adminEmail: 'admin@example.com',
              helpLink: 'https://www.example.com/trouble_signing_in'
            }
          },
          // SAML provider. (multiple SAML providers can be passed)
          {
            provider: 'saml.my-provider1',
            providerName: 'SAML provider',
            fullLabel: 'Employee Login',
            buttonColor: '#4666FF',
            iconUrl: 'https://www.example.com/photos/my_idp/saml.png'
          },
        ],
        // If there is only one sign-in provider eligible for the user,
        // whether to show the provider selection page.
        immediateFederatedRedirect: true,
        signInFlow: 'redirect', // Or popup
        // The terms of service URL and privacy policy URL for the sign-in page
        // specific to each tenant.
        tosUrl: 'http://localhost/tenant1/tos',
        privacyPolicyUrl: 'http://localhost/tenant1/privacypolicy'
      },
      // Tenant configuration for tenant ID tenantId2.
      tenantId2: {
        fullLabel: 'OCP Portal',
        displayName: 'OCP',
        buttonColor: '#2F2F2F',
        iconUrl: '<icon-url-of-sign-in-button>',
        // Tenant2 supports a SAML, OIDC and Email/password sign-in.
        signInOptions: [
          // Email/password sign-in.
          {
            provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
            // Do not require display name on sign up.
            requireDisplayName: false
          },
          // SAML provider. (multiple SAML providers can be passed)
          {
            provider: 'saml.my-provider2',
            providerName: 'SAML provider',
            fullLabel: 'Contractor Portal',
            buttonColor: '#4666FF',
            iconUrl: 'https://www.example.com/photos/my_idp/saml.png'
          },
          // OIDC provider. (multiple OIDC providers can be passed)
          {
            provider: 'oidc.my-provider1',
            providerName: 'OIDC provider',
            buttonColor: '#4666FF',
            iconUrl: 'https://www.example.com/photos/my_idp/oidc.png'
          },
        ],
      },
      // Tenant configuration for tenant ID tenantId3.
      tenantId3: {
        fullLabel: 'Tenant3 Portal',
        displayName: 'Tenant3',
        buttonColor: '#007bff',
        iconUrl: '<icon-url-of-sign-in-button>',
        // Tenant3 supports a Google and Email/password sign-in.
        signInOptions: [
          // Email/password sign-in.
          {
            provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
            // Do not require display name on sign up.
            requireDisplayName: false
          },
          // Google provider.
          {
            provider: 'google.com',
            scopes: ['scope1', 'scope2', 'https://example.com/scope3'],
            loginHintKey: 'login_hint',
            customParameters: {
              prompt: 'consent',
            },
          },
        ],
        // Sets the adminRestrictedOperation configuration for providers
        // including federated, email/password, email link and phone number.
        adminRestrictedOperation: {
          status: true,
          adminEmail: 'admin@example.com',
          helpLink: 'https://www.example.com/trouble_signing_in'
        }
      },
    },
  },
};

다음 섹션에서는 IAP 관련 필드를 구성하는 방법을 안내합니다. 다른 필드를 설정하는 예는 위의 코드 스니펫 또는 GitHub의 FirebaseUI 문서를 참조하세요.

API 키 설정

일반적인 구성은 프로젝트의 API 키로 시작합니다.

// The project configuration.
const configs = {
  // Configuration for API_KEY.
  API_KEY: {
    // Config goes here
  }
}

대부분의 경우 단일 API 키만 지정하면 됩니다. 그러나 여러 프로젝트에서 단일 인증 URL을 사용하려는 경우 여러 API 키를 포함할 수 있습니다.

const configs = {
  API_KEY1: {
    // Config goes here
  },
  API_KEY2: {
    // Config goes here
  },
}

인증 도메인 가져오기

제휴 로그인을 용이하게 하려면 authdomain 필드를 프로비저닝된 도메인으로 설정합니다. Google Cloud 콘솔의 Identity Platform 페이지에서 이 필드를 가져올 수 있습니다.

테넌트 ID 지정

구성에는 사용자가 인증할 수 있는 테넌트 및 공급업체 목록이 필요합니다.

각 테넌트는 해당 ID로 식별됩니다. 프로젝트 수준의 인증(테넌트 없음)을 사용하는 경우 특수 _ 식별자를 API 키로 대신 사용하세요. 예를 들면 다음과 같습니다.

const configs = {
  // Configuration for project identified by API key API_KEY1.
  API_KEY1: {
    tenants: {
      // Project-level IdPs flow.
      _: {
        // Tenant config goes here
      },
      // Single tenant flow.
      1036546636501: {
        // Tenant config goes here
      }
    }
  }
}

* 연산자를 사용하여 와일드 카드 테넌트 구성을 지정할 수도 있습니다. 일치하는 ID가 없으면 이 테넌트가 대체로 사용됩니다.

테넌트 공급업체 구성

각 테넌트는 signInOptions 필드에 지정되는 자체 공급업체가 있습니다.

tenantId1: {
  signInOptions: [
    // Options go here
  ]
}

공급업체 구성 방법에 대해서는 FirebaseUI 설명서에서 로그인 공급업체 구성을 참조하세요.

FirebaseUI 설명서에 요약된 단계 외에도 선택한 테넌트 선택 모드에 따라 IAP와 관련된 몇 가지 필드가 있습니다. 이 필드에 대한 자세한 내용은 다음 섹션을 참조하세요.

테넌트 선정 모드 선택

사용자는 옵션 우선 모드 또는 식별자 우선 모드의 두 가지 방법으로 테넌트를 선택할 수 있습니다.

옵션 모드에서 사용자는 목록에서 테넌트를 선택하여 시작한 다음 사용자 이름과 비밀번호를 입력합니다. 식별자 모드에서는 사용자가 이메일을 먼저 입력합니다. 그러면 시스템에서 이메일 도메인과 일치하는 ID 공급업체가 있는 첫 번째 테넌트를 자동으로 선택합니다.

옵션 모드를 사용하려면 displayModeoptionFirst로 설정하세요. 그런 다음 displayName, buttonColor, iconUrl을 포함하여 각 테넌트 버튼에 대한 구성 정보를 제공해야 합니다. 선택사항인 fullLabel를 제공하여 표시 이름만 표시하는 대신 전체 버튼 라벨을 재정의할 수도 있습니다.

다음은 옵션 모드를 사용하도록 구성된 테넌트의 예시입니다.

tenantId1: {
  fullLabel: 'ACME Portal',
  displayName: 'ACME',
  buttonColor: '#2F2F2F',
  iconUrl: '<icon-url-of-sign-in-button>',
  // ...

식별자 모드를 사용하려면 각 로그인 옵션이 지원하는 도메인을 나타내는 hd 필드를 지정해야 합니다. 이는 정규식(예: /@example\.com$/) 또는 도메인 문자열(예: example.com).

아래 코드는 식별자 모드를 사용하도록 구성된 테넌트를 보여줍니다.

tenantId1: {
  signInOptions: [
    // Email/password sign-in.
    {
      hd: 'acme.com', // using regex: /@acme\.com$/
      // ...
    },

즉각적인 리디렉션 사용 설정

앱이 단일 ID 공급업체만 지원하는 경우 immediateFederatedRedirecttrue로 설정하면 로그인 UI를 건너뛰고 사용자를 공급업체로 직접 리디렉션합니다.

콜백 설정

구성 객체에는 인증 흐름 중에 다양한 지점에서 호출되는 콜백 집합이 포함됩니다. 이렇게 하면 UI를 추가로 맞춤설정할 수 있습니다. 다음 후크를 사용할 수 있습니다.

selectTenantUiShown() 테넌트를 선택하기 위한 UI가 표시될 때 트리거됩니다. 맞춤설정된 제목 또는 테마로 UI를 수정하려는 경우 이를 사용하세요.
signInUiShown(tenantId) 테넌트가 선택되고 사용자가 사용자 인증 정보를 입력할 수 있는 UI가 표시될 때 트리거됩니다. 맞춤설정된 제목 또는 테마로 UI를 수정하려는 경우 이를 사용하세요.
beforeSignInSuccess(user) 로그인이 완료되기 전에 트리거됩니다. IAP 리소스로 다시 리디렉션하기 전에 로그인한 사용자를 수정할 수 있습니다.

다음 예시 코드는 이러한 콜백을 구현하는 방법을 보여줍니다.

callbacks: {
  selectTenantUiShown: () => {
    // Show info of the IAP resource.
    showUiTitle(
        'Select your employer to access your Health Benefits');
  },
  signInUiShown: (tenantId) => {
    // Show tenant title and additional display info.
    const tenantName = getTenantNameFromId(tenantId);
    showUiTitle(`Sign in to access your ${tenantName} Health Benefits`);
  },
  beforeSignInSuccess: (user) => {
    // Do additional processing on user before sign-in is
    // complete.
    // For example update the user profile.
    return user.updateProfile({
      photoURL: 'https://example.com/profile/1234/photo.png',
    }).then(function() {
      // To reflect updated photoURL in the ID token, force token
      // refresh.
      return user.getIdToken(true);
    }).then(function() {
      return user;
    });
  }
}

라이브러리 초기화

구성 객체를 만든 후에는 다음 단계에 따라 인증 페이지에서 라이브러리를 초기화합니다.

  1. UI를 렌더링할 HTML 컨테이너를 만듭니다.

    <!DOCTYPE html>
    <html>
     <head>...</head>
     <body>
       <!-- The surrounding HTML is left untouched by FirebaseUI.
            Your app may use that space for branding, controls and other
            customizations.-->
       <h1>Welcome to My Awesome App</h1>
       <div id="firebaseui-auth-container"></div>
     </body>
    </html>
    
  2. HTML 컨테이너에서 렌더링할 FirebaseUiHandler 인스턴스를 만들고 만든 config 요소를 전달합니다.

    const configs = {
      // ...
    }
    const handler = new firebaseui.auth.FirebaseUiHandler(
      '#firebaseui-auth-container', configs);
    
  3. Authentication 인스턴스를 만들고 핸들러를 전달한 후 start()를 호출합니다.

    const ciapInstance = new ciap.Authentication(handler);
    ciapInstance.start();
    

애플리케이션을 배포하고 인증 페이지로 이동하세요. 테넌트 및 공급업체가 포함된 로그인 UI가 나타납니다.

다음 단계