이 문서에서는 외부 ID 및 IAP를 사용하여 자체 인증 페이지를 빌드하는 방법을 설명합니다. 이 페이지를 직접 구성하면 인증 흐름과 사용자 환경을 완전히 제어할 수 있습니다.
UI를 완전히 맞춤설정할 필요가 없는 경우 IAP에서 로그인 페이지를 호스팅하거나 FirebaseUI를 사용하여 환경을 더욱 간소화할 수 있습니다.
개요
자체 인증 페이지를 빌드하려면 다음 단계를 따르세요.
- 외부 ID를 사용 설정합니다. 설정 중에 자체 UI 제공 옵션을 선택합니다.
gcip-iap
라이브러리를 설치합니다.AuthenticationHandler
인터페이스를 구현하여 UI를 구성합니다. 인증 페이지에서는 다음 시나리오를 처리해야 합니다.- 테넌트 선정
- 사용자 승인
- 사용자 로그인
- 오류 처리
- 선택사항: 진행률 표시줄, 로그아웃 페이지, 사용자 처리 등의 추가 기능으로 인증 페이지를 맞춤설정합니다.
- UI를 테스트합니다.
gcip-iap 라이브러리 설치
gcip-iap
라이브러리를 설치하려면 다음 명령어를 실행합니다.
npm install gcip-iap --save
gcip-iap
NPM 모듈은 애플리케이션, IAP, Identity Platform 간의 통신을 추상화합니다. 이렇게 하면 UI와 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~v1.1.0
버전 v1.0.0부터는 gcip-iap
에 firebase
v9 이상의 피어 종속 항목이 필요합니다.
gcip-iap
v1.0.0 이상으로 마이그레이션하는 경우 다음 작업을 완료합니다.
package.json
파일의firebase
버전을 v9.6.0 이상으로 업데이트합니다.- 다음과 같이
firebase
import 문을 업데이트합니다.
// 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 형식을 사용하여 커스텀 UI 애플리케이션을 다시 작성해야 합니다. gcip-iap
v2.0.0 이상으로 마이그레이션하는 경우 다음 작업을 완료해야 합니다.
package.json
파일의firebase
버전을 v9.8.3 이상으로 업데이트합니다.- 다음과 같이
firebase
import 문을 업데이트합니다.
// Import Firebase modules.
import { initializeApp } from 'firebase/app';
import { getAuth, GoogleAuthProvider } 'firebase/auth';
// Import the gcip-iap module.
import * as ciap from 'gcip-iap';
UI 구성
UI를 구성하려면 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
의 메서드를 자동으로 호출합니다.
테넌트 선택
테넌트를 선택하려면 selectTenant()
를 구현합니다. 이 메서드를 구현하면 테넌트를 프로그래매틱 방식으로 선택하거나 사용자가 직접 선택할 수 있도록 UI를 표시할 수 있습니다.
두 경우 모두 라이브러리는 반환된 SelectedTenantInfo
객체를 사용하여 인증 흐름을 완료합니다. 선택한 테넌트의 ID, 모든 제공업체 ID, 사용자가 입력한 이메일이 포함됩니다.
프로젝트에 테넌트가 여러 개 있는 경우 테넌트 중 하나를 선택해야 사용자를 인증할 수 있습니다. 테넌트가 하나만 있거나 프로젝트 수준 인증을 사용 중인 경우 selectTenant()
를 구현할 필요가 없습니다.
IAP는 다음과 같은 Identity Platform과 동일한 제공업체를 지원합니다.
- 이메일 및 비밀번호
- OAuth(Google, Facebook, Twitter, GitHub, Microsoft 등)
- SAML
- OIDC
- 전화번호
- 커스텀
- 익명
멀티테넌시에는 전화번호, 커스텀, 익명 인증 유형이 지원되지 않습니다.
프로그래매틱 방식으로 테넌트 선택
프로그래매틱 방식으로 테넌트를 선택하려면 현재 컨텍스트를 활용하세요. Authentication
클래스에는 인증 전에 사용자가 액세스한 URL을 반환하는 getOriginalURL()
이 포함됩니다.
이를 사용하여 연결된 테넌트 목록에서 일치 항목을 찾으세요.
// 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()
를 구현하여 제공된 API 키 및 테넌트 ID에 해당하는 인증 인스턴스를 반환합니다. 테넌트 ID가 제공되지 않으면 프로젝트 수준의 ID 공급업체를 사용합니다.
getAuth()
는 제공된 구성에 해당하는 사용자가 저장된 위치를 추적합니다. 또한 사용자가 사용자 인증 정보를 다시 입력하지 않아도 이전에 인증된 사용자의 Identity Platform ID 토큰을 자동으로 새로 고칠 수 있습니다.
서로 다른 테넌트에 여러 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()
을 구현하고 사용자가 인증할 UI를 표시한 다음 완료 시 로그인한 사용자에 대해 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 |
클라이언트에게 충분한 권한이 없거나 UI가 승인되지 않은 도메인에서 호스팅됩니다. |
not-found |
지정한 리소스를 찾을 수 없습니다. |
aborted |
읽기-수정-쓰기 충돌 같은 동시 실행 충돌이 발생했습니다. |
already-exists |
클라이언트가 만들려고 했던 리소스가 이미 존재합니다. |
resource-exhausted |
리소스 할당량이 부족하거나 비율 제한에 도달했습니다. |
cancelled |
클라이언트에서 요청을 취소했습니다. |
data-loss |
복구할 수 없는 데이터 손실 또는 손상이 발생했습니다. |
unknown |
알 수 없는 서버 오류가 발생했습니다. |
internal |
내부 서버 오류입니다. |
not-implemented |
서버에서 API 메소드를 구현하지 않았습니다. |
unavailable |
서비스를 사용할 수 없습니다. |
restart-process |
이 페이지로 리디렉션된 URL을 다시 방문하여 인증 프로세스를 다시 시작하세요. |
deadline-exceeded |
요청 기한이 지났습니다. |
authentication-uri-fail |
인증 URI를 생성할 수 없습니다. |
gcip-token-invalid |
제공된 GCIP ID 토큰이 잘못되었습니다. |
gcip-redirect-invalid |
잘못된 리디렉션 URL입니다. |
get-project-mapping-fail |
프로젝트 ID를 가져오지 못했습니다. |
gcip-id-token-encryption-error |
GCIP ID 토큰 암호화 오류입니다. |
gcip-id-token-decryption-error |
GCIP ID 토큰 복호화 오류입니다. |
gcip-id-token-unescape-error |
웹 안전 base64 이스케이프 제거가 실패했습니다. |
resource-missing-gcip-sign-in-url |
지정된 IAP 리소스의 GCIP 인증 URL이 누락되었습니다. |
UI 맞춤설정
진행률 표시줄, 로그아웃 페이지와 같은 선택적 기능을 사용하여 인증 페이지를 맞춤설정할 수 있습니다.
진행률 UI 표시
gcip-iap
모듈이 장기 실행 네트워크 태스크를 실행할 때마다 사용자에게 커스텀 진행률 UI를 표시하려면 showProgressBar()
와 hideProgressBar()
를 구현합니다.
사용자 로그아웃
경우에 따라 사용자가 동일한 인증 URL을 공유하는 모든 현재 세션에서 로그아웃하도록 할 수 있습니다.
사용자가 로그아웃하면 다시 리디렉션할 URL이 없을 수 있습니다.
이는 일반적으로 사용자가 로그인 페이지와 연결된 모든 테넌트에서 로그아웃할 때 발생합니다. 이 경우 completeSignOut()
을 구현하여 사용자가 성공적으로 로그아웃했다는 메시지를 표시합니다. 이 메서드를 구현하지 않으면 사용자가 로그아웃하면 빈 페이지가 나타납니다.
사용자 처리
IAP 리소스로 리디렉션하기 전에 로그인한 사용자를 수정하려면 processUser()
를 구현합니다.
이 메서드를 사용하여 다음을 수행할 수 있습니다.
- 추가 제공업체에 연결
- 사용자 프로필 업데이트
- 등록 후 사용자에게 추가 데이터 요청
signInWithRedirect()
를 호출한 후getRedirectResult()
에서 반환된 OAuth 액세스 토큰 처리
다음은 processUser()
를 구현하는 예시입니다.
gcip-iap v1.0.0
processUser(user) {
return lastAuthUsed.getRedirectResult().then(function(result) {
// Save additional data, or ask the user for additional profile information
// to store in database, etc.
if (result) {
// Save result.additionalUserInfo.
// Save result.credential.accessToken for OAuth provider, etc.
}
// Return the user.
return user;
});
}
gcip-iap v2.0.0
import {getRedirectResult} from 'firebase/auth';
processUser(user) {
return getRedirectResult(lastAuthUsed).then(function(result) {
// Save additional data, or ask the user for additional profile information
// to store in database, etc.
if (result) {
// Save result.additionalUserInfo.
// Save result.credential.accessToken for OAuth provider, etc.
}
// Return the user.
return user;
});
}
IAP에서 앱으로 전파한 ID 토큰 클레임에 반영된 사용자 변경 사항을 원하면 토큰을 새로고침해야 합니다.
gcip-iap v1.0.0
processUser(user) {
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;
});
}
gcip-iap v2.0.0
import {updateProfile} from 'firebase/auth';
processUser(user) {
return updateProfile(user, {
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;
});
}
UI 테스트
AuthenticationHandler
를 구현하는 클래스를 만든 후에는 이를 사용하여 새 Authentication
인스턴스를 만들고 시작할 수 있습니다.
// Implement interface AuthenticationHandler.
// const authHandlerImplementation = ....
const ciapInstance = new ciap.Authentication(authHandlerImplementation);
ciapInstance.start();
애플리케이션을 배포하고 인증 페이지로 이동하세요. 커스텀 로그인 UI가 표시되어야 합니다.
다음 단계
- 프로그래매틱 방식으로 Google 이외의 리소스에 액세스하는 방법에 대해 알아보세요.
- 세션 관리에 대해 알아보세요.
- 외부 ID가 IAP와 어떻게 작동하는지에 대해 알아보세요.