☑️ 로그인에 대해 알기 위해서는 우선 인증
과 인가
에 대한 개념을 알아야 한다.
로그인을 해서 저장소로부터 토큰을 받아오는 과정
세션 아이디를 보내서 정보를 받아오는 과정
로그인 정보를 굳이 서버나 DB에 저장해야할까? 과부하 걸리는데..
이를 해결하기 위해 나온 것이 JWT 토큰
JWT 토큰은 유저 정보를 담은 객체를 문자열로 만들어 암호화한 후 암호화된 키
accessToken)을 브라우저에 준다.받아온 AccessToken은 브라우저 저장소에 저장해두었다가 유저의 정보가 필요한 API를 사용할 때 보내주게 되면, 해당 키를 백엔드에서 복호화해서 사용자를 식별한 후 접근 가능하도록 함.
JWT 토큰에는 발급 받아온 서버에서 정상적으로 발급을 받았다는 증명을 하는 signature를 갖고 있기 때문에 DB를 열어볼 필요가 없음
회원가입
CreateUser API를 뮤테이션으로 날리고 회원가입 등록을함.
로그인
loginUser API를 뮤테이션으로 날리면 accessToken을 받을 수 있음
accessToken안에는 유저가 로그인 한 기록이 있다.
결국 이 accessToken을 통해 해당 API를 사용할 수 있도록 함
https://jwt.io/ 사이트에 들어가보면
이런식으로 우리가 받아온 accessToken을 넣으면 정보를 알 수 있게 됨
만약 토큰을 해킹당하면, 우리의 정보를 열람할 수 있기 때문에
중요한 데이터는 JWT토큰에 넣으면 안됨!
💡
Encoded ⇒ 암호화
decoded ⇒ 복호화(암호를 푸는 것)
💡 JWT토큰의 구성
- header: 토큰의 타입, 암호화시 사용한 알고리즘 정보
- payload: 토큰 발행정보(누구인지, 언제발행되었는지, 언제 만료될 것인지)
- signature: 토큰 비밀번호
이러한 JWT토큰의 조작을 미연에 방지하기 위해 sinature(토큰 비밀번호)를 사용
비밀번호나 계좌번호 같은 민감한 정보는 백엔드에 저장할 때 그대로 저장하면 안됨.
단방향 암호화 : 암호화는 되지만 복호화는 안됨(민감한 정보 저장 시 필요)
양방향 암호화 : JWT같이 복호화가 되는 암호화
HTTP HEADERS에 "Authorization" 을 보내면 된다.
(Bearer는 관례상 사용하는 것이고 반드시는 X 추후 백엔드와 상의해서 사용)
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>
)
}