๐ ํ์๊ฐ์ /๋ก๊ทธ์ธ ํ์ด์ง
ํ์๊ฐ์ ๊ณผ ๋ก๊ทธ์ธ์ ํตํด์ ๊ฐ์ธ ๋ณ๋ก ์ฑ์ฉ๊ณต๊ณ ๋ฅผ ๊ด๋ฆฌํ๊ธฐ ์ํด์ ํ์๊ฐ์ /๋ก๊ทธ์ธ ํ์ด์ง๋ฅผ ๋ง๋ค์๋ค. ํ์ด์ง๋ฅผ ๋ง๋ค๋ฉด์ ๊ณ ๋ฏผํ๋ ๊ณผ์ ์ ์ ๋ฆฌํด๋ณด์๋ค.
๐จ ๋์์ธ
๋์์ธ์ ์ํฐ๋์ ๋ก๊ทธ์ธ/ํ์๊ฐ์ ํ์ด์ง๋ฅผ ์ฐธ๊ณ ํ๋๋ฐ ์ปจํ ์ธ ๋ฅผ ๋ชจ๋ฐ์ผ ์ฌ์ด์ฆ์ธ 400px๋ฅผ ๊ธฐ์ค์ผ๋ก ๋ง๋ค์ด ๋ชจ๋ฐ์ผ๊ณผ ๋ฐ์คํฌ ํ์์ ๋์ผํ๊ฒ ๋ณด์ผ ์ ์๊ฒ ํ๋ค. ์ด๊ฒ์ ์ฐธ๊ณ ํด ๋๊ฐ์ด 400px๋ฅผ ๊ธฐ์ค์ผ๋ก ๋์์ธํ๊ณ , OAuth๋ฅผ ์ฐ๊ฒฐํด ๊ฐํธํ๊ฒ ์ด์ฉํ ์ ์๊ฒ ํด๋น ํ๋ซํผ๋ค์ ์๋์ ์ถ๊ฐํ๋ค.
[์ํฐ๋์ ํ์๊ฐ์ /๋ก๊ทธ์ธ ํ์ด์ง]
  
    
    
[๋ชจ์ผ์ก์ ํ์๊ฐ์
/๋ก๊ทธ์ธ ํ์ด์ง]
  
    
    
๐Firebase์ Auth ๊ธฐ๋ฅ
๋ก๊ทธ์ธ/ํ์๊ฐ์ ์ ์์ง ์ง์  ๊ตฌํํ๊ธฐ ์ํด์๋ node js์ ๋ํ ๊ณต๋ถ๊ฐ ๋ ํ์ํด, ์ฐ์ ์ firebase๋ก ๊ตฌํํ๋ค. firebase ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํด์ email/password, google, github ์ธ ๊ฐ์ง ๋ฐฉ์์ ์ด์ฉํด ๊ธฐ๋ฅ์ ๊ตฌํํ๋ค.
AuthService ๋ชจ๋ ์ ์
๋จผ์  firebase๋ฅผ ์ดํ์ database์๋ ์ด์ฉํ ์์ ์ด๊ธฐ ๋๋ฌธ์, ๊ณตํต์ผ๋ก ์ ๋ฌํ ์ ์๊ฒ _app.tsx์์ firebaseApp์ ๋ง๋ค๊ณ ๊ฐ๊ฐ์ ๋ชจ๋์ ์ ๋ฌํ ์ ์๊ฒ ํ๋ค.
const config: ConfigType = {
  apiKey: process.env.NEXT_PUBLIC_API_KEY || "",
  authDomain: process.env.NEXT_PUBLIC_AUTH_DOMAIN || "",
  projectId: process.env.NEXT_PUBLIC_PROJECT_ID || "",
  storageBucket: process.env.NEXT_PUBLIC_STORAGE_BUCKET || "",
  appId: process.env.NEXT_PUBLIC_APP_ID || "",
  measurementId: process.env.NEXT_PUBLIC_MEASUREMENT_ID || "",
}
function MyApp({ Component, pageProps }: AppProps) {
  const app = initializeApp(config)
  const authService = new AuthServiceImpl(app)
  const { push } = useRouter()
  return (
    <>
      <AuthProvider authService={authService}>
        <Component {...pageProps} />
      </AuthProvider>
    </>
  )
}
export default MyAppํ์๊ฐ์ /๋ก๊ทธ์ธ ๋ก์ง์ ๋ด๋ ๋ชจ๋์ธ Authservice๋ ์๋์ ๊ฐ์ด interface์ interface๋ฅผ ์คํํ๋ AuthserviceImpl class๋ฅผ ๋ง๋ค์๋ค.
/Authtypes.ts
export interface AuthService {
  signIn: (email: string, password: string) => Promise<UserCredential>;
  signUp: (email: string, password: string) => Promise<UserCredential>;
  OAuthSignIn: (platfrom: OAuthType) => Promise<UserCredential>;
  signOut: () => Promise<void>;
}
//Authservice.ts
export class AuthServiceImpl implements AuthService {
  googleProvider: GoogleAuthProvider;
  githubProvider: GithubAuthProvider;
  auth: Auth;
  constructor(private app: FirebaseApp) {
    this.googleProvider = new GoogleAuthProvider();
    this.githubProvider = new GithubAuthProvider();
    this.auth = getAuth(this.app);
  }
  signIn(email: string, password: string) {
    return signInWithEmailAndPassword(this.auth, email, password);
  }
  signUp(email: string, password: string) {
    return createUserWithEmailAndPassword(this.auth, email, password);
  }
  OAuthSignIn(platform: OAuthType): Promise<UserCredential> {
    const provider = this[`${platform}Provider`];
    return signInWithPopup(this.auth, provider);
  }
  signOut() {
    return signOut(this.auth);
  }
}SignIn๊ณผ SignUp
ํ์๊ฐ์ /๋ก๊ทธ์ธ ํ์ด์ง ๋ด AuthService๊ฐ ์ฌ์ฉ๋ ๊ณณ์ AuthForm ์ปดํฌ๋ํธ๋ก ํ์๊ฐ์ ๊ณผ ๋ก๊ทธ์ธ ๋ชจ๋์์ ๋์ผํ UI๋ฅผ ์ฌ์ฉ๋๊ฒ ํ๋ค. ํ๊ณณ์์ ํ์๊ฐ์ /๋ก๊ทธ์ธ ๋ ๊ฐ์ง ์กฐ๊ธ ๋ค๋ฅธ ๋ก์ง์ ์ฒ๋ฆฌํด์ผ ํ๊ธฐ ๋๋ฌธ์ ์ปดํฌ๋ํธ ๋ด๋ถ๊ฐ ๋ณต์กํ๋ค๊ณ ์๊ฐํ๋ค.
ํ์๊ฐ์ ํ์ด์ง์์ ์ฑ๊ณต ์์๋ loginํ์ด์ง๋ก ์ด๋ํ๋ฉด์ ์ด์ ์ ์๋ ์๋ฌ ๋ฉ์์ง๋ ์ง์์ค์ผ ํ๊ณ , ๋ก๊ทธ์ธ ํ์ด์ง๋ ์ฑ๊ณต์์ ๋ฐ์ userData๋ฅผ ์ด์ฉํด accessToken์ ๋ฐ์ ์ ์์ด ์ฐ์ localStorage์ ์ ์ฅํ๊ณ ๋ฉ์ธ ํ์ด์ง๋ก ์ด๋ํด์ผ ํ๋ค.
//AuthForm.tsx
...
export default function AuthForm({ isLogin }: AuthFormProps) {
	...
  const authService = useAuth();
  const { push } = useRouter();
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const { email, password } = userInfo;
    if (isLogin) {
      authService
        .signIn(email, password)
        .then((userData: UserCredential) => {
          return userData.user.getIdToken();
        })
        .then((token) => {
          localStorage.setItem(AccessToken, token);
          push('/');
        })
        .catch((error) => setMessage(error.message));
    } else {
      authService
        .signUp(email, password)
        .then(() => {
          push('/login');
          setMessage('');
        })
        .catch((error) => setMessage(error.message));
    }
  };
  return (
    <Layout action="submit" onSubmit={handleSubmit} isActive={!isInActive}>
      <AuthInput
        name={EMAIL_INPUT.name}
        text={userInfo.email}
        title={'์ด๋ฉ์ผ'}
        placeholder={EMAIL_INPUT.placeholder}
        dispatch={dispatch}
      />
      <AuthInput
        name={PASSWORD_INPUT.name}
        text={userInfo.password}
        title={'๋น๋ฐ๋ฒํธ'}
        placeholder={PASSWORD_INPUT.placeholder}
        dispatch={dispatch}
      />
      {message && <ErrorMessage message={message} />}
      <button type="submit" disabled={isInActive}>
        {name}
      </button>
    </Layout>
  );
}๋ก์ง์ด promise chaining์ผ๋ก ์ด์ด์ง๋ค ๋ณด๋ ํ๋ ์ผ์ ๋นํด ๋ก์ง์ด ์ฐจ์งํ๋ ์ฝ๋๊ฐ ๋ง์ด ๋ณด์ฌ, ๋ฐ๋ก ๋ถ๋ฆฌํ๊ณ ์ ํ๋ค. ์ ๋ฆฌํ ๋ custom Hook์ ์ด์ฉํ๋ ค ํ์ง๋ง custom Hook์ handleSubmit ๋ด๋ถ์์ ์ฌ์ฉํ ์ ์๊ธฐ ๋๋ฌธ์ "ํจ์"๋ก ๋ถ๋ฆฌํด ์ ๋ฆฌํ๋ค.
//logic.ts
export const login = async (
  userInfo: UserInfoType,
  authService: AuthService,
  router: NextRouter,
  setMessage: React.Dispatch<React.SetStateAction<string>>
) => {
  const { push } = router;
  const { email, password } = userInfo;
  try {
    const userData = await authService.signIn(email, password);
    const token = await userData.user.getIdToken();
    localStorage.setItem(AccessToken, token);
    push('/');
  } catch (error) {
    const loginError = error as { message: string };
    setMessage(loginError?.message);
  }
};
export const register = async (
  userInfo: UserInfoType,
  authService: AuthService,
  router: NextRouter,
  setMessage: React.Dispatch<React.SetStateAction<string>>
) => {
  const { push } = router;
  const { email, password } = userInfo;
  try {
    await authService.signUp(email, password);
    push('/login');
  } catch (error) {
    const registerError = error as { message: string };
    setMessage(registerError?.message);
  }
};
//AuthForm.tsx
export default function AuthForm({ isLogin }: AuthFormProps) {
  const [message, setMessage] = useState('');
  const [userInfo, dispatch] = useReducer(authReducer, initialState);
  const isInActive = !userInfo.emailValid || !userInfo.passwordValid;
  const authService = useAuthService();
  const router = useRouter();
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (isLogin) {
      login(userInfo, authService, router, setMessage);
    } else {
      register(userInfo, authService, router, setMessage);
    }
  };
  const name = isLogin ? '๋ก๊ทธ์ธ' : 'ํ์๊ฐ์
';
  return (
 	...
  );
}OAuthSignIn
firebase์ OAuth ์๋น์ค๋ฅผ ์ด์ฉํ๊ธฐ ์ํด์ ํด๋น ํ๋ซํผ์ provider์ ์ฐ๊ฒฐํ๊ณ signInWithPopup ๋ฉ์๋๋ฅผ ์ด์ฉํ๋ฉด ๋๋๋ฐ, provider์ ์ข ๋ฅ์ ๊ด๊ณ์์ด ํ๋์ ํจ์๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด ์ ๋ฌ๋ฐ์ platform์ provider์ ์ฐ๊ฒฐ๋ ์ ์๊ฒ ๋ก์ง์ ๊ตฌ์ฑํ๋ค. AuthService์ ์ฐ๊ฒฐํ๊ธฐ ์ํด์ buttonํ๊ทธ์ name์ผ๋ก ์ธ์๋ฅผ ์ ๋ฌ๋๊ฒ ํ๋ค.
//AuthService.ts
export class AuthServiceImpl implements AuthService {
  googleProvider: GoogleAuthProvider
  githubProvider: GithubAuthProvider
  auth: Auth
  constructor(private app: FirebaseApp) {
    this.googleProvider = new GoogleAuthProvider()
    this.githubProvider = new GithubAuthProvider()
    this.auth = getAuth(this.app)
  }
  OAuthSignIn(platform: OAuthType): Promise<UserCredential> {
    const provider = this[`${platform}Provider`]
    return signInWithPopup(this.auth, provider)
  }
}
//PlatformBtns.tsx
export const OAuthLogin = async (
  name: OAuthType,
  authService: AuthService,
  router: NextRouter
) => {
  const { push } = router
  try {
    const userData = await authService.OAuthSignIn(name)
    const token = await userData.user.getIdToken()
    localStorage.setItem(AccessToken, token)
    push("/")
  } catch (error) {
    console.log(error)
  }
}
export default function PlatformBtns() {
  const authService = useAuthService()
  const router = useRouter()
  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
    const { name } = e.currentTarget
    if (name === PLATFORM.GOOGLE || name === PLATFORM.GITHUB) {
      OAuthLogin(name, authService, router)
    }
  }
  return (
    <Wrapper>
      <button name={PLATFORM.GOOGLE} onClick={handleClick}>
        ...
      </button>
      <button name={PLATFORM.GITHUB} onClick={handleClick}>
        ...
      </button>
    </Wrapper>
  )
}Logout
Navbar์ ๋ก๊ทธ์์ ๋ฒํผ์ ์ถ๊ฐํด ๊ฐ๋จํ๊ฒ ์ฒ๋ฆฌํ ์ ์์๋ค.
export default function Navbar() {
  const { push } = useRouter()
  const authService = useAuthService()
  const onSignOut = () => {
    authService
      .signOut()
      .then(() => {
        localStorage.removeItem(AccessToken)
        push("/login")
      })
      .catch(error => console.log(error))
  }
  return (
    <Wrapper>
      <Layout>
        <Link href="/">๋ชจ์ผ์ก</Link>
        <Btns>
          <button onClick={onSignOut}>๋ก๊ทธ์์</button>
        </Btns>
      </Layout>
    </Wrapper>
  )
}์์ง ์์ธ ์ฒ๋ฆฌ, token๊ด๋ฆฌ, redirection์ ํด์ฃผ์ด์ผ ํ์ง๋ง ์ฐ์ ์ ๋ฐ์ ์ผ๋ก ์์ฑํ ํ์ ๋ค์ ๋์์์ ์์ ํ๊ณ ์ ํ๋ค. ๋จ์ ํฐ ๊ธฐ๋ฅ๋ค์ Database, ์๊ฒฉ์กฐ๊ฑด/์ฐ๋์ฌํญ ์ฒดํฌ ๊ธฐ๋ฅ, ํฌ๋กค๋ฌ ํ๋ก๊ทธ๋จ์ ์ํํด ์ค ์๋ฒ ๊ธฐ๋ฅ์ด ์๋ค.