멀티테넌시로 인증
이 문서에서는 멀티 테넌트 Identity Platform 환경에서 사용자를 인증하는 방법을 설명합니다.
시작하기 전에
프로젝트에 멀티테넌시를 사용 설정하고 테넌트를 구성해야 합니다. 자세한 방법은 멀티테넌시 시작하기를 참조하세요.
또한 앱에 클라이언트 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]' });
테넌트로 로그인
테넌트에 로그인하려면 테넌트 ID를 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 인스턴스의 향후 로그인 요청에는 테넌트 ID를 변경하거나 재설정할 때까지 테넌트 ID(앞의 예시에서 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 인스턴스를 만들고 다른 ID를 할당합니다.
웹 버전 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가 해당 테넌트로 설정된 테넌트 사용자가 반환됩니다. 나중에 auth 인스턴스에서 tenantId를 전환해도 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 공급업체를 통해 로그인하려면 Google Cloud 콘솔의 공급업체 ID로 SAMLAuthProvider 인스턴스를 인스턴스화합니다.
웹 버전 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. }); 
- 리디렉션 - 웹 버전 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 인스턴스에 올바른 테넌트 ID를 설정합니다.
이메일 링크
이 인증 과정을 시작하려면 사용자에게 이메일 주소를 제공하도록 요청하는 인터페이스를 표시하고 sendSignInLinkToEmail을 호출하여 인증 링크를 보냅니다. 이메일을 보내기 전에 auth 인스턴스에 올바른 테넌트 ID를 설정하세요.
웹 버전 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 });
방문 페이지에서 로그인을 완료하려면 먼저 이메일 링크에서 테넌트 ID를 파싱하여 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 인스턴스에 올바른 테넌트 ID가 설정되어 있으면 최상위 tenant_id 클레임이 결과 JWT에 추가됩니다.
커스텀 토큰을 만들고 사용하는 방법에 대한 자세한 안내는 커스텀 토큰 만들기를 참조하세요.
다음 예시에서는 Admin 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; // ... });
테넌트 ID가 일치하지 않으면 signInWithCustomToken() 메서드가 실패합니다.
멀티 테넌트 사용자 인증 정보 연결
다른 유형의 사용자 인증 정보를 기존 멀티 테넌트 사용자에 연결할 수 있습니다. 예를 들어 사용자가 이전에 테넌트에서 SAML 공급업체를 통해 인증한 경우 기존 계정에 이메일/비밀번호 로그인을 추가하여 두 방법 중 하나를 사용하여 테넌트에 로그인할 수 있습니다.
웹 버전 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를 사용하여 사용할 테넌트를 지정합니다. 이는 updateProfile 및 updatePassword와 같은 다른 사용자 관리 API에도 적용됩니다.
account-exists-with-different-credential 오류 처리
Google Cloud Console에서 동일한 이메일을 사용하는 계정 연결 설정을 사용 설정한 경우 사용자가 Google과 같은 다른 공급업체에 이미 있는 이메일로 SAML과 같은 공급업체에 로그인을 시도하면 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(); }); }); } }); } }); 
- 리디렉션 - signInWithRedirect를 사용하는 경우 리디렉션 흐름을 완료하면- getRedirectResult에서- auth/account-exists-with-different-credential오류가 발생합니다.- 오류 객체에는 - error.tenantId속성이 포함되어 있습니다. 리디렉션한- auth인스턴스의 테넌트 ID는 유지되지 않으므로 오류 객체의 테넌트 ID를- 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(); }); }); 
최종 사용자 계정 만들기 및 삭제 중지
사용자 작업을 통해 계정을 만드는 대신 관리자가 사용자 계정을 만들도록 하는 경우가 있습니다. 이러한 경우 REST API를 통해 사용자 작업을 중지할 수 있습니다.
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: 프로젝트 ID입니다.
- TENANT_ID: 테넌트 ID.