HEROPY
Tech
Firebase 인증으로 Sapper(Svelte)에 구글 로그인 구현
{{ scrollPercentage }}%

Firebase 인증으로 Sapper(Svelte)에 구글 로그인 구현

firebasesapper

Firebase 인증을 통해 구글 및 페이스북, 트위터, GitHub 등의 계정으로 쉽게 로그인할 수 있는 서비스를 만들 수 있습니다.
최근 관심이 있는 Sapper(Svelte)를 사용해 간단하게 구글 로그인을 구현해 보려고 합니다.

예제 결과는 위 GitHub Repository 버튼을 클릭하면 확인할 수 있습니다.

$ npx degit "ParkYoungWoong/firebase-auth-with-sapper" YOUR_PROJECT_NAME

Sapper 설치

Sapper(Svelte APP makER)는 Svelte를 기반으로 동작하는 다음과 같은 기능들을 지원하는 프레임워크입니다.
React의 Next.js, Vue의 Nuxt.js와 같은 라인업이라고 볼 수 있습니다.

  • 라우팅
  • 서버 사이드 렌더링(SSR)
  • 자동 코드 분할
  • 오프라인 지원(Service Worker)
  • 고급 프로젝트 구조 관리

다음과 같이 Degit을 이용해 새로운 프로젝트를 생성합니다.

$ npx degit "sveltejs/sapper-template#rollup" YOUR_PROJECT_NAME
$ cd YOUR_PROJECT_NAME
$ npm install

설치 후 Sapper는 다음과 같은 구조를 가집니다.

Sapper structure

Firebase 프로젝트 생성

Firebase는 인증, 실시간 데이터베이스, 스토리지, 푸시, 호스팅 등 다양한 기능을 지원하는 통합 플랫폼입니다.
Firebase 페이지에 접속에 로그인 후 다음과 같이 진행합니다.

Create Firebase project

1) Firebase 콘솔에 로그인한 후 새로운 프로젝트 생성합니다.

Create Firebase project

2) 프로젝트의 이름을 지정하는 등 ‘프로젝트 만들기’의 단계를 수행합니다.

Create Firebase project

3) 새로운 프로젝트가 준비되면 ‘계속’을 눌러 대시보드로 접근합니다.

Create Firebase project

4) ‘Authentication’의 ‘로그인 방법’에서 제공 업체로 ‘Google’을 선택합니다.

Create Firebase project

5) ‘사용 설정’을 활성화하고 ‘프로젝트 지원 이메일’을 설정한 후 저장합니다.

Create Firebase project

6) 제공 업체 상태가 ‘사용 설정됨’으로 표시되면 준비가 끝났습니다.

Firebase 설치 및 초기화

전역으로 firebase 명령어를 사용하기 위해 다음과 같이 ‘firebase-tools’을 전역 설치합니다.

$ npm i -g firebase-tools

설치가 완료되었다면 다음으로 구글에 로그인해야 합니다.

이 명령어는 로컬 머신을 Firebase에 연결하고 Firebase 프로젝트에 대한 액세스 권한을 부여합니다.

$ firebase login

다음은 Firebase 프로젝트를 초기화해야 합니다.
프로젝트 디렉터리의 루트에서 다음 명령어를 실행합니다.

$ firebase init

Firebase Init

원하는 Firebase CLI 기능을 선택(당장은 ‘인증’만 테스트하기 때문에 아무 기능이나 선택)하고,
위에서 생성한 Firebase 프로젝트(auth-test-with-sapper)를 연결합니다.

Firebase 호스팅에 필요한 firebase.json 파일을 생성합니다.
Firebase가 찾는 디렉터리의 기본 이름은 ‘public’입니다.
나중에 firebase.json 파일을 직접 수정하여 공개 디렉터리를 설정할 수도 있습니다.

Firebase 내 앱 추가

Sapper 프로젝트에 Firebase 인증을 적용하기 전에,
우선 다음과 같이 Firebase가 지원할 내 앱(Sapper 프로젝트)의 플랫폼을 선택해 SDK 구성을 얻어야 합니다.

Firebase Add App

1) ‘프로젝트 설정’으로 접근합니다.

Firebase Add App

2) ‘내 앱’에서 ‘Web’으로 플랫폼을 선택합니다.

Firebase Add App

2) 혹은 ‘Project Overview’에서 바로 선택할 수도 있습니다.

Firebase Add App

3) 내 웹앱의 이름을 설정합니다.
(하나의 Firebase 프로젝트 내 여러 앱을 추가할 수 있습니다)

Firebase Add App

4) Firebase SDK 추가를 위한 코드가 발급됩니다.
(공개 가능한 구성입니다)

보안 규칙과 관련된 내용은 Firebase Security Rules를 참고하세요.

이제 인증(Auth)을 위한 SDK를 연결하기 위해 위 구성에 적혀있는 것처럼 https://firebase.google.com/docs/web/setup#available-libraries 로 이동합니다.

Available Libraries

우리는 인증을 사용하기 때문에 firebase-auth.js를 연결할 것입니다.
우리의 Sapper 프로젝트의 src/template.html에 다음과 같이 코드를 추가합니다.

<!-- src/template.html -->

<script src="https://www.gstatic.com/firebasejs/7.6.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.6.1/firebase-auth.js"></script>

<script>
  // Your web app's Firebase configuration
  var firebaseConfig = {
    apiKey: "AIzaSyDEZzrvW7PlAgDmQWtw_hecYHTLi7NBWxA",
    authDomain: "auth-test-with-sapper.firebaseapp.com",
    databaseURL: "https://auth-test-with-sapper.firebaseio.com",
    projectId: "auth-test-with-sapper",
    storageBucket: "auth-test-with-sapper.appspot.com",
    messagingSenderId: "148687957530",
    appId: "1:148687957530:web:919b0b6018d8bbde5d2ba3"
  };
  // Initialize Firebase
  firebase.initializeApp(firebaseConfig);
</script>

이 프로젝트는 클라이언트 정적 파일에서 인증(로그인)만 사용하기 때문에 Sapper 서버에서 사용하지 않도록 CDN으로 설치합니다.
https://stackoverflow.com/questions/56315901/how-to-import-firebase-only-on-client-in-sapper

컴포넌트 및 스토어

구글 로그인을 위해 다음과 같이 컴포넌트와 스토어 파일을 생성할 것입니다.

Sapper structure

user 스토어

프로젝트 내 전역으로 현재 사용자(currentUser)에 접근하기 위해 쓰기 가능하도록 다음과 같이 작성합니다.

// src/store/user.js

import { writable } from 'svelte/store'

export const currentUser = writable(null)

SignInWithGoogle 컴포넌트

구글 로그인을 위한 컴포넌트를 다음과 같이 작성합니다.
Nav(Header)의 오른쪽에 위치하도록 스타일을 정의합니다.

<!-- src/components/SignInWithGoogle.svelte -->

<script>
  // 스토어에서 현재 사용자를 가져옵니다.
  // `currentUser`는 쓰기용 객체 데이터(writable)이기 때문에 `$` 접두사를 사용해 스토어를 참조해야 합니다.
  // `$currentUser`
  import { currentUser } from '../store/user';

  // 구글로 로그인
  const signInWithGoogle = async () => {
    await firebase.auth().signInWithPopup(
      new firebase.auth.GoogleAuthProvider()
      // 페이스북이나 트위터 등으로도 로그인할 수 있습니다.
      // new firebase.auth.FacebookAuthProvider()
      // new firebase.auth.TwitterAuthProvider()
      // new firebase.auth.GithubAuthProvider()
    );
  };

  // 로그아웃
  const signOut = async () => {
    await firebase.auth().signOut();
  };
</script>

<div class="sign-in-with-google">
  {#if $currentUser}
    <div class="user">
      <div class="user__name">{$currentUser.displayName}</div>
      <div class="user__photo">
        <img
          src={$currentUser.photoURL}
          alt={$currentUser.displayName}
          width="28" />
      </div>
    </div>
    <div
      class="sign-out"
      on:click={signOut}>
      Sign out
    </div>
  {:else}
    <div
      class="sign-in"
      on:click={signInWithGoogle}>
      Sign in with Google
    </div>
  {/if}
</div>

<style>
  .sign-in-with-google {
    height: 56px;
    position: absolute;
    top: 0;
    right: 1em;
    display: flex;
    align-items: center;
  }
  .sign-in-with-google .user {
    display: flex;
    align-items: center;
  }
  .sign-in-with-google .user .user__name {
    font-size: 13px;
    font-weight: 700;
    margin-right: 6px;
  }
  .sign-in-with-google .user .user__photo {
    width: 28px;
    height: 28px;
    border: 1px solid lightgray;
    border-radius: 50%;
    overflow: hidden;
    margin-right: 10px;
  }
  .sign-in-with-google .sign-out {
    font-size: 13px;
    cursor: pointer;
  }
  .sign-in-with-google .sign-out:hover {
    text-decoration: underline;
  }
</style>

User Information

구글 로그인 후 반환된 사용자 객체

작성한 SignInWithGoogle.svelte 컴포넌트는 src/components/Nav.svelte에서 다음과 같이 사용합니다.
nav 요소에 position 속성이 있어야 정상적으로 배치될 수 있습니다.

<!-- src/components/Nav.svelte -->

<script>
  import SignInWithGoogle from './SignInWithGoogle.svelte';
  // ...
</script>

<style>
  nav {
    /* ... */
    position: relative;
  }
</style>

<nav>
  <!-- ... -->
  <SignInWithGoogle />
</nav>

UserObserver 컴포넌트

사용자의 상태(로그인, 로그아웃)를 감지하기 위한 컴포넌트를 다음과 같이 작성합니다.

<!-- src/components/UserObserver.svelte -->

<script>
  import { onMount } from 'svelte';
  import { currentUser } from '../store/user';

  // Client 환경에서 동작하도록 `onMount` 훅에서 실행합니다.
  onMount(() => {
    // 로그인 사용자의 상태 변환(로그인, 로그아웃)에 따라 콜백을 실행합니다.
    firebase.auth().onAuthStateChanged(user => {
      if (user) {
        // 쓰기 가능한 객체이기 때문에 바로 사용자를 할당할 수 있습니다.
        $currentUser = user
      } else {
        // 사용자가 없는 경우 초기화합니다.
        $currentUser = null
      }
    });
  });
</script>

작성한 UserObserver.svelte 컴포넌트는 항시 동작할 수 있도록 src/routes/_layout.svelte에서 다음과 같이 사용합니다.

<!-- src/routes/_layout.svelte -->

<script>
  import UserObserver from '../components/UserObserver.svelte';
  // ...
</script>

<UserObserver />

Sign In

FirebaseUI로 로그인 UI 추가

FirebaseUI는 Firebase 인증 UI를 제공하는 공식 라이브러리입니다.
별도의 UI 제작 없이 손쉽게 구글, 페이스북, 트위터 등의 로그인 UI를 제작할 수 있습니다.

먼저 다음과 같이 FirebaseUI를 설치합니다.

<!-- src/template.html -->

<!-- ... -->
<script src="https://www.gstatic.com/firebasejs/ui/4.3.0/firebase-ui-auth.js"></script>
<link type="text/css" rel="stylesheet" href="https://www.gstatic.com/firebasejs/ui/4.3.0/firebase-ui-auth.css" />
<!-- ... -->

SignInWithGoogle.svelte 컴포넌트를 FirebaseUI가 적용될 수 있도록 다음과 같이 수정합니다.

<!-- src/components/SignInWithGoogle.svelte -->

<script>
  import { onMount, tick } from 'svelte'
  import { currentUser } from '../store/user';

  // 컴포넌트가 마운트되면 현재 사용자를 체크하고,
  // 사용자가 없으면 구글 로그인 버튼을 생성합니다.
  onMount(() => {
    if (!$currentUser) {
      createLoginButton();
    }
  });

  // 로그인 버튼 생성
  function createLoginButton() {
    // FirebaseUI config.
    const uiConfig = {
      signInOptions: [
        firebase.auth.GoogleAuthProvider.PROVIDER_ID,
        // 페이스북이나 트위터 등의 로그인 UI도 같이 제공할 수 있습니다.
        // firebase.auth.FacebookAuthProvider.PROVIDER_ID,
        // firebase.auth.TwitterAuthProvider.PROVIDER_ID,
        // firebase.auth.GithubAuthProvider.PROVIDER_ID
      ],
      callbacks: {
        signInSuccessWithAuthResult: (authResult, redirectUrl) => false
      }
    };

    // FirebaseUI 초기화
    const ui = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(firebase.auth());
    // 해당 요소를 검색(아이디 선택자)하고 UI를 렌더링합니다.
    ui.start("#firebaseui-auth-container", uiConfig);
  }

  // ...

  const signOut = async () => {
    await firebase.auth().signOut();
    // 현재 사용자 상태 변화(로그아웃)에 따라 렌더링을 대기합니다.
    await tick();
    // 로그인 버튼을 생성합니다.
    createLoginButton();
  };
</script>

<div class="sign-in-with-google">
  <!-- ... -->
  {:else}
    <!-- `.sign-in`에 있던 클릭 이벤트를 제거해야 합니다. -->
    <div class="sign-in">
      <!-- FirebaseUI가 렌더링되는 위치 -->
      <div id="firebaseui-auth-container"></div>
    </div>
  {/if}
</div>

<style>
  /* ... */

  /* reset firebaseui margin & padding */
  /* 현재 화면에 맞지 않는 스타일들을 초기화합니다. */
  /* Svelte에서는 유효범위 없이 스타일을 선언하려면 :global(선택자) 수정자(Modifier)를 사용해야 합니다. */
  :global(.firebaseui-card-content) {
    padding: 0 !important;
  }
  :global(.firebaseui-idp-list),
  :global(.firebaseui-list-item) {
    margin: 8px 0 !important;
  }
</style>

Sign Out

FirebaseUI에서 제공하는 구글 로그인 버튼

참고 자료(References)

https://firebase.google.com/docs/web/setup

공지 이미지
이 내용을 72시간 동안 안 보고 싶어요!?