이전에 생성한 Firebase 프로젝트를 백엔드와 연동하기 위한 셋팅을 해보겠다.
그전에, Firebase에서 제공하는 Authentication기능을 사용하기 위해 Firebase Admin SDK라는 것을 활용해볼 건데, 이를 위해 Firebase 프로젝트에 Authentication 빌드를 추가하고 서비스 계정을 발급받아야 한다.
Authentication 빌드 추가

콘솔에 접속해서 프로젝트 좌측 사이드바에서 [빌드]-[Authentication]을 선택해준다.

인증(로그인) 수단을 선택해야 하는데, 소셜로그인을 구현하고 싶다면 우측에서 골라주면 된다.
나는 기본방식만 구현할 거라 '이메일/비밀번호'를 선택해줬다.

본인이 선택한 수단들이 제공업체로서 표시가 될 것이다.
서비스 계정 발급받기
프로젝트 설정에서 '서비스 계정' 탭을 클릭한다.

그러면 대충 아래와 같은 화면이 보일텐데,

여기서 '새 비공개 키 생성'을 눌러서 Firebase SDK를 이용하기 위한 서비스 계정을 발급받는다.
다이렉트로 다운로드 디렉토리에 json 형식의 파일로 내려받아진다.
Firebase App Configuration
콘솔에서 확인했던 프로젝트 설정들을 환경변수에 옮겨준다.

// .env
FIREBASE_PROJECT_ID=(projectId)
FIREBASE_API_KEY=(apiKey)
FIREBASE_AUTH_DOMAIN=(authDomain)
FIREBASE_STORAGE_BUCKET=(storageBucket)
FIREBASE_SENDER_ID=(messagingSenderId)
FIREBASE_APP_ID=(appId)
Firebase Admin Configuration
위에서 본 설정들은 Firebase (APP) SDK 를 사용하기 위한 설정들이다.
Authentication을 위한 기타 기능들을 활용하려면 Firebase Admin SDK를 사용해야 한다.
우선 해당 패키지를 설치해준다.
npm i firebase-admin
아까 내려받은 서비스 계정 json 파일을 프로젝트의 루트 디렉토리에 두고 사용하는 것도 방법이겠지만
좀 더 깔끔함을 위해 역시 .env 파일에 환경변수로 등록해두고 불러와서 사용하기로 했다.
// .env
FIREBASE_PROJECT_ID=(project_id)
FIREBASE_PRIVATE_KEY_ID=(private_key_id)
FIREBASE_PRIVATE_KEY=(private_key)
FIREBASE_CLIENT_EMAIL=(client_email)
FIREBASE_CLIENT_ID=(client_id)
FIREBASE_AUTH_URI=(auth_uri)
FIREBASE_TOKEN_URI=(token_uri)
FIREBASE_AUTH_CERT_URL=(auth_provider_x509_cert_url)
FIREBASE_CLIENT_CERT_URL=(client_x509_cert_url)
FIREBASE_UNIVERSE_DOMAIN=(universe_domain)
여기서 주의할 게 있는데, private_key를 환경변수에 등록할때 큰따옴표 로 감싸야 한다.
아래와 같은 식으로 해주면 된다.
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n ... -----END PRIVATE KEY-----\n"
Firebase App, Auth 초기화
위에서 등록해둔 설정들을 활용하여 각각 App과 Admin을 위한 초기화를 해준다.
// firebase.config.ts
import { ConfigService } from "@nestjs/config";
import * as admin from "firebase-admin";
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase-admin/auth";
import { getAuth as getAppAuth } from "firebase/auth";
export const getAdminAuth = (configService: ConfigService) => {
const serviceAccount = {
type: configService.get<string>('firebase.type'),
project_id: configService.get<string>('firebase.projectId'),
private_key_id: configService.get<string>('firebase.privateKeyId'),
private_key: configService.get<string>('firebase.privateKey'),
client_email: configService.get<string>('firebase.clientEmail'),
client_id: configService.get<string>('firebase.clientId'),
auth_uri: configService.get<string>('firebase.authUri'),
token_uri: configService.get<string>('firebase.tokenUri'),
auth_provider_x509_cert_url: configService.get<string>('firebase.authCertUrl'),
client_x509_cert_url: configService.get<string>('firebase.clientCertUrl'),
universe_domain: configService.get<string>('firebase.universeDomain'),
}
const app = admin.initializeApp({
credential: admin.credential.cert(serviceAccount as admin.ServiceAccount),
databaseURL: `https://${configService.get<string>('firebase.projectId')}.firebaseio.com`,
storageBucket: configService.get<string>('firebase.storageBucket')
});
return getAuth(app);
}
export const getFirebaseAppAuth = (configService: ConfigService) => {
const firebaseConfig = {
apiKey: configService.get<string>('firebase.apiKey'),
authDomain: configService.get<string>('firebase.authDomain'),
projectId: configService.get<string>('firebase.projectId'),
storageBucket: configService.get<string>('firebase.storageBucket'),
messagingSenderId: configService.get<string>('firebase.senderId'),
appId: configService.get<string>('firebase.appId')
};
const app = initializeApp(firebaseConfig);
return getAppAuth(app);
}
FirebaseModule 생성
Firebase 기능을 제공하는 독립적인 Module을 만들어보겠다.
우선 FirebaseModule을 생성해준다.
import { Module } from "@nestjs/common";
import { FirebaseService } from "./firebase.service";
@Module({
providers: [FirebaseService],
exports: [FirebaseService]
})
export class FirebaseModule {}
FirebaseService 의 뼈대도 생성해준다.
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
@Injectable()
export class FirebaseService {
constructor(private configService: ConfigService) {}
}
이제 여기서 아까 위의 firebase.config.ts에 정의된 getAdminAuth와 getFirebaseAppAuth 를 호출해서 각각의 SDK를 활용하기 위한 Auth 객체를 초기화 해준다.
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Auth as AdminAuth, UserRecord } from "firebase-admin/auth";
import { getAdminAuth, getFirebaseAppAuth } from "src/config/firebase.config";
import { Auth } from "firebase/auth";
@Injectable()
export class FirebaseService {
app: Auth;
admin: AdminAuth;
constructor(private configService: ConfigService) {
this.admin = getAdminAuth(configService);
this.app = getFirebaseAppAuth(configService);
}
}
저기서 firebase-admin/auth 에서 제공하는 Auth 객체와 firebase/auth에서 제공하는 Auth 객체가 이름만 같고 달라서 결국 firebase-admin/auth 쪽의 Auth객체 이름을 AdminAuth로 바꿔줬다.
활용 예시
1) firebase app SDK
import { signInWithEmailAndPassword } from "firebase/auth";
...
const userCredential = await signInWithEmailAndPassword(this.app, email, password);
...
const userRecord = await this.admin.createUser({
email: 'test@gmail.com',
password: '1234',
emailVerified: false,
disabled: false
});