본문 바로가기

FrontEnd/React

[React] JSON 웹 토큰으로 회원 인증 구현하기

사용자 인증과 JSON 웹 토큰

 사용자 인증은 각각의 사용자를 구분하는 목적으로 이용되므로 서비스를 제공하는 서버에 사용자가 입력한 정보를 저장한다.  하지만 구글, 페이스북, 네이버 등 다른 사이트에서 가입되어 입력한 정보를 바탕으로 회원가입을 하용하기도 한다.

 

 이런 방식으로 동작하는 서버들은 OAuth(open standard for access delegation)라고 하는 표준에 기반하며 OAuth는 모두 JSON 웹 토큰이란 기술에 기반을 두고 있다.  토큰(token)은 보통 문자열이나 숫자로 만든다.

 

 

 JSON 웹 토큰(JWT)은 선택적 서명(optional signature)과 선택적 암호화(optional encryption)기술을 사용해 데이터를 만들게 하는 인터넷 표준이며 명칭은 REF 7519이며 HTTP 헤더의 Authorization항목에 값을 전송하는 방식으로 동작한다.

headers: {
    Authorization: `Bearer ${jwt}`
}

 

 이러한 HTTP 헤더를 수신받은 서버headers의 Authorization 항목에서 JWT값을 검증정상작업을 수행하며 작업의 결과를 반환한다.  JWT는 보통 회원가입 시 서버에서 생성하며 웹 브라우저 쪽 프런트엔드 프레임워크에 전달한다.

 

 결과적으로 프런트엔드에서 JWT 값을 보관하고 있다 서버 API 호출 시 HTTP Headers의 Authorization 항목에 실어서 전송한다.

 

 

 

JWT 기능 구현하기 - 서버

 Node.js 환경에서 JSON 웹 토큰과 관련된 기능은 jsonwebtoken 패키지를 사용한다.

> npm i jsonwebtoken
> npm i -D @types/jsonwebtoken

 

 

 jsonwebtoken 패키지는 sign, verify 함수를 사용한다.  sign 함수는 JSON 웹 토큰을 생성하고 verify 함수는 jwt를 검증하기 위해 사용된다.

import {sign, verify} from jsonwebtoken

const secret = 'payload를 암호화할 키'
// sign(payload, secret, options)
// options: 옵션을 설정(여기서는 유효기한을 설정)
const jwt = sign('Jack', secret, {expiresIn: '1d'})
const decoded = verify(jwt, secret, options)  // jwt 검증

 

 

 jsonwebtoken 패키지가 제공하는 기능을 프로그래밍으로 좀 더 쉽게 사용할 수 있게 jwtP.ts를 추가하자.  추가 후 ts-node명령을 통해 실행 로그를 확인하자.

import * as U from "../utils";

const jwtNomalTest = async () => {  //웹 토큰 생성 & 검증 테스트
  try {
    const jwt = await U.jwtSignP({ name: "Jack", age: 32 });
    console.log("jwt", jwt);
    const decoded = await U.jwtVerifyP(jwt);
    console.log("decoded", decoded);
  } catch (e) {
    if (e instanceof Error) console.log("error", e.message);
  }
};

const jwtExceptionTest = async () => {  // 토큰이 비정상 테스트("1234")
  try {
    const decoded = await U.jwtVerifyP("1234");
    console.log("decoded", decoded);
  } catch (e) {
    if (e instanceof Error) console.log("error", e.message);
  }
};

const jwtExpireTest = async () => {  // 토큰 유효시간 생성 & 검증 테스트
  const jwt = await U.jwtSignP({ name: "Jack", age: 32 }, { expiresIn: "1s" });
  const id = setTimeout(async () => {
    try {
      const decoded = await U.jwtVerifyP(jwt);
      console.log("decoded", decoded);
    } catch (e) {
      if (e instanceof Error) console.log("error", e.message);
    }
  }, 2000);
};

jwtNomalTest().then(jwtExceptionTest).then(jwtExpireTest);

 

실행결과: 토큰이 정상적으로 발행 및 검증된 것을 확인했으며, 토큰오류검증 및 토큰기한오류검증을 수행해 결과를 확인 하였다.

 

 

 

 

비밀번호 해시값 구하기 - 서버

 사용자 인증 라우터에서 사용될 비밀번호를 해시값(hash value)으로 변경하기 위해 bcrypt 패키지를 사용해 구현하자.  이 패키지의 bcrypt 객체를 통해 hash와 compare 함수를 bcrypt.hash, bcrypt.compare 형태로 사용할 수 있다.

> npm i bcrypt
> npm i -D @types/bcrypt

 

 Promise 버전인 hash와 compare 함수를 이용해 비밀번호를 해시하는 함수를 hashPasswordP.ts 파일로 추가하자.

import bcrypt from "bcrypt";
const saltRounds = 10;

export const hashPasswordP = (password: string) =>
  new Promise<string>(async (resolve, reject) => {
    try {
      const salt = await bcrypt.genSalt(saltRounds);  // 임의의 문자열 생성
      const hash = await bcrypt.hash(password, salt); // 임의의 값으로 해시화
      resolve(hash);
    } catch (e) {
      reject(e);
    }
  });
// 비밀번호 검증
export const comparePasswordP = (password: string, hashedPassword: string) =>
  new Promise<boolean>(async (resolve, reject) => {
    try {
      const result = await bcrypt.compare(password, hashedPassword);
      resolve(result);
    } catch (e) {
      reject(e);
    }
  });

 

 

 추가한 hashPasswordP.ts의 함수를 테스트하기 위해 hashTest.ts를 추가한다.

import * as U from "../utils";

const hashTest = async () => {
  const password = "1234";
  try {
    const hashed = await U.hashPasswordP(password);
    console.log("hashed", hashed);
    const same = await U.comparePasswordP(password, hashed);
    console.log("same", same);
    const same2 = await U.comparePasswordP("abcd", hashed);
    console.log("same2", same2);
  } catch (e) {
    if (e instanceof Error) console.log("error", e.message);
  }
};
hashTest();

 

실행결과로 해시값을 얻은 후 패스워드와 비교 및 잘못된 비밀번호 검증까지 확인할 수 있다.

 

 

 

라우터 구현하기 - 서버

 routers 디렉터리에 authRouter.ts 파일을 생성해 코드를 추가하자.  회원가입 정보 검증 처리와 더불어 비밀번호 해시화를 통한 jwt 토큰을 발행한다.

import type { MongoDB } from "../mongodb";
import { Router } from "express";
import * as U from "../utils";

export const authRouter = (...args: any[]) => {
  const db: MongoDB = args[0];
  const user = db.collection("user");
  const router = Router();

  return router.post("/signUp", async (req, res) => {
    const { body } = req;

    try {
      console.log("/signup", body);
      const exists = await user.findOne({ email: body.email });

      if (exists) {
        res.json({ ok: false, errorMessage: "이미 가입한 회원입니다." });
      } else {
        const { email, password } = body;
        const hashed = await U.hashPasswordP(password);
        const newBody = { email, password: hashed };
        const { insertedId } = await user.insertOne(newBody);
        const jwt = await U.jwtSignP({ userId: insertedId });

        res.json({ ok: true, body: jwt });
      }
    } catch (e) {
      if (e instanceof Error) console.log("error", e.message);
    }
  });
};

 

 기존에 testRouter를 설정한 setupRouters에 authRouter를 설정한다.   이로서 회원가입 경로는 '/auth/signup'이 되며 물론 요청은 post로 요청해야 한다.

import { Express } from "express";
import * as R from "../routers";

export const setupRouters = (app: Express, ...args: any[]): Express => {
  // 3) 전달받은 router를 testRouter로 전달
  return app
    .use("/test", R.testRouter(...args))
    .use("/auth", R.authRouter(...args));
};

 

 

 

 

회원 인증 기능 구현하기 - 클라이언트

 클라이언트와 백앤드의 가장 이상적인 API 호출방식은 API서버 요청행위에 대한 오류가 없을 때 클라이언트에서도 서버에서 얻어온 결과를 반영해 주는 것이 일반적이다.

 

 기존 백앤드를 타지 않고 구현한 signup을 확인해보면 이상적인 형태로 구현되어 있진 않다.  왜냐하면 API 서버와의 통신이 빠져있기 때문이다.

  const signup = useCallback((email: string, password: string, callback?: Callback) => {
    const user = {email, password}
    setLoggedUser(notUsed => ({email, password}))
    U.writeObjectP('user', user).finally(() => callback && callback())
    // callback && callback()
  }, [])

 

 

 백엔드 API 서버가 있을때의 구현예시를 들어보자.  서버에서 ok 값이  true일 때 setLoggedUser, U.writeObjectP 함수를 호출하는 방식으로 변경 하였으며 서버에서 보내온 JSON 토큰이나 통신장애의 오류를 처리하기 위한 상태를 추가한다.

const [jwt, setJwt] = useState<string>('')
const [errorMessage, setErrorMessage] = useState<string>('')

const signup = useCallback((email: string, password: string, callback?: Callback) => {
    const user = { email, password}
    
    post('/auth/signup', user).then(res => res.json())
        .then((result: {ok: boolean, body?: string; errorMessage?: string}) => {
        const {ok, body, errorMessage} = result
            if (ok) {
                U.writeStringP('jwt', body ?? '').finally() => {
                    setJwt(body ?? '')
                    setLoggedUser(notUsed => user)
                    U.writeObjectP('user', user).finally(() => callback && callback())
                }) // U.writeStringP, U.writeObjectP : 로컬스토리지 사용자 정보 추가

            }
    	})
}, [])

 

 

 로그인이 정상적으로 되었을 때 localStorage에 저장된 jwt값을 읽어 컨텍스트의 jwt 상태값을 복원해 주는 것도 필요하다.

useEffect(() => {
    U.readStringP('jwt').then(jwt => setJwt(jwt ?? '').catch(() => {/* 오류무시 */})
, [])

 

 

 앞서 API서버를 적용시 변경부분을 종합해 AuthContext.tsx를 변경하자.

import type {FC, PropsWithChildren} from 'react'
import {createContext, useContext, useState, useCallback, useEffect} from 'react'
import * as U from '../utils'
import {post} from '../server'

export type LoggedUser = {email: string; password: string}
type Callback = () => void

type ContextType = {
  loggedUser?: LoggedUser
  signup: (email: string, password: string, callback?: Callback) => void
  login: (email: string, password: string, callback?: Callback) => void
  logout: (callback?: Callback) => void
}

export const AuthContext = createContext<ContextType>({
  signup(email: string, password: string, callback?: Callback) {},
  login(email: string, password: string, callback?: Callback) {},
  logout(callback?: Callback) {}
})

type AuthProviderProps = {}
export const AuthProvider: FC<PropsWithChildren<AuthProviderProps>> = ({children}) => {
  const [loggedUser, setLoggedUser] = useState<LoggedUser | undefined>(undefined)
  const [jwt, setJwt] = useState<string>('')
  const [errorMessage, setErrorMessage] = useState<string>('')
  const signup = useCallback((email: string, password: string, callback?: Callback) => {
    const user = {email, password}
    debugger
    post('/auth/signup', user)
      .then(res => res.json())
      .then((result: {ok: boolean; body: string; errorMessage: string}) => {
        const {ok, body, errorMessage} = result
        if (ok) {
          U.writeStringP('jwt', body ?? '').finally(() => {
            setJwt(body ?? '')
            setLoggedUser(notUsed => ({email, password}))
            U.writeObjectP('user', user).finally(() => callback && callback())
          })
        } else setErrorMessage(errorMessage ?? '')
      })
      .catch((e: Error) => setErrorMessage(e.message))

    // callback && callback()
  }, [])
  const login = useCallback((email: string, password: string, callback?: Callback) => {
    setLoggedUser(notUsed => ({email, password}))
    callback && callback()
  }, [])
  const logout = useCallback((callback?: Callback) => {
    setLoggedUser(undefined)
    callback && callback()
  }, [])
  useEffect(() => {
    U.readStringP('jwt')
      .then(jwt => setJwt(jwt ?? ''))
      .catch(() => {
        /* 오류무시 */
      })
  }, [])

  const value = {
    jwt,
    errorMessage,
    loggedUser,
    signup,
    login,
    logout
  }
  return <AuthContext.Provider value={value} children={children} />
}

export const useAuth = () => {
  return useContext(AuthContext)
}

 

실행결과로 회원가입 후 몽고쉘의 데이터를 확인해보면 동일한 것을 알 수 있다.

 

 

 

 

로그인 기능 구현하기 - 서버

 몽고DB는 ObjectId 타입의 _id속성이 있기 떄문에 문자열을 ObjectId 형태로 변환해주는 stringToObjectId 함수를 구현하자.  

import { ObjectId } from "mongodb";
export const stringToObjectId = (id: string) => new ObjectId(id);

 

 로그인 시 JSON토큰은 HTTP 요청 헤더의 Authorization 속성의 설정값으로 서버에 전송이 되며 전송 받은 값은 다양한 방어하는 코드 및 jwt를 반환하는 형태로 구현되어야 한다.

router.post(경로, (req, res) => {
    const {authorization} = req.header || {}
    const tmp = authorization.split(' ')
    
    if(tmp.length !== 2) {
        res.json({ok: false, errorMessage: '헤더에서 JSON 토큰을 얻을 수 없습니다'})
    } else {
        // 얻은 토큰을 통해 user컬렉션의 문서 _id값을 얻어 findOne으로 userId값을 얻는다.
        const jwt = tmp[1]
        const decoded = (await U.jwtVerifyP(jwt)) as {userId: string}
        const result = await user.findOne({_id: stringToObjectId(decoded.userId)})
    }

 

 

 로그인 후 사용자 정보를 얻는 방법까지 확인했으며 이를 토대로 authRouter.ts에 post('/login')라우트 부분을 구현하자.

import { stringToObjectId, type MongoDB } from "../mongodb";
import { Router } from "express";
import * as U from "../utils";

export const authRouter = (...args: any[]) => {
  const db: MongoDB = args[0];
  const user = db.collection("user");
  const router = Router();

  return router
    ...(생략)...
    .post("/login", async (req, res) => {
      const { authorization } = req.headers || {};
      if (!authorization) {
        res.json({ ok: false, errorMessage: "JSON 토큰이 없습니다." });
        return;
      }

      try {
        const tmp = authorization.split(" ");
        if (tmp.length !== 2) {
          res.json({
            ok: false,
            errorMessage: "헤더에서 JSON 토큰을 얻을 수 없습니다.",
          });
        } else {
          const jwt = tmp[1];
          const decoded = (await U.jwtVerifyP(jwt)) as { userId: string };
          const result = await user.findOne({
            _id: stringToObjectId(decoded.userId),
          });
          if (!result) {
            res.json({
              ok: false,
              errorMessage: "등록되지 않은 사용자 입니다.",
            });
            return;
          }

          const { email, password } = req.body;
          if (email !== result.email) {
            res.json({ ok: false, errorMessage: "이메일 주소가 틀립니다." });
            return;
          }
          const same = await U.comparePasswordP(password, result.password);
          if (false === same) {
            res.json({ ok: false, errorMessage: "비밀번호가 틀립니다." });
            return;
          }

          res.json({ ok: true });
        }
      } catch (e) {
        if (e instanceof Error)
          res.json({ ok: false, errorMessage: e.message });
      }
    });
};

 

 

 

 

로그인 기능 구현하기 - 클라이언트

 클라이언트 쪽에서 HTTP요청에 JSON 토큰을 실어 보내는 방법을 알아보자.  프론트앤드에서 서버로 로그인하려면 먼저 JSON 토큰을 운반하는 post메서드를 구현해야 한다.  

 

 참고로 fetch함수의 두 번째 매개변수 부분을 분리하는 형태는 다음과 같다.

    let init: RequestInit = {
      method: 'POST' 혹은 'PUT',
      body: JSON.stringify(data),
      mode: 'cors',
      cache: 'no-cache',
      credentials: 'same-origin'
    }
    fetch(getServerUrl(path), init)

 

 

이를 토대로 JSON 웹 토큰 유무에 따라 headers부분을 변경할 수 있도록 postAndPut.ts에 코드를 추가하자.

import {getServerUrl} from './getServerUrl'

const postAndPut =
  (methodName: string) =>
  (path: string, data: object, jwt?: string | null | undefined) => {
    let init: RequestInit = {
      method: methodName,
      body: JSON.stringify(data),
      mode: 'cors',
      cache: 'no-cache',
      credentials: 'same-origin'
    }
    if (jwt) {
      // jwt 토큰유무에 따라 헤더 값을 추가여부 설정
      init = {
        ...init,
        headers: {'Content-Type': 'application/json', Authorization: `Bearer ${jwt}`}
      }
    } else init = {...init, headers: {'Content-type': 'application/json'}}
    return fetch(getServerUrl(path), init)
  }
export const post = postAndPut('POST')
export const put = postAndPut('PUT')

 

 

 

 

 AuthContext.tsx 파일의 login 함수 다시 구현하기

  authRouter의 '/auth/login' 호출 시 JSON 토큰 값이 필요한데 현재는 localStorage에 담겨 있어 AuthContext.tsx의 login함수는 localStorage에서 JSON토큰을 읽는 행위로 시작되어야 하며 얻은 토큰 정보와 함께 사용자 정보를 전송한다.

 

 이후 얻은 결과값 중 ok 속성값이 false일경우 오류 메시지를 화면에 출력하고, true일 경우 setLoggedUser 와 Callback함수를 호출해 다음 화면으로 진행 되도록 구현한다.

const login = userCallback((email: string, password: string, callback?: Callback) => {
    const user = {email, password}
    U.readString('jwt').then(jwt => {
        return post('/auth/login', user, jwt)
    }).then(res => res.json).then((result: {ok:boolean, errorMessage?:string}) => {
        if(result.ok) {
            setLoggedUser(notUsed => user)
            callback && callback()
        } else {
            setErrorMessage(result.errorMessage ?? '')
        }
    }).catch((e: Error) => setErrorMessage(e.message ?? '')
},[])

 

 

 다음은 logout 함수의 구현 내용으로 AuthProvider는 useState 훅으로 얻은 jwt값만을 사용하며 localStorage에 담긴 jwt값을 사용하지 않는다.  즉 useState훅으로 유지되는 jwt 토큰만을 초기화 해야 한다.

const logout = useCallback((callback?: Callback) => {
    setJwt(notUsed => '')
    setLoggedUser(undefined)
    callback && callback()
},[])

 

 

 앞서 본 login과 logout함수를 AuthContext.tsx에 코드를 추가한다.

import type {FC, PropsWithChildren} from 'react'
import {createContext, useContext, useState, useCallback, useEffect} from 'react'
import * as U from '../utils'
import {post} from '../server'

export type LoggedUser = {email: string; password: string}
type Callback = () => void

type ContextType = {
  jwt?: string
  errorMessage?: string
  loggedUser?: LoggedUser
  signup: (email: string, password: string, callback?: Callback) => void
  login: (email: string, password: string, callback?: Callback) => void
  logout: (callback?: Callback) => void
}

export const AuthContext = createContext<ContextType>({
  signup(email: string, password: string, callback?: Callback) {},
  login(email: string, password: string, callback?: Callback) {},
  logout(callback?: Callback) {}
})

type AuthProviderProps = {}
export const AuthProvider: FC<PropsWithChildren<AuthProviderProps>> = ({children}) => {
  const [loggedUser, setLoggedUser] = useState<LoggedUser | undefined>(undefined)
  const [jwt, setJwt] = useState<string>('')
  const [errorMessage, setErrorMessage] = useState<string>('')
  ...(생략)...
  const login = useCallback((email: string, password: string, callback?: Callback) => {
    const user = {email, password}
    U.readStringP('jwt').then(jwt => {
      setJwt(jwt ?? '')
      return post('/auth/login', user, jwt)
        .then(res => res.json())
        .then((result: {ok: boolean; errorMessage?: string}) => {
          if (result.ok) {
            setLoggedUser(notUsed => user)
            callback && callback()
          } else {
            setErrorMessage(result.errorMessage ?? '')
          }
        })
        .catch((e: Error) => setErrorMessage(e.message ?? ''))
    })
  }, [])
  const logout = useCallback((callback?: Callback) => {
    setJwt(notUsed => '')
    setLoggedUser(undefined)
    callback && callback()
  }, [])

  useEffect(() => {
    const deleteToken = false // localStorage의 jwt값을 초기화할 때 사용
    if (deleteToken) {
      U.writeStringP('jwt', '')
        .then(() => {})
        .catch(() => {})
    } else {
      U.readStringP('jwt')
        .then(jwt => setJwt(jwt ?? ''))
        .catch(() => {
          /* 오류무시 */
        })
    }
  }, [])

  useEffect(() => {
    if (errorMessage) {
      alert(errorMessage)
      setErrorMessage(notUsed => '')
    }
  }, [errorMessage])

  const value = {
    jwt,
    errorMessage,
    loggedUser,
    signup,
    login,
    logout
  }
  return <AuthContext.Provider value={value} children={children} />
}

export const useAuth = () => {
  return useContext(AuthContext)
}

 

 

 

 

 

 RequireAuth 컴포넌트에 JSON 토큰 반영하기 - 클라이언트

  사용자가 회원가입이나 로그인 했는지를 useAuth 훅이 반환하는 loggedUser 객체(상태) 유무로 판단했지만 현재는  JSON 웹 토큰(JWT)의 유무로 구분하는게 좀 더 정확하다.   RequireAuth.tsx의 loggedUser를 jwt로 변경하자.

import type {FC, PropsWithChildren} from 'react'
import {useEffect} from 'react'
import {useNavigate} from 'react-router-dom'
import {useAuth} from '../../contexts'

type RequireAuthProps = {}

const RequireAuth: FC<PropsWithChildren<RequireAuthProps>> = ({children}) => {
  const {jwt} = useAuth()
  const navigate = useNavigate()

  useEffect(() => {
    if (!jwt) navigate('/login') // jwt 토큰이 없을 경우 login화면으로 이동
  }, [jwt, navigate])
  return <>{children}</> // 토큰이 있을경우 children이 element가 되게 함
}

export default RequireAuth

 

 

 

 서버 쪽에서 JSON 토큰 기능 구현하기 - 서버

  authRouter.ts에서 JSON토큰으로부터 user 컬렉션에 저장된 특정 문서의 _id 속성값을 userId란 이름으로 찾는 로직이 있다.  하지만 아래 코드는 양이 적지 않고 다른 REST API 구현에도 중복적으로 적용해야 하므로 유틸로 구성함이 좋다.

post("/login", async (req, res) => {
      const { authorization } = req.headers || {};
      const jwt = tmp[1];
      const decoded = (await U.jwtVerifyP(jwt)) as { userId: string };
      const result = await user.findOne({_id: stringToObjectId(decoded.userId),});
});

 

 이 중복을 제거하고자 getUserIdFromJwtP 유틸리티 함수를 구현하겠다.

import type { Request } from "express";
import * as U from "../utils";

export const getUserIdFromJwtP = (req: Request) =>
  new Promise<string>(async (resolve, reject) => {
    const { authorization } = req.headers || {};
    if (!authorization) {
      reject(new Error("JSON 토큰이 없습니다."));
      return;
    }

    try {
      const tmp = authorization.split(" ");
      if (tmp.length !== 2)
        reject(new Error("헤더에서 JSON 토큰을 얻을 수 없습니다."));
      else {
        const jwt = tmp[1];
        const decoded = (await U.jwtVerifyP(jwt)) as { userId: string };
        resolve(decoded.userId);
      }
    } catch (e) {
      if (e instanceof Error) reject(new Error(e.message));
    }
  });

 

  다음으로 testRouter.ts에 getUserIdFromJwtP 함수를 호출하는 코드를 추가한다.  코드는 라우트 콜백 함수들의 try 문 첫 줄에 await getUserIdfromJwtP(res) 호출문을 추가한다.   

 

  즉 클라이언트에서 보내오는 JSON 웹 토큰을 얻으려면 이런 기계적인 코드를 작성하면 된며 하단은 각각의 라우터에 적용할 getUserIdFromJwtP의 예시이다.

router.get("/", async (req, res) => {  
      try {
        const userId = await getUserIdFromJwtP(req);  // 모든 라우터에 적용
      } catch (e) {
        if (e instanceof Error)
          res.json({ ok: false, errorMessage: e.message });
      }
    })

실행결과로 앞서 적용한 라우터의 기계적인 코드로 인해 코튼 예외처리한 것을 확인할 수 있다.

 

 

 

 

 

 클라이언트 쪽 JSON 토큰 기능 구현하기 - 클라이언트

  JSON 웹 토큰(JWT)은 다음 형태로 서버로 전송된다.  이 형태로 서버에 전송하기 위해 getAndDel, postAndPut을 수정할 필요가 있어보인다.

headers: {
    Authorization: `Bearer ${jwt}`
}

 

 우선 getAndDel.ts의 코드를 수정한다.  2번째 매개변수로 jwt 토큰을 선택적으로 수신한다.  토큰이 있을 경우 RequestInit타입 init변수의 headers속성에 Authorization을 추가한다.  이 내용은 postAndPut도 동일하다.

import {getServerUrl} from './getServerUrl'

const getAndDel =
  (methodName: string, jwt?: string | null | undefined) =>
  (path: string, jwt?: string | null | undefined) => {
    let headers = {'Content-type': 'application/json'}
    let init: RequestInit = {
      method: methodName
    }
    if (jwt) {
      init = {
        ...init,
        headers: {...headers, Authorization: `Bearer ${jwt}`}
      }
    } else init = {...init, headers}
    return fetch(getServerUrl(path), init)
  }
export const get = getAndDel('GET')
export const del = getAndDel('DELETE')

 

 

 JWT기능을 각각의 get, put, post, del에 적용하면 된다 예시로 get에 대해서 확인하자.

  const getAllTest = useCallback(() => {
    setErrorMessage(null)
    get('/test', jwt)
      .then(res => res.json())
      .then(data => setData(data))
      .catch(error => setErrorMessage(error.message))
  }, [jwt])