프론트엔드에서 API Key를 숨기는 법

권세진·2021년 3월 8일
10

시행착오

목록 보기
2/5

이전에 여기에서 웹팩과 dotenv 패키지를 통해 api key를 숨길 수 있다고 포스팅한 적이 있는데, 사실 그게 아니었다.

어차피 AJAX 방식으로 보내는 거라 개발자 도구에서 정보가 줄줄 새고 있었던....
지금이야 토이프로젝트 개념으로 개발하는 것이라 유출되어도 큰 타격이 없지만
중요한 프로젝트에서 이렇게 하고 있었다면... 상상만 해도 아찔하다

그렇다면 프론트엔드에서 api key를 어떻게 숨길 수 있을까?
답은... 없다...
AJAX 방식으로 request를 보내는 것이라 이 정보를 숨길 수는 없을 것으로 보인다.
그렇다면 다른 방법은 없을까?

서버리스 백엔드 서비스 활용하기

하지만 사람들은 항상 답을 찾는다.
서버리스 백엔드를 이용해 백엔드가 api와 직접 통신하고
프론트는 백엔드와 통신해 그 정보를 받아오는 것이다.

😵 서버리스란?

서버리스는 서버가 없는 것을 의미하지 않는다.
서버는 존재하지만 서버에 백엔드를 모두 올리는 것이 아닌,
필요한 기능만 함수로 올려서
나머지 보안, 관리 등등은 업체에서 하는 것이다.

사용자는 기능에만 집중하면 되기 때문에
'서버를 고려할 필요가 없다'라고 해서 서버리스라고 불리는 것이다.

🧵 서버리스 서비스(netlify) 파헤치기

netlify의 최초 설정, 사용법에 대해서는 하루 티스토리에 너무 잘 정리되어 있으므로 생략하고
내가 서버리스를 사용하면서 어려움을 겪었던 부분에 대해서만 작성한다.

(아래에서 사용한 코드 일체는 동동 깃허브에서 가져왔음을 밝힙니다)

1. function 동작 원리 파악하기

서버리스는 function 단위로 동작하기 때문에
function을 정의해주어야 한다. js 파일도 가능하고 다른 언어로도 작성할 수 있는듯 하다.

functions/youtube.js

const fetch = require("node-fetch");
const querystring = require("querystring");

exports.handler = async (event) => {
  const { path, queryStringParameters } = event;

  const ENDPOINT = "https://www.googleapis.com/youtube/v3";
  const apiMethod = path.split("/").pop();
  const parameters = querystring.stringify({
    ...queryStringParameters,
    key: process.env.YOUTUBE_API_KEY,
  });
  const headers = {
    "Access-Control-Allow-Origin": process.env.LOCAL_HOST,
    Vary: "Origin",
  };

  try {
    const URI = `${ENDPOINT}/${apiMethod}?${parameters}`;
    const response = await fetch(URI);
    const json = await response.json();

    return {
      statusCode: 200,
      ok: true,
      headers,
      body: JSON.stringify(json),
    };
  } catch (error) {
    return {
      statusCode: 404,
      statusText: error.message,
      ok: false,
      headers,
    };
  }
};

중요한 부분만 해석해보도록 하자.

이런 방식으로 path와 queryString을 따로 받아올 수 있다.

exports.handler = async (event) => {
  const { path, queryStringParameters } = event;

apiMethod를 마지막 경로명으로 사용하고 있다.

const apiMethod = path.split("/").pop();
...
try {
    const URI = `${ENDPOINT}/${apiMethod}?${parameters}`;

그래서 프론트엔드에서 netlify 함수에 접근할 때

https://[netlify에서 발급받은 url]/.netlify/functions/[함수파일이름]/[apiMethod]?[queryString]

이렇게 사용해야한다. 지금은 유튜브 검색이므로

https://[netlify에서 발급받은 url]/.netlify/functions/youtube/search?[queryString]

이렇게 접근해야한다.

2. CORS 문제 해결하기

CORS는 교차 출처 리소스 공유를 의미한다.
기본적으로 리소스를 공유할 때 동일 출처 정책을 따르는 AJAX를
다른 출처에서도 리소스를 공유할 수 있도록 만들어 줄 수 있는 방법을 의미한다.

동일 출처 정책이란?

리소스를 공유할 때 출처가 같은(스킴,호스트,포트가 모두 같아야함)
리소스만 공유할 수 있도록 하는 정책을 의미한다.

예를 들어
프론트에서 서버로 request를 보낼 때 request의 origin에 프론트의 url를
담아서 보내는데 이 url이 서버의 url과 같지 않다면 동일 출처 정책을 위반한 것이다.

CORS로 동일 출처 정책을 피해가보자

웹은 다른 출처의 리소스를 굉장히 많이 쓰므로 이런 교차 출처를 허용하기 위해서는
서버에서 response를 프론트엔드로 반환할 때 response의 헤더에 프론트엔드의 url를 넣어주어야한다.

CORS는 심지어 localhost도 허용이 가능한데 localhost에서도 request의 origin에 localhost의 url를
넣어주기 때문이다.
(로컬 개발 시에만 localhost를 허용하고 실제 배포 시에는 제외하세요. 보안에 좋지 않습니다.)

항상 스킴에 주의하자... http인데 https로 CORS 헤더에 넣어줘서
왜안되지 하고 한참을 씨름했다...ㅠ

  • 실제 코드
const headers = {
    "Access-Control-Allow-Origin": process.env.LOCAL_HOST,
    Vary: "Origin",
  };
...
 return {
      statusCode: 200,
      ok: true,
      headers,
      body: JSON.stringify(json),
    };
profile
상상을 현실로 꺼내길 좋아하는 프론트엔드 개발자입니다.

1개의 댓글