AWS amplify에서 cognitio를 활용한 jwt 인증 [AWS]

SnowCat·2023년 8월 29일
1
post-thumbnail

배경지식

사용자 인증

  • 사용자가 백엔드에 접근할 때 그 사용자가 누구인지, 유효한 사용자인지를 검증해야 한다.
  • 크게보면 사용자 정보를 쿠키, 세션, 토큰을 사용해 인증을 하게된다.
  • 사용자를 인증했을 때 서버에서 사용자에게 쿠키를 전송하고, 해당 쿠키 데이터를 바탕으로 하여 사용자를 인증하는 방식이다.
  • 클라이언트 측에서 별 신경을 쓰지 않아도 알아서 인증정보를 전송할 수 있다는 장점이 있다.
  • 하지만 브라우저 간 쿠키 공유가 어렵고, 쿠키값을 평문으로 저장하기 때문에 데이터 탈취의 위험이 있다는 단점이 있다.

Session 인증

  • Cookie 인증과 비슷하지만, 서버의 메모리에 대부분의 정보를 저장하고, 클라이언트에는 세션 id만을 저장하는 방식이다.
  • 쿠키를 사용하는 방법보다 보안면에서 낫지만, 서버의 부하가 증가하고, 여전히 세션 id를 탈취해 다른 사용자인 척 속일 수 있다는 문제점이 있다.

Token 인증

  • 쿠키와 세션 인증 방법을 절충한 방법으로, 인증에 필요한 사용자 정보와 토큰 검증에 필요한 데이터를 담아 클라이언트에 전달하는 방식이다.
  • 서버에서는 클라이언트에서 전달받은 데이터를 받아 토큰의 위조여부를 검증한다.
  • 데이터를 클라이언트가 저장하기 때문에 서버의 부하를 줄일 수 있고, 사용기한을 짧게 설정함으로써 보안성을 강화할 수 있다.
  • 하지만 토큰 데이터가 길어 요청이 많으면 네트워크 부하가 심해지며, Payload는 암호화되지 않기 때문에 사용자의 민감한 정보를 담을 수 없다는 단점을 가지고 있다.

JWT

  • JWT(Json Web Token)은 인증에 필요한 정보들을 JSON 토큰을 의미한다.
  • JWT는 JSON 데이터를 인코딩해 Serialize한 것으로 토큰 내부에는 위변조 방지를 위한 전자서명도 들어가있다.
  • JWT는 크게 header, payload, signature의 3부분으로 구분할 수 있다.
    • header: 서명 암호화에 사용한 알고리즘과 토큰 유형을 저장하는 부분
    • payload: 실제 사용자 데이터 및 토큰에 관한 정보를 저장하는 부분
    • signature: 헤더에서 정의한 알고리즘 방식에 따라 헤더와 페이로드를 합치고, 서버에 저장된 secret key로 해싱을 해 저장하는 부분, 아래와 같은 알고리즘을 가진다.
    HMACSHA256(
      base64UrlEncode(header) + "." +
      base64UrlEncode(payload),
      secret
    )
  • JWT 토큰은 탈취의 위험성이 있기 때문에, 실제 인증에 필요한 access token은 유효 기간을 짧게, access token 발급에 필요한 refresh 토큰은 유효 기한을 길게 설정하는 방식으로 대응한다.

AWS amplify에서 cognitio를 활용한 JWT 인증 사용하기

  • 그럼 이제 aws amplify 서비스에서 JWT 토큰을 활용한 인증을 사용하는 법을 알아보자.
  • 클라이언트에서 직접 aws 서비스에 접근하는 경우에는 별도의 설정을 하지 않고 amplify에서 제공하는 함수만으로 인증 처리를 해줄 수 있다.
  • 하지만 lambda나 next.js 등의 서버리스 함수를 거치는 경우에는 별도로 토큰을 제출하고, 검증하는 작업을 걸쳐야 한다.
  • Cognitio를 사용한 경우 시크릿 키를 직접 보관하는것이 아니라 aws 서비스에 저장하기 때문에, 이를 가져올 라이브러리 역시 필요하다.

Frontend

  • 프론트엔드에서는 amplify 라이브러리를 통해 jwt 토큰을 가져올 수 있다.
import { Auth } from "aws-amplify";

export default async function getCurrentUserToken() {
  try {
    // 로그인이 되지 않은 경우에도 오류가 발생함에 주의
    const currentSession = await Auth.currentSession();
    const accessToken = currentSession.getAccessToken();
    const jwt = accessToken.getJwtToken();
    return jwt;
  } catch (error) {
    console.log(error);
    return "";
  }
}
  • 이 값을 인증이 필요한 곳에 header로 전송해주면 된다.
  • 인증 프로토콜은 <type> <credentials> 형식을 가지기 때문에 앞에 type이름으로 Bearer를 붙여줘야 한다.
export default async function getPostInfo(
  id: string,
): Promise<PostData> {
  try {
    const token = await getCurrentUserToken();
    const result = await fetch(`${process.env.NEXT_PUBLIC_API_DOMAIN}/categories/${id}`, {
      method: "GET",
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
      },
    });

    const { post }: { post: PostData } = await result.json();
    return post;
  } catch (error) {
    throw error;
  }
}

Backend

  • Node.js 환경에서는 jwt 토큰 검증을 위해 aws-jwt-verify 사용을 권장하고 있다.
npm i aws-jwt-verify
  • 인증 조건을 담은 verifier 객체를 생성하고, 받아온 토큰값을 검증하는 함수를 사용해주면 간단하게 토큰 검증을 수행할 수 있다.
import { CognitoJwtVerifier } from "aws-jwt-verify";

export default async function verifyToken(token: string | null) {

  const [tokenType, tokenText] = tokenString.split(" ");
  if (tokenType !== "Bearer") {
    throw new Error(ErrorMessage.INVALID_TOKEN_TYPE);
  }
  const verifier = CognitoJwtVerifier.create({
    userPoolId: "ap-northeast-2_mollu" // cognitio 유저 풀 id,
    tokenUse: "access",
    clientId: "d30lguc62630ad" //백엔드 앱 id,
  });

  try {
    const payload = await verifier.verify(
      tokenText // JWT 토큰의 credentials값
    );
    console.log("Token is valid!);
    return userID;
  } catch (error) {
    console.log(error);
    throw new Error("Token is not valid...");
  }
}

출처:
https://docs.aws.amazon.com/ko_kr/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html

profile
냐아아아아아아아아앙

0개의 댓글