JadeCode

[혼프] 혼자하는 프로젝트 1. firebase-auth 적용기 본문

개발

[혼프] 혼자하는 프로젝트 1. firebase-auth 적용기

z-zero 2023. 3. 29. 11:00

MEALTI - mealtime(식사시간) - 식비기록가계부 커뮤니티

 

작년 2학기, 캡스톤 디자인이라는 수업에서 팀 프로젝트를 했다. 하지만 리액트 네이티브를 처음 도입해봤고, 새로운 시도였기 때문에 별로 마음에 들지 않았다. 피그마에 디자인이 남아있기도 해서 혼자 firebase로 적용해보겠다고 생각했다.

 

firebase로그인 with firebaseui

next-firebase-auth를 사용할까 하다가 firebaseui로 하면 더 쉬울 것 같아서 react-firebaseui를 하려고 했다가.

https://github.com/gladly-team/next-firebase-auth#readme

 

GitHub - gladly-team/next-firebase-auth: Simple Firebase authentication for all Next.js rendering strategies

Simple Firebase authentication for all Next.js rendering strategies - GitHub - gladly-team/next-firebase-auth: Simple Firebase authentication for all Next.js rendering strategies

github.com

위의 깃에서 example이 있길래 클론받아서 동작시켜 봤다.

firebase에서 프로젝트 생성하고, firebaseConfig를 저장하고, 적용시키는 것 부터 했다.

process.env.~~로 시작하는 코드를 보니 .env파일은 보안이 중요해서 깃에 올리지 않은 걸로 보인다.

그래서 직접 생성한 파이어베이스 프로젝트 정보로 나도 .env 파일을 만들어 npm run dev를 해 봤다.

 

하지만 프로젝트를 살펴보니 "react-firebaseui/StyledFirebaseAuth"에 내가 진짜 원하는 파이어베이스 로그인 방법이 있길래, 최소한의 라이브러리를 사용하고자 react-firebaseui를 도입하려했다.

 

https://www.npmjs.com/package/firebaseui

 

firebaseui

Javascript library for customizable UI on top of Firebase SDK. Latest version: 6.0.2, last published: 4 months ago. Start using firebaseui in your project by running `npm i firebaseui`. There are 80 other projects in the npm registry using firebaseui.

www.npmjs.com

https://github.com/firebase/firebaseui-web#react-dom-setup

 

GitHub - firebase/firebaseui-web: FirebaseUI is an open-source JavaScript library for Web that provides simple, customizable UI

FirebaseUI is an open-source JavaScript library for Web that provides simple, customizable UI bindings on top of Firebase SDKs to eliminate boilerplate code and promote best practices. - GitHub - f...

github.com

일단 패키지를 다운받고 사용법을 알아보려 깃으로 갔다.

리액트에서 사용할 것이기 때문에 React DOM Setup으로 확인 해 봤다.

https://github.com/firebase/firebaseui-web-react

 

GitHub - firebase/firebaseui-web-react: React Wrapper for firebaseUI Web

React Wrapper for firebaseUI Web. Contribute to firebase/firebaseui-web-react development by creating an account on GitHub.

github.com

npm install --save react-firebaseui
npm install --save firebase

위의 코드로 설치하라 했다. 하지만 

npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR! 
npm ERR! While resolving: new_project@0.1.0
npm ERR! Found: react@18.2.0
npm ERR! node_modules/react
npm ERR!   react@"18.2.0" from the root project
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peer react@">=15 <=17" from react-firebaseui@6.0.0
npm ERR! node_modules/react-firebaseui
npm ERR!   react-firebaseui@"*" from the root project
npm ERR! 
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.

이런 오류가 뜨는 것이다. 정말...

이 때부터 알아차려야 했다. react-firebaseui를 사용하면 안된단걸...

하지만 나는 일단 해보자 라는 생각으로

npm install --save react-firebaseui --legacy-peer-deps

로 리액트 파이어베이스 유아이를 깔았다.

 

문서에 나와있는 대로

// Import FirebaseAuth and firebase.
import React from 'react';
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth';
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';

// Configure Firebase.
// 내 프로젝트 정보. 
const config = {
  apiKey: 'AIzaSyAeue-AsYu76MMQlTOM-KlbYBlusW9c1FM',
  authDomain: 'myproject-1234.firebaseapp.com',
  // ...
};
firebase.initializeApp(config);

// Configure FirebaseUI.
const uiConfig = {
  // Popup signin flow rather than redirect flow.
  signInFlow: 'popup',
  // Redirect to /signedIn after sign in is successful. Alternatively you can provide a callbacks.signInSuccess function.
  signInSuccessUrl: '/signedIn',
  // We will display Google and Facebook as auth providers.
  signInOptions: [
    firebase.auth.GoogleAuthProvider.PROVIDER_ID,
    firebase.auth.FacebookAuthProvider.PROVIDER_ID,
  ],
};

function SignInScreen() {
  return (
    <div>
      <h1>My App</h1>
      <p>Please sign-in:</p>
      <StyledFirebaseAuth uiConfig={uiConfig} firebaseAuth={firebase.auth()} />
    </div>
  );
}

export default SignInScreen

코드를 실행해 봤지만 돌아오는 것은 참담했다.

 

엄청난 구글링 끝에 원인을 알아냈다.

 

https://github.com/firebase/firebaseui-web-react/issues/172

 

React 18 StrictMode causes "AuthUI instance is deleted" error · Issue #172 · firebase/firebaseui-web-react

When using react-firebaseui in React 18 with Strict Mode enabled and running a local dev server, the UI doesn’t render at all and produces this error in the console: This is because of a change in ...

github.com

https://github.com/firebase/firebaseui-web-react/pull/173

 

Upgraded to React 18, updated dependencies, fixed "AuthUI instance is deleted" error by gvillenave · Pull Request #173 · fireb

Updated peer dependency to support React 18 Updated all dependencies (Babel, Webpack, ...) Replaced React class component with function component Updated component lifecycle management from compone...

github.com

많은 사람들이 같은 오류를 겪고 있었고, merge가 되었냐는 질문을 보아하니 아직 반영이 안된 것 같았다. 

진짜 똑똑하고 착한 사람들이 react-firebaseui를 쓰지 말고 직접 컴포넌트를 만들어서 쓰라고 코드도 올려 줬다.

 

//MartinXPN 사람의 답변
import { useEffect, useRef, useState } from 'react';
import { onAuthStateChanged } from 'firebase/auth';
import 'firebaseui/dist/firebaseui.css';
import {auth} from "firebaseui";

interface Props {
    // The Firebase UI Web UI Config object.
    // See: https://github.com/firebase/firebaseui-web#configuration
    uiConfig: auth.Config;
    // Callback that will be passed the FirebaseUi instance before it is
    // started. This allows access to certain configuration options such as
    // disableAutoSignIn().
    uiCallback?(ui: auth.AuthUI): void;
    // The Firebase App auth instance to use.
    firebaseAuth: any; // As firebaseui-web
    className?: string;
}


const StyledFirebaseAuth = ({uiConfig, firebaseAuth, className, uiCallback}: Props) => {
    const [firebaseui, setFirebaseui] = useState<typeof import('firebaseui') | null>(null);
    const [userSignedIn, setUserSignedIn] = useState(false);
    const elementRef = useRef(null);

    useEffect(() => {
        // Firebase UI only works on the Client. So we're loading the package only after
        // the component has mounted, so that this works when doing server-side rendering.
        setFirebaseui(require('firebaseui'));
    }, []);


    useEffect(() => {
        if (firebaseui === null )
            return;

        // Get or Create a firebaseUI instance.
        const firebaseUiWidget = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(firebaseAuth);
        if (uiConfig.signInFlow === 'popup')
            firebaseUiWidget.reset();

        // We track the auth state to reset firebaseUi if the user signs out.
        const unregisterAuthObserver = onAuthStateChanged(firebaseAuth, user => {
            if (!user && userSignedIn)
                firebaseUiWidget.reset();
            setUserSignedIn(!!user);
        });

        // Trigger the callback if any was set.
        if (uiCallback)
            uiCallback(firebaseUiWidget);

        // Render the firebaseUi Widget.
        // @ts-ignore
        firebaseUiWidget.start(elementRef.current, uiConfig);

        return () => {
            unregisterAuthObserver();
            firebaseUiWidget.reset();
        };
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [firebaseui, uiConfig]);

    return <div className={className} ref={elementRef} />;
};

export default StyledFirebaseAuth;

MartinXPN 진짜 똑똑한 사람이다. 이 사람의 코드를 쓰니 실행이 잘 되었다.

 

결과

그래서 최종 나의 firebase-auth코드이다.

Email로그인은 따로 회원가입 없이 로그인이 가능하다!!

// pages/auth/login.tsx

// Import FirebaseAuth and firebase.
import StyledFirebaseAuth from "StyledFirebaseAuth";
import firebase from "firebase/compat/app";
import "firebase/compat/auth";
import { fireAuth } from "clientApp";
import { useRouter } from "next/router";

function Login() {
  const router = useRouter();
  // Configure FirebaseUI.
  const uiConfig = {
    // Popup signin flow rather than redirect flow.
    signInFlow: "popup",
    // 이메일 인증과 구글 인증
    signInOptions: [
      {
        provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
        requireDisplayName: true,
        buttonColor: "#FFBC58", // 원하는 버튼 색깔
      },
      firebase.auth.GoogleAuthProvider.PROVIDER_ID, // 구글
    ],
    callbacks: {
      signInSuccessWithAuthResult: (res: any) => {
        setCurrentUser(res.additionalUserInfo.profile);
        router.push("/");
        return false;
      },
    },
  };

  return (
    <>
        <StyledFirebaseAuth uiConfig={uiConfig} firebaseAuth={fireAuth} />
    </>
  );
}

export default Login;
// clientApp.ts
// 초기화 및 사용

import firebase from "firebase/compat/app";
import "firebase/compat/auth";
import "firebase/compat/auth";
import firebaseConfig from "firebaseConfig.json";

const app = firebase.initializeApp(firebaseConfig);

const fireAuth = firebase.auth();

export { fireAuth };
// firebaseConfig.json
// 처음 프로젝트 생성하면 보여주는 것 ~
{
  "apiKey": "앱 키",
  "authDomain": "앱 도메인",
  "databaseURL": "데이터베이스 url",
  "projectId": "프로젝트 아이디",
  "storageBucket": "스토리지 버킷",
  "messagingSenderId": "메세지센더아이디",
  "appId": "앱 아이디",
  "measurementId": "아이디"
}
// StyledFirebaseAuth.tsx
// https://github.com/firebase/firebaseui-web-react/pull/173

import { useEffect, useRef, useState } from "react";
import { onAuthStateChanged } from "firebase/auth";
import "firebaseui/dist/firebaseui.css";
import { auth } from "firebaseui";
interface Props {
  // The Firebase UI Web UI Config object.
  // See: https://github.com/firebase/firebaseui-web#configuration
  uiConfig: auth.Config;
  // Callback that will be passed the FirebaseUi instance before it is
  // started. This allows access to certain configuration options such as
  // disableAutoSignIn().
  uiCallback?(ui: auth.AuthUI): void;
  // The Firebase App auth instance to use.
  firebaseAuth: any; // As firebaseui-web
  className?: string;
}

const StyledFirebaseAuth = ({
  uiConfig,
  firebaseAuth,
  className,
  uiCallback,
}: Props) => {
  const [firebaseui, setFirebaseui] = useState<
    typeof import("firebaseui") | null
  >(null);
  const [userSignedIn, setUserSignedIn] = useState(false);
  const elementRef = useRef(null);

  useEffect(() => {
    // Firebase UI only works on the Client. So we're loading the package only after
    // the component has mounted, so that this works when doing server-side rendering.
    setFirebaseui(require("firebaseui"));
  }, []);

  useEffect(() => {
    if (firebaseui === null) return;

    // Get or Create a firebaseUI instance.
    const firebaseUiWidget =
      firebaseui.auth.AuthUI.getInstance() ||
      new firebaseui.auth.AuthUI(firebaseAuth);
    if (uiConfig.signInFlow === "popup") firebaseUiWidget.reset();

    // We track the auth state to reset firebaseUi if the user signs out.
    const unregisterAuthObserver = onAuthStateChanged(firebaseAuth, (user) => {
      if (!user && userSignedIn) firebaseUiWidget.reset();
      setUserSignedIn(!!user);
    });

    // Trigger the callback if any was set.
    if (uiCallback) uiCallback(firebaseUiWidget);

    // Render the firebaseUi Widget.
    // @ts-ignore
    firebaseUiWidget.start(elementRef.current, uiConfig);

    return () => {
      unregisterAuthObserver();
      firebaseUiWidget.reset();
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [firebaseui, uiConfig]);

  return <div className={className} ref={elementRef} />;
};

export default StyledFirebaseAuth;
// 이메일 인증 시 원하는 색깔로 바꾸기 !important 원하는컬러 자리에 색상코드
.firebaseui-button {
  background-color: #원하는컬러 !important;
  color: white !important;
}

.firebaseui-textfield.mdl-textfield .firebaseui-label:after {
  background-color: #원하는컬러 !important;
}

.firebaseui-form-links > a {
  color: #원하는컬러;
}
.mdl-progress > .progressbar {
  background-color: #원하는컬러 !important;
}

.mdl-progress > .bufferbar {
  background-image: linear-gradient(
      90deg,
      hsla(0, 0%, 100%, 0.7),
      hsla(0, 0%, 100%, 0.7)
    ),
    linear-gradient(90deg, #원하는컬러, #원하는컬러) !important;
  z-index: 0;
  left: 0;
}

.mdl-progress:not(.mdl-progress--indeterminate) > .auxbar,
.mdl-progress:not(.mdl-progress__indeterminate) > .auxbar {
  background-image: linear-gradient(
      90deg,
      hsla(0, 0%, 100%, 0.9),
      hsla(0, 0%, 100%, 0.9)
    ),
    linear-gradient(90deg, #원하는컬러, #원하는컬러) !important;
}

이틀 간의 고생 끝에 결국 성공했다!!

firebaseui (EmailAuthProvider,GoogleAuthProvider) 

 

Comments