게으른개발너D

Authentication 1 - Setup, Create Account, Protected Routes 본문

개발/Firebase

Authentication 1 - Setup, Create Account, Protected Routes

lazyhysong 2023. 12. 6. 19:10

1. Setup

파이어베이스 안에 있는 기능들은 너무 많아서 처음엔 그 제품 기능들이 다 비활성화 되어있다.

그래서 우리가 필요한 것들을 직접 선택해서 활성화 시켜줘야한다.

먼저 firebase 콘솔과 우리 코드 양쪽에서 Authentication을 활성화 시켜주자.

 

방법은 항상 같다.

먼저 firebase 콘솔에서 필요한 기능을 활성화한 다음 프로젝트 코드에서 initialize하면 된다.

 

1.1 firebase 콘솔 설정

project overview (프로젝트 개요)로 이동해서 authentication을 선택하자.

get started (시작하기)를 누르자.

사용자가 어떻게 로그인하기를 원하는지 우리에게 묻고있다.

 

익명도 있지만 스팸 사용자가 많이 생길 수도 있어서 추천하지는 않는다.

우리는 일단 이메일/비밀번호로 해볼 것이다.

이메일/비밀번호로 들어가서 사용설정을 활성화 한 후 저장을 누른다.

 

이제 Authentication에 이메일/비밀번호가 생겼다!.

User 탭에 들어가보면 아무것도 없지만 곧 생길거다!

 

콘솔 설정을 해줬으니 이제 우리 프로젝트 코드로 가자

 

 

1.2 프로젝트 앱 코드 설정

firebase.ts 파일 어딘가에 우리가 authentication을 원한다는 코드를 작성해야한다.

먼저 auth라는 변수를 만들고 getAuth라는 함수를 불러온다. getAuth 함수는 'firebase/auth'에 서 import할 수 있다.

그리고 우리 app에 대한 인증을 사용하고 싶다고 설정한다. getAuth의 인자 부분에 app을 넣는다.

// firebase.ts

import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';

const firebaseConfig = {
  ...
};

const app = initializeApp(firebaseConfig);

export const auth = getAuth(app);

설정코드에서 firebaseConfig를 보면 도메인, api key 등 여러가지 키 값들이 포함된 config 개체가 주어져있다,

이러한 config 옵션을 통해서 app을 생성하고 그 app에 대한 인증 서비스를 사용하고 싶다고 선언한 것이다.

auth는 인증을 해야하는 다양한 곳에 쓰일 예정이므로 앞에 export를 넣는다.

 

이제 이전 게시글 App.tsx에서 작성했던 firebase가 로그인한 사용자를 확인하는 부분을 마무리 지을 차례이다.

await를 하고 firebase.ts에서 정의한 auth를 불러온 다음 .(dot)을 찍으면 엄청 많은 것들이 보여질 것이다.

예를들어 현재 사용자 값을 받아올 수도 있다.

우리는 여기서 authStateReady를 선택한다.

import { auth } from './firebase';

...

const init = async () => {
  // wait for firebase
  await auth.authStateReady();
  setIsLoading(false);
};

우리가 할 일은 인증 상태가 준비되었는지를 기다리는 것인데, authStateReady는 최초 인증 상태가 완료될 때 실행되는 Promise를 return한다.

즉, firebase가 쿠키과 토큰을 읽고 백엔드와 소통해서 로그인 여부를 확인하는 동안 기다리겠다는 뜻이다.

 

이렇게 우리 프로젝트에 인증과정을 추가했다! 😇😇

// App.tsx

import { auth } from './firebase';

function App() {
  const [isLoading, setIsLoading] = useState(true);

  const init = async () => {
    await auth.authStateReady();
    setIsLoading(false);
  };

  useEffect(() => {
    init();
  }, []);

  return (
    <Wrapper>
      <GlobalStyles />
      <GridStyles />
      {isLoading ? <LoadingScreen /> : <RouterProvider router={router} />}
    </Wrapper>
  );
}

 

 

 

 

2. Forms and UI

css로 계정생성 페이지 만들기를 할거다.

그 다음에 react.js나 event listener같은 걸 사용한 input 로직을 만들고, firebase를 접합시킬 것이다!

 

가입화면인 createAccount.tsx 에 form을 넣어주자.

// createAccount.tsx

import styled from "styled-components";

const Wrapper = styled.div``;
const Form = styled.form``;
const Input = styled.input``;

export default function CreateAccount() {
  return <Wrapper>
    <Form>
      <Input name="name" placeholder="Name" type="text" required />
      <Input name="email" placeholder="Email" type="email" required />
      <Input name="password" placeholder="Password" type="password" required />
      <Input type="submit" value="Create Account" />
    </Form>
  </Wrapper>;
}

가입 폼은 이름, 이메일, 비밀번호를 받을 거다.

 

form와 input에 각각 state와 함수를 넣어주자.

// createAccount.tsx

export default function CreateAccount() {
  const [isLoading, setIsLoading] = useState(false);
  
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  
  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { target: { name, value } } = e;
    if (name === 'name') {
      setName(value);
    } else if (name === 'email') {
      setEmail(value);
    } else if (name === 'password') {
      setPassword(value);
    }
  };

  const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    try {
      // create an account
      // set the name of the user.
      // redirect to the home page
    } catch (e) {
      // setError
    } finally {
      setIsLoading(false);
    }
  };
  
  return (
    <Wrapper>
      <Form onSubmit={onSubmit}>
        <Input onChange={onChange} name="name" value={name} placeholder="Name" type="text" required />
        <Input onChange={onChange} name="email" value={email} placeholder="Email" type="email" required />
        <Input onChange={onChange} name="password" value={password} placeholder="Password" type="password" required />
        <Input type="submit" value={isLoading ? 'Loading...' : 'Create Account'} />
      </Form>
    </Wrapper>
  );
}

 

물론 저렇게 쓰지말고 react-hook-form을 쓰면 더 간단한 코드가 된다.

 

isLoading state를 넣은 이유는 로딩일 경우 버튼을 Loading... 으로, 로딩이 끝났을 경우 Create Account로 변경해주기 위함이다.

 

 

 

 

3. Create Account

3.1 계정 생성하기

먼저 유효성 검사를 위해 name, email, password가 비어있을 경우 return을 하자.

// createAccount.tsx

  const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (isLoading || name === '' || email === '' || password === '') {
      return;
    }
    try {
      // create an account
      // set the name of the user.
      // redirect to the home page
    } catch (e) {
      // setError
    } finally {
      setIsLoading(false);
    }
  };

 

이제 onSubmit의 try 블록에서 await를 하고 email과 password를 이용해서 사용자를 생성해 볼거다.

await를 하고 createUser를 입력하다보면 이미 관련 function인 createUserWithEmailAndPassword가 있다.

지정한 email과 password와 연결된 새 사용자 계정을 만들어주는 함수이다.

인자로는 auth와 입력한 email, password를 순서대로 넣는다.

// createAccount.tsx

import { createUserWithEmailAndPassword } from 'firebase/auth';
import { useState } from 'react';
import styled from 'styled-components';
import { auth } from '../../firebase';

...

  const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (isLoading || name === '' || email === '' || password === '') {
      return;
    }
    try {
      setIsLoading(true);
      const credentials = await createUserWithEmailAndPassword(auth, email, password);
    } catch (e) {
      // setError
    } finally {
      setIsLoading(false);
    }
  };

 

계정 생성에 성공하지 못하면 catch로 넘어갈 것이다.

오류가 발생할 경우는 해당 이메일이 이미 계정에 있거나 비밀번호가 유효하지 않은 경우이다.

firebase가 비밀번호가 강력한지 알아서 유효성 검사도 해준다!

 

생성한 사용자 계정을 credentials라는 변수에 담았다.

credentials 뒤에 .을 찍으면 user가 있다. 바로 user 정보를 얻을 수 있다는 뜻이다!

 

 

3.2 사용자 이름 넣기

이제 해야할 것은 가입폼에 name이 있으니 user 정보에 사용자 이름을 넣어야한다.

 

firebase가 생성하는 계정은 이름과 작은 아바타 이미지의 url을 가지는 미니 프로필을 가지게 된다.

 

await를 한 후 updateProfile 함수를 적어 사용자 프로필을 업데이트하자.

updateProfile은 사용자(user)가 필요하다.

따라서 updateProfile에 방금 생성한 유저 credentials.user를 넣는다.

그리고 displayName에 가입폼에서 얻은 name을 넣는다.

// createAccount.tsx

import { createUserWithEmailAndPassword, updateProfile } from 'firebase/auth';
import { useState } from 'react';
import styled from 'styled-components';
import { auth } from '../../firebase';

...

  const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (isLoading || name === '' || email === '' || password === '') {
      return;
    }
    try {
      setIsLoading(true);
      const credentials = await createUserWithEmailAndPassword(
        auth,
        email,
        password
      );
      console.log(credentials.user);
      await updateProfile(credentials.user, {
        displayName: name,
      });
    } catch (e) {
      // setError
    } finally {
      setIsLoading(false);
    }
  };

 

3.3 redirect 하기

다음으로 해야할 건 사용자를 원래의 타임라인으로, 즉 index 페이지로 보내는 것이다.

react-router-dom에 있는 useNavigate라는 hook을 사용하자.

 

// createAccount.tsx

import { createUserWithEmailAndPassword, updateProfile } from 'firebase/auth';
import { useState } from 'react';
import styled from 'styled-components';
import { auth } from '../../firebase';
import { useNavigate } from 'react-router-dom';

export default function CreateAccount() {
  const navigate = useNavigate();
  ...
  
  const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (isLoading || name === '' || email === '' || password === '') {
      return;
    }
    try {
      setIsLoading(true);
      const credentials = await createUserWithEmailAndPassword(
        auth,
        email,
        password
      );
      console.log(credentials.user);
      await updateProfile(credentials.user, {
        displayName: name,
      });
      navigate('/');
    } catch (e) {
      // setError
    } finally {
      setIsLoading(false);
    }
  };
  
 ...
 }

 

navigate('/') 를 적어 모든 과정이 실행된 다음 index 페이지로 이동하도록 만들었다.

 

 

참고로 firebase 콘솔에서 가입한 유저들의 리스트를 볼 수 있는데, 계정 비활성와, 암호 재설정, 계정 삭제 등을 할 수 있다.

 

 

 

4. Protected Routes

앞에서 만든 내용처럼 firebase를 사용해서 계정을 만들면 자동으로 로그인 된다.

이번에는 protected route라는 특별한 컴포넌트를 만들어보자.

components 폴더에 protectedRoute.tsx 파일을 만든다.

 

4.1 protected route component

바로바로! 로그인한 사용자는 protected route를 볼 수 있게 되고 로그인하지 않은 경우 로그인 또는 계정 생성 페이지로 리다이렉션해주는 컴포넌트다!

 

// protectedRoute.tsx

export default function ProtectedRoute({ children }: { children: React.ReactNode }) {
  return children;
}

먼저 reactNode에 있는 children을 prop으로 받아와서 리턴해준다.

이렇게 해는 이유는 protect 해야하는 페이지 텀포넌트를 ProtectedRoute 컴포넌트로 감싸주기 위해서다.

 

4.2 유저의 로그인 여부 확인

하지만 일단은 유저가 로그인 했는지 여부를 확인해야한다.

user라는 변수를 만든 다음 또 firebase 파일에 있는 auth를 사용할 거다.

auth를 적고 .을 찍으면 앞에서도 봤듯이 authentication 인스턴스에는 많은 것들이 있다.

이 중 우리가 사용할 것은 currentUser이다.

auth.currentUser;는 로그인 되어있는 user의 값을 주거나 null을 넘겨주는 방식으로 유저가 로그인했는지 여부를 알려준다.

// protectedRoute.tsx

import { Navigate } from 'react-router-dom';
import { auth } from '../firebase';

export default function ProtectedRoute({ children }: { children: React.ReactNode }) {
  const user = auth.currentUser;
  if (user === null) {
    return <Navigate to="/auth/login" />;
  }
  return children;
}

따라서 user가 null이라면 로그인 페이지로 리다이렉트 한다.

react-router-dom에 있는 Navigate 컴포넌트를 이용한다.

 

 

4.3 프로텍트할 route 감싸기

Router.tsx로 가서 Home과 Profile 페이지를 감싸자.

// Router.tsx

import ProtectedRoute from './components/protectedRoute';

export const router = createBrowserRouter([
  {
    path: '/',
    element: <Layout />,
    children: [
      {
        path: '',
        element: (
          <ProtectedRoute>
            <Home />
          </ProtectedRoute>
        ),
      },
      {
        path: 'profile',
        element: (
          <ProtectedRoute>
            <Profile />
          </ProtectedRoute>
        ),
      },
    ],
  },
  ...

 

이렇게 Protect하는 걸 반복하는 대신 더 쉽게 하려면 Home과 Profile의 부모인 Layout 컴포넌트를 Protect 하면 된다.

이렇게 바꿔주자.

// Router.tsx

import ProtectedRoute from './components/protectedRoute';

export const router = createBrowserRouter([
  {
    path: '/',
    element: (
      <ProtectedRoute>
        <Layout />
      </ProtectedRoute>
    ),
    children: [
      {
        path: '',
        element: <Home />,
      },
      {
        path: 'profile',
        element: <Profile />,
      },
    ],
  },
  ...

 

 

 

 

 

 


출처

노마드코더

https://nomadcoders.co/nwitter/

 

트위터 클론코딩 – 노마드 코더 Nomad Coders

React Firebase for Beginners

nomadcoders.co

 

Comments