22일차) [React/JS]로그인 프로세스 이해하기(JWT토큰,accessToken)

김재범·2022년 8월 6일
1

코드캠프

목록 보기
26/46
post-thumbnail

☑️ 로그인에 대해 알기 위해서는 우선 인증인가에 대한 개념을 알아야 한다.

💡Authentication(인증)

로그인을 해서 저장소로부터 토큰을 받아오는 과정

💡Authorization(인가)

세션 아이디를 보내서 정보를 받아오는 과정



✍JWT 로그인

로그인 정보를 굳이 서버나 DB에 저장해야할까? 과부하 걸리는데..
이를 해결하기 위해 나온 것이 JWT 토큰

JWT 토큰은 유저 정보를 담은 객체를 문자열로 만들어 암호화한 후 암호화된 키
accessToken)을 브라우저에 준다.

받아온 AccessToken은 브라우저 저장소에 저장해두었다가 유저의 정보가 필요한 API를 사용할 때 보내주게 되면, 해당 키를 백엔드에서 복호화해서 사용자를 식별한 후 접근 가능하도록 함.

JWT 토큰에는 발급 받아온 서버에서 정상적으로 발급을 받았다는 증명을 하는 signature를 갖고 있기 때문에 DB를 열어볼 필요가 없음

✍로그인 과정

회원가입
CreateUser API를 뮤테이션으로 날리고 회원가입 등록을함.

로그인
loginUser API를 뮤테이션으로 날리면 accessToken을 받을 수 있음
accessToken안에는 유저가 로그인 한 기록이 있다.

결국 이 accessToken을 통해 해당 API를 사용할 수 있도록 함

❗JWT토큰의 주의할 점❗

https://jwt.io/ 사이트에 들어가보면

이런식으로 우리가 받아온 accessToken을 넣으면 정보를 알 수 있게 됨
만약 토큰을 해킹당하면, 우리의 정보를 열람할 수 있기 때문에
중요한 데이터는 JWT토큰에 넣으면 안됨!

💡
Encoded ⇒ 암호화
decoded ⇒ 복호화(암호를 푸는 것)

💡 JWT토큰의 구성

  1. header: 토큰의 타입, 암호화시 사용한 알고리즘 정보
  2. payload: 토큰 발행정보(누구인지, 언제발행되었는지, 언제 만료될 것인지)
  3. signature: 토큰 비밀번호

이러한 JWT토큰의 조작을 미연에 방지하기 위해 sinature(토큰 비밀번호)를 사용

암호화

비밀번호나 계좌번호 같은 민감한 정보는 백엔드에 저장할 때 그대로 저장하면 안됨.

단방향 암호화 : 암호화는 되지만 복호화는 안됨(민감한 정보 저장 시 필요)

양방향 암호화 : JWT같이 복호화가 되는 암호화

✍토큰을 보내는 방법

HTTP HEADERS에 "Authorization" 을 보내면 된다.
(Bearer는 관례상 사용하는 것이고 반드시는 X 추후 백엔드와 상의해서 사용)

📌로그인과 recoil 연결

loginUser의 경우 타입을 직접 적어야함

// login 폴더의 index.tsx 화면 그려주기
import {useMutation,gql} from "@apollo/client"
import {ChangeEvent} from "react"

const LOGIN_USER = gql`
	mutation loginUser($email:String){
		loginUser(email: $email, password: $password){
			accessToken
		}
	}
`

export default function LoginPage(){
	const [email,setEmail]=useState("")
	const [password,setPassword]=useState("")
	const [loginUser] = useMutation<Pick<IMutation,'loginUser'>,IMutationLoginUserArgus>(LOGIN_USER)

	const onChangeEmail = (event:ChangeEvent<HTMLInputElement>)=>{
		setEmail(event.target.value)
		}
	const onChangePassword = (event:ChangeEvent<HTMLInputElement>)=>{
    setPassword(event.target.value)
		}

	const onClickLogin = async()=>{
	try{
		cosnt result = await loginUser({
			variables:{
					email : email,
					password : password
				}
			})
		const accessToken = result.data?.loginUser.accessToken // accessToken 변수생성
		}catch(error){
			// alert(error.message)을 사용하셔도 무방
			Modal.error({content : error.message})
		}
	} 

	return(
		<div>
			이메일 : <input type="text" onchange={onChangeEmail}/> <br/>
			비밀번호 : <input type="password" onchange={onChangePassword}/> 
			<button onClick={onClickLogin}>로그인하기!!</button>
		</div>
	)
}

📌이제 토큰을 보내주도록 세팅을 한 로그인페이지를 만들고 난 후,
로그인 된 페이지를 세팅해야함.

// loginsuccess 폴더의 index.tsx
const FETCH_USER_LOGGED_IN = gql`
query fetchUserLoggedIn{
	fetchUserLoggedIn{
		email
		name
		}
	}
`
export default function LoginSuccessPage(){
	const {data} = useQuery<Pick<IQuery,"fetchUserLoggedIn">>(FETCH_USER_LOGGED_IN)

	return(
		<div>
			{data?.fetchUserLoggedIn.name}님 환영합니다.
		</div>
	)
}

📌accessToken은 app.tsx에서 업로드할 때 설정해 놓은 부분에 추가

여기에 추가해야만 앞으로 발생하는 모든 useQuery, useMutation 헤더에 내용이 추가됨

//app.tsx파일

const APOLLO_CACHE = new InMemoryCache();

function MyApp({ component,pageProps }:AppProps){
const [accessToken,setAccessToken] = useState("")
const uploadLink = createUploadLink({
		uri : "백엔드 주소",
		cache: APOLLO_CACHE,
		headers : { Authorization : "Bearer ${accessToken}" }
	})

	return (
			<ApolloProvider client={client}>
				<Component {..pageProps}/>
			</ApolloProvider>
	)
}

📌 Global State에 accessToken을 저장하여 사용(recoilState)

  • Global State
  • ☑️accessToken이 RecoilState에 저장되고 결국 _app.tsx의 accessToken도 그 값을 가리킴
  • ❗그러나 이렇게 되면 오류가 발생하는데, RecoilRoot 밖에 RecoilState에 있기 때문이다❗

    ☑️ 이를 해결하기 위해선 ApolloSetting을 빼서 분리해줘서 recoilroot안으로 변경되게 한다.
// src/components/commons/apollo/index.tsx

// Apollo Setting 빼주기
import { useRecoilState } from "recoil";
import { accessTokenState } from "../../../commons/store";

const APOLLO_CACHE = new InMemoryCache();

export default function ApolloSetting(props) {
	const [accessToken,setAccessToken] =useRecoilState(accessTokenState)

	const uploadLink = createUploadLink({
			uri : "백엔드 주소",
			cache: APOLLO_CACHE,
			headers : { Authorization : "Bearer 받아온 토큰" }
		})

	return (
		<ApolloProvider client={client}>
			{props.children}
		</ApolloProvider>
	)
}

최종 app.tsx 부분을 보면 RecoilRoot안에 RecoilState가 있게 된다
(Apollo Setting 부분에 RecoilState가 있기 때문)

//app.tsx파일
import { RecoilRoot } from "recoil";

function MyApp({ component,pageProps }:AppProps){

	return (
		<RecoilRoot>
        <ApolloSetting>
          <Global styles={globalStyles} />
          <Layout>
            <Component {...pageProps} />
          </Layout>
        </ApolloSetting>
      </RecoilRoot>
	)
}
profile
지식을 쌓고 있습니다.

0개의 댓글