11. [ 기능 ] FIRE-BASE AUTH 를 사용한, 로그인 작업 시작, with. env 를 사용하여, 중요한 개인정보(API KEY) 숨기기
열심히 React 를 다시하기 위한 프로젝트이다.
서버측 코드를 사용하고 싶으면, 따로 Node 를 만들던가, Nextjs 를 사용하겠지만 React 에 집중하기 위해
파이어 베이스를 사용하여, DB 와 AUTH 를 사용해보았다.
파이어 베이스 AUTH 에서는,
기본 E-mail 기능을 사용했으며
GoogleOAuth 도 시도해 볼 예정이다.
1. 파이어 베이스 AUTH
Firebase 인증 REST API
firebase.google.com
2. 환경변수를 사용하여, 파이어베이스의 API KEY 숨기기
환경변수를 사용하기 위해, dotenv 패키지를 사용해 보았다.
https://www.npmjs.com/package/dotenv
dotenv
Loads environment variables from .env file. Latest version: 16.0.3, last published: 3 months ago. Start using dotenv in your project by running `npm i dotenv`. There are 31974 other projects in the npm registry using dotenv.
www.npmjs.com
1. npm i dotenv
2. .env 파일을 root 디렉토리에 만든다.
3. 안에 "반드시" REACT_APP_ 으로 시작하는 이름으로 값을 만든다.
ex) REACT_APP_FIREBASE_API_KEY=asdasdasdasfdasdas
** "", ; 등의 기호를 사용하지 않음에 주의
4. 사용한다.
const API_KEY = process.env.REACT_APP_FIREBASE_API_KEY;
console.log(API_KEY);
.gitignore 안에 .env 를 넣어, gitHub에 올라가는 일이 없도록 하자
3. FireBase Auth DOCS 를 보고, 회원가입 기능 만들기
1. 회원가입 helper 함수 만들기
<helper / signup.tsx >
export const signupHandler = async (email: string, password: string) => {
const sendRequest = await fetch(
`https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=${process.env.REACT_APP_FIREBASE_API_KEY}`,
{
method: "POST",
body: JSON.stringify({ email, password, returnSecureToken: true }),
headers: {
"Content-Type": "application/json",
},
}
);
const responseData = await sendRequest.json();
return responseData; // 반드시 헬퍼함수에서, return 해야한다.
};
** 계속해서, 헬퍼함수 내에서 return 을 하지 않아서, response 를 받지 못하는 어처구니 없는 실수를 반복하고 있다.
** 꼭 retrun 하여, 밖에서 사용하도록 만들자.
* 환경변수를 사용하여, API 키를 숨기자
2. 회원가입 폼 만들기
< components / 5.sign-up / signup-form.tsx >
import styles from "./signup-form.module.css";
import { signupHandler } from "../../helper/signup-handler";
import { useRef, FormEvent } from "react";
const SignUpForm = () => {
const emailRef = useRef<HTMLInputElement>(null);
const passwordRef = useRef<HTMLInputElement>(null);
const submitHanlder = async (e: FormEvent) => {
e.preventDefault();
const email = emailRef.current!.value;
const password = passwordRef.current!.value;
const responseData = await signupHandler(email, password);
console.log(responseData);
};
return (
<div className={styles.main_div}>
<form onSubmit={submitHanlder}>
<div>
<label> 이메일을 입력하세요</label>
<input type={"email"} ref={emailRef} />
</div>
<div>
<label> 비밀번호를 입력하세요</label>
<input type={"password"} ref={passwordRef} />
</div>
<div>
<button> 제출</button>
</div>
</form>
</div>
);
};
export default SignUpForm;
3. 사용자들이 잘못된 정보나, 동일한 email 로 가입할 경우, 경고 표시하기
import styles from "./signup-form.module.css";
import { signupHandler } from "../../helper/signup-handler";
import { useRef, FormEvent, useState } from "react";
import { useHistory } from "react-router-dom";
const SignUpForm = () => {
const history = useHistory();
const emailRef = useRef<HTMLInputElement>(null);
const passwordRef = useRef<HTMLInputElement>(null);
const [error, setError] = useState<string>("");
const submitHanlder = async (e: FormEvent) => {
e.preventDefault();
const email = emailRef.current!.value;
const password = passwordRef.current!.value;
const responseData = await signupHandler(email, password);
if (responseData.idToken) { // idToken 이 res 되어왔다면, 올바른 것이다.
history.replace("/");
} else if (responseData.error.message === "EMAIL_EXISTS") { // 잘못되었다면, error.message 가 온다.
setError("이미 존재하는 이메일 입니다.");
} else if ( responseData.error?.message === "WEAK_PASSWORD : Password should be at least 6 characters") {
setError("패스워드가 6자리 이하입니다.");
}
};
return (
<div className={styles.main_div}>
...
<form 3onSubmit={submitHanlder} className={styles.signup_form}>
{error && <p className={styles.error}> {error}</p>}
<div>
<label> 이메일을 입력해주세요</label>
<input type={"email"} ref={emailRef} required />
</div>
<div>
<label> 비밀번호를 입력해주세요</label>
<input type={"password"} ref={passwordRef} required />
</div>
<div className={styles.button_div}>
<button className="Just_Click_Button_Default"> 제출</button>
</div>
</form>
</div>
);
};
export default SignUpForm;
// "파이어베이스는, 잘못된 정보를 입력하면, error 를 리턴해준다."
// 각각돌려주는 프로퍼티들은 DOCS 를 참고하거나, log로 뽑아보자.
4. 로그인하고, react-cookie 를 사용하여, 쿠키를 이용해 로그인상태를 유지시킨다. 로그아웃 버튼을 만든다.
https://www.npmjs.com/package/react-cookie
react-cookie
Universal cookies for React. Latest version: 4.1.1, last published: a year ago. Start using react-cookie in your project by running `npm i react-cookie`. There are 541 other projects in the npm registry using react-cookie.
www.npmjs.com
리액트 쿠키는 브라우저의 쿠키에, 값을 저장 할 수 있게 만들어준다.
정확히는 쿠키의 기능을 사용할 수 있게 해주는 정도이다.
사용자의 정보는 "로컬 스토리지", "쿠키", "Session (DB)" 등에 저장 할 수 있다.
로컬 스토리지 는 내 로컬 컴퓨터에서 사용하기엔 적절할 지 모르나, 보안기능이 전혀 없다. 해킹당하기 쉽다.
세션 을 통한 저장은 괜찮지만 성능적인 면에서 꽤나 큰 단점이 있고, 백엔드와 프론트엔드 간의 긴밀한 관계가 없는 이상
중간에 정보를 탈취 당하기 쉽다.
https://dive-into-frontend.tistory.com/98
12. 리액트 [인증] - 인증의 기본 원리, 파이어 베이스 Auth편(1)
[인증의 기본 원리] 인증은 크게, 서버사이드 인증 방식과 토큰 인증 방식을 가진다. 1. 서버사이드 인증 방식 서버측에 사용자들의 정보를 저장하고, 유저가 정보를 제출하면, 확인하고, 그에 따
dive-into-frontend.tistory.com
쿠키 ( "토큰"을 쿠키에 저장) 를 사용한 인증 방식을 채택하여, 성능면으로나, 보안 면으로 좋은 이점을 가질 수 있다.
1) 쿠키 프로바이더 감싸기
< index.tsx >
import { CookiesProvider } from "react-cookie";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<CookiesProvider>
<Provider store={store}>
<BrowserRouter>
<Layout>
<App />
</Layout>
</BrowserRouter>
</Provider>
</CookiesProvider>
);
2) 로그인 컴포넌트에서, 로그인하면 쿠키에 idToken 을 등록한다.
import { useCookies } from "react-cookie";
import { useHistory } from "react-router-dom";
const SignInForm = () => {
const history = useHistory();
const [cookies, setCookie] = useCookies(["auth-cookie"]); // 쿠키 등록을 위한 setCookie
const [error, setError] = useState<string>("");
const emailRef = useRef<HTMLInputElement>(null);
const passwordRef = useRef<HTMLInputElement>(null);
const submitHanlder = async (e: FormEvent) => {
e.preventDefault();
const email = emailRef.current!.value;
const password = passwordRef.current!.value;
const responseData = await signinHandler(email, password);
if (responseData.idToken) { // idToken 이 있다면 OK
setCookie("auth-cookie", {
idToken: responseData.idToken,
email: responseData.email,
});
history.replace("/");
} else if (responseData.error.message === "EMAIL_NOT_FOUND") { // 아니면 에러메시지
setError("이메일을 잘못 입력하셨습니다.");
} else if (responseData.error.message === "INVALID_PASSWORD") { // 아니면 에러메시지
setError("패스워드를 잘못 입력하셨습니다.");
}
};
return (
<div className={styles.main_div}>
<div>
<h1> 로그인</h1>
3) Layout 등, 필요한 곳에서, 쿠키가 없다면 나가게 만들거나, 쿠키가 없다면 버튼을 숨기는 기능을 추가한다.
<layout-header.tsx>
import styles from "./layout-header.module.css";
import { Link } from "react-router-dom";
import { useCookies } from "react-cookie";
import { useHistory } from "react-router-dom";
const LayoutHeader = () => {
const [cookies, setCookie, removeCookie] = useCookies(["auth-cookie"]); // 쿠키
const history = useHistory();
const logoutHandler = () => {
removeCookie("auth-cookie");
history.replace("/");
};
return (
<div className={styles.main_header}>
<Link to="/" className={styles.header_logo}>
<img src="/main-logo.png" alt="logo" />
<h3> DO.HEALTH</h3>
</Link>
<ul className={styles.header_menu}>
{!cookies["auth-cookie"] && ( // 쿠키 없다면, "활성화"
<Link to="/sign-up">
<li> 회원가입</li>
</Link>
)}
{!cookies["auth-cookie"] && ( // 쿠키 없다면, "활성화"
<Link to="/sign-in">
<li> 로그인</li>
</Link>
)}
{cookies["auth-cookie"] && <li onClick={logoutHandler}> 로그아웃</li>} // 쿠키 있다면, "활성화"
</ul>
</div>
);
};
export default LayoutHeader;
** 강제로 Redirect 하는 기능은, useHistory 를 사용하여, 쿠키가 없으면 되돌려보낸다.