초보자를 위한 OAuth 2.0 이해하기
바이브코딩과 AI 에이전트로 빠르게 서비스를 만들 수 있는 시대입니다. 하지만 인증 구조를 이해하지 못하면 비밀번호가 코드에 그대로 노출되는 사고가 일어납니다. 인증의 기본 개념부터 OAuth 2.0까지, 비개발자도 이해할 수 있도록 정리했습니다.
AI가 코드를 써주는데, 인증은 왜 알아야 하나요
바이브코딩으로 웹사이트를 만들었습니다. "구글 로그인 추가해줘"라고 말하니 AI가 코드를 만들어 줍니다. 동작합니다. 로그인이 됩니다. 문제 없어 보입니다.
그런데 생성된 코드를 자세히 보면, API 비밀키가 프론트엔드 코드에 그대로 들어 있습니다. 브라우저의 개발자 도구를 열면 누구나 이 키를 볼 수 있습니다. 이 키로 무엇을 할 수 있을까요? 여러분의 앱을 사칭해서 사용자 정보를 가로챌 수 있습니다.
AI는 "동작하는 코드"를 만들어 줍니다. 하지만 "안전한 코드"를 보장하지는 않습니다. 특히 인증(Authentication)과 관련된 코드에서 이 차이는 치명적입니다.
이런 사고는 실제로 일어나고 있습니다.
- GitHub에 API 키가 포함된 코드를 올려서 클라우드 요금이 수백만 원 청구된 사례
- 바이브코딩으로 만든 서비스에서 사용자 비밀번호가 암호화 없이 저장된 사례
- AI가 생성한 코드에서 인증 토큰이 URL에 노출되어 세션 탈취가 가능했던 사례
AI 에이전트를 사용하더라도 마찬가지입니다. AI 에이전트는 기존 코드를 이해하고 수정하는 데 뛰어나지만, 보안의 "맥락"까지 완벽하게 파악하지는 못합니다. "이 토큰은 서버에서만 다뤄야 한다", "이 키는 환경 변수로 분리해야 한다"는 판단은 결국 사람의 몫입니다.
이 글에서는 인증의 기본 개념부터 시작해서, 인증 방식이 왜 발전해 왔는지, 그리고 OAuth 2.0이 무엇인지를 비유로 풀어 설명합니다. 코드를 직접 작성하지 않더라도, 이 구조를 이해하고 있으면 AI가 만든 코드에서 위험 신호를 알아볼 수 있습니다.
인증이란 무엇인가
인증을 한 문장으로 정의하면 이렇습니다.
"당신이 누구인지 확인하는 과정"
일상에서 인증은 어디에나 있습니다. 은행에서 신분증을 보여주는 것, 아파트 출입구에서 카드를 찍는 것, 스마트폰 잠금을 푸는 것이 모두 인증입니다.
웹 서비스에서의 인증도 같은 원리입니다. "이 요청을 보낸 사람이 정말 권한이 있는 사람인가?"를 확인하는 것입니다. 로그인 화면에서 이메일과 비밀번호를 입력하는 것이 가장 기본적인 인증 방식입니다.
여기서 한 가지 더 알아야 할 개념이 있습니다. 인가(Authorization)입니다.
- 인증(Authentication): 당신이 누구인지 확인하는 것 — "이 사람이 홍길동이 맞는가?"
- 인가(Authorization): 확인된 사람에게 어떤 권한을 줄지 결정하는 것 — "홍길동은 관리자 페이지에 접근할 수 있는가?"
아파트 비유로 설명하면, 인증은 "입주민 카드로 본인 확인"이고, 인가는 "이 카드로 어디까지 출입할 수 있는지 결정"입니다. 입주민 카드로 본인 집은 들어갈 수 있지만, 관리사무소 금고에는 들어갈 수 없습니다.
OAuth 2.0은 이 두 가지를 모두 다루는 프로토콜인데, 특히 인가 부분에 초점을 맞추고 있습니다. 이 부분은 뒤에서 자세히 설명하겠습니다.
인증 방식은 어떻게 발전해 왔나
현재 OAuth 2.0이 왜 필요한지 이해하려면, 그 전에 어떤 방식들이 있었고 왜 한계가 있었는지를 알아야 합니다. 각 방식은 이전 방식의 문제를 해결하기 위해 등장했습니다.
1단계: 비밀번호 직접 전달
가장 원시적인 방식입니다. 사용자의 아이디와 비밀번호를 매번 직접 전달합니다.
비유하면, 은행에 갈 때마다 주민등록번호를 큰 소리로 외치는 것과 같습니다. 한 번이면 괜찮을지 몰라도, 매번 반복하면 주변 사람이 다 들을 수 있습니다.
웹에서도 마찬가지입니다. 매 요청마다 비밀번호를 전송하면 네트워크를 감청하는 누군가에게 비밀번호가 노출될 수 있습니다. 특히 바이브코딩으로 만든 코드에서 흔히 보이는 실수가, API 호출 시 비밀번호를 URL에 포함하는 것입니다.
// 위험한 예시 (절대 이렇게 하면 안 됩니다)
https://api.example.com/data?username=hong&password=1234
2단계: API Key
비밀번호 대신 별도의 "키"를 발급하는 방식입니다. 비밀번호를 직접 주고받지 않으니 한 단계 나아졌습니다.
비유하면, 주민등록번호 대신 회원 카드를 만들어 쓰는 것입니다. 카드가 도난당해도 주민등록번호는 안전합니다. 카드만 정지하면 됩니다.
하지만 API Key에도 문제가 있습니다.
- 권한 구분이 안 됩니다: 키 하나로 모든 것을 할 수 있습니다. 읽기만 허용하고 싶어도, 쓰기 권한까지 함께 갑니다
- 만료가 없습니다: 한 번 유출되면 키를 직접 폐기하기 전까지 계속 사용 가능합니다
- 누가 사용하는지 모릅니다: 키만 있으면 누구든 사용할 수 있어서, 유출 시 추적이 어렵습니다
AI 코딩에서 가장 많이 발생하는 보안 사고가 API Key 노출입니다. AI가 "동작하는 코드"를 만들면서 키를 코드 파일에 직접 넣는 경우가 많기 때문입니다.
3단계: 세션 기반 인증
웹사이트에서 오랫동안 사용된 방식입니다. 한 번 로그인하면 서버가 "세션"이라는 임시 기록을 만들고, 브라우저에 "세션 ID"가 담긴 쿠키를 보냅니다. 이후 요청에서는 쿠키만 보내면 서버가 "아, 아까 로그인한 사람이구나"라고 인식합니다.
비유하면, 놀이공원에서 입장할 때 받는 팔찌와 같습니다. 처음 입장할 때 신분 확인을 하고 팔찌를 채워주면, 이후 놀이기구를 탈 때마다 신분증을 보여줄 필요 없이 팔찌만 보여주면 됩니다.
세션 방식은 매번 비밀번호를 보내지 않아도 되므로 더 안전합니다. 하지만 서버가 모든 세션 정보를 기억하고 있어야 합니다. 사용자가 수만 명이 되면 서버의 부담이 커집니다. 또한 여러 서버를 운영할 때 세션 동기화 문제가 발생합니다.
4단계: JWT (토큰 기반 인증)
JWT(JSON Web Token)는 세션의 부담을 줄이기 위해 등장했습니다. 서버가 세션을 기억하는 대신, 사용자 정보를 "토큰"이라는 문자열 안에 담아서 사용자에게 돌려줍니다. 이후 요청에서는 이 토큰을 보내면 됩니다.
비유하면, 놀이공원 팔찌에 이름, 입장 시간, 이용 가능한 놀이기구 목록이 모두 적혀 있는 것입니다. 직원은 팔찌만 읽으면 되고, 별도로 입장 명부를 확인할 필요가 없습니다.
JWT의 장점은 서버가 상태를 저장하지 않아도 된다(Stateless)는 것입니다. 하지만 토큰이 탈취되면 만료 전까지 막을 수 없다는 단점이 있습니다. 그리고 핵심적으로, JWT는 "우리 서비스 안에서의 인증"은 해결하지만, "다른 서비스의 데이터에 접근하는 문제"는 해결하지 못합니다.
예를 들어, 내 블로그에서 사용자의 LinkedIn 계정에 글을 대신 올리고 싶다면? JWT만으로는 불가능합니다. LinkedIn이 내 블로그를 신뢰할 이유가 없기 때문입니다.
바로 이 문제를 해결하는 것이 OAuth 2.0입니다.
OAuth 2.0이란
OAuth 2.0을 한 문장으로 정의하면 이렇습니다.
"비밀번호를 주지 않고, 필요한 권한만 빌려주는 방식"
호텔 카드키 비유
호텔에 체크인하면 카드키를 받습니다. 이 카드키로 할 수 있는 것과 없는 것이 명확합니다.
- 내 객실 문은 열 수 있습니다
- 수영장, 피트니스센터에 출입할 수 있습니다
- 다른 사람의 객실에는 들어갈 수 없습니다
- 호텔 금고에는 접근할 수 없습니다
- 체크아웃하면 카드키는 무효화됩니다
OAuth 2.0의 액세스 토큰이 바로 이 카드키입니다. 호텔(LinkedIn 같은 서비스)이 투숙객(사용자)의 동의를 받고, 제한된 권한이 담긴 카드키(토큰)를 발급하는 것입니다.
마스터키(비밀번호)는 호텔이 보관합니다. 우리 앱은 마스터키를 볼 수도 없고, 필요하지도 않습니다. 카드키만 있으면 허용된 범위 안에서 할 일을 할 수 있습니다.
발레파킹 비유
또 다른 비유로, 발레파킹을 생각해 보겠습니다.
고급 레스토랑에 차를 몰고 갔습니다. 발레파킹 직원에게 차를 맡기려고 합니다. 이때 두 가지 선택지가 있습니다.
선택 1: 차 키 전체를 직접 줍니다. 직원은 차를 이동할 수 있지만, 트렁크도 열 수 있고, 블루투스로 연결된 폰의 주소록도 볼 수 있습니다.
선택 2: 발레 전용 키를 줍니다. 차를 이동하는 것만 가능하고, 트렁크는 열 수 없습니다. 주행 거리도 제한되어 있습니다.
OAuth 2.0은 선택 2입니다. 전체 권한(비밀번호)을 넘기는 대신, 필요한 권한만 담긴 제한된 키(토큰)를 발급하는 것입니다.
기술적 동작 원리
비유를 이해했으니, 실제로 어떻게 동작하는지 살펴보겠습니다. OAuth 2.0에는 4개의 등장인물이 있습니다.
| 등장인물 | 역할 | 호텔 비유 |
|---|---|---|
| 사용자(Resource Owner) | 권한을 가진 사람 | 투숙객 |
| 우리 앱(Client) | 권한을 빌리려는 앱 | 룸서비스 직원 |
| 인증 서버(Authorization Server) | 토큰을 발급하는 곳 | 프론트 데스크 |
| 리소스 서버(Resource Server) | 데이터가 있는 곳 | 호텔 시설 |
LinkedIn API 연동을 예로 들면 이렇습니다.
- 우리 앱이 사용자에게 말합니다: "LinkedIn에 글을 올리려면 권한이 필요합니다. LinkedIn에서 허락해주세요."
- 사용자가 LinkedIn 로그인 페이지에서 "허용"을 누릅니다
- LinkedIn 인증 서버가 우리 앱에 "인증 코드"를 줍니다
- 우리 앱이 인증 코드를 "액세스 토큰"으로 교환합니다
- 우리 앱이 액세스 토큰으로 LinkedIn 리소스 서버에 글을 올립니다
왜 인증 코드를 바로 쓰지 않고 토큰으로 교환할까요? 인증 코드는 사용자의 브라우저를 경유하므로 노출 위험이 있습니다. 토큰 교환은 서버 간 직접 통신으로 이루어져서 외부에 노출되지 않습니다. 보안을 한 단계 더 강화하는 장치입니다.
핵심 개념 정리
OAuth 2.0에서 자주 나오는 용어를 정리합니다.
스코프(Scope): 토큰에 부여되는 권한의 범위입니다. LinkedIn의 경우 w_member_social이라는 스코프가 "게시물 작성" 권한을 의미합니다. 호텔 카드키에 "수영장 출입 가능" "피트니스 출입 가능"이라고 적혀 있는 것과 같습니다.
액세스 토큰(Access Token): API를 호출할 때 보내는 인증 키입니다. 유효 기간이 있어서, 만료되면 더 이상 사용할 수 없습니다. LinkedIn의 경우 60일입니다.
리프레시 토큰(Refresh Token): 액세스 토큰이 만료되었을 때 새 토큰을 발급받는 데 사용합니다. 재로그인 없이 토큰을 갱신할 수 있게 해주는 장치입니다. 호텔 체크인 확인서로 카드키를 재발급받는 것과 비슷합니다.
Redirect URI: OAuth 인증이 끝난 후 사용자를 돌려보내는 주소입니다. LinkedIn이 "인증 완료했으니 이 주소로 돌아가세요"라고 안내하는 곳입니다. 이 주소가 사전에 등록된 것과 일치하지 않으면 인증이 거부됩니다.
Client ID / Client Secret: 우리 앱의 신분증과 비밀번호입니다. Client ID는 공개되어도 괜찮지만, Client Secret은 절대로 외부에 노출되면 안 됩니다. 이 값이 노출되면 누군가가 우리 앱을 사칭할 수 있습니다.
바이브코딩 시대에 OAuth가 특히 중요한 이유
바이브코딩과 AI 에이전트가 보편화되면서, 코드를 직접 작성하지 않는 사람도 API를 연동하고 소셜 로그인을 추가할 수 있게 되었습니다. 이것은 좋은 변화이지만, 동시에 새로운 위험도 만들었습니다.
위험 1: 비밀 값이 코드에 직접 들어간다
AI에게 "Google 로그인 추가해줘"라고 요청하면, AI는 동작하는 코드를 만들어 줍니다. 하지만 이때 Client Secret을 어디에 넣는지가 중요합니다.
안전한 방식은 환경 변수(.env 파일)에 저장하고 코드에서 참조하는 것입니다. 위험한 방식은 코드 파일에 직접 문자열로 넣는 것입니다.
// 위험: 코드에 직접 넣은 경우 const CLIENT_SECRET = "WPL_AP1.H9b9ObRBH7o80O4P"; // 안전: 환경 변수에서 가져오는 경우 const CLIENT_SECRET = process.env.LINKEDIN_CLIENT_SECRET;
AI가 첫 번째 방식으로 코드를 생성했다면, 이것을 GitHub에 올리는 순간 전 세계에 비밀키가 공개됩니다. 실제로 GitHub에서 자동으로 노출된 API 키를 탐지하는 봇이 돌아다닐 정도로 흔한 사고입니다.
그런데 .env 파일에 넣었다고 끝이 아닙니다. .env 파일 자체가 GitHub에 올라가면 똑같은 문제가 발생합니다. 이것을 막아주는 것이 .gitignore 파일입니다.
.gitignore는 "이 파일은 Git에 올리지 마세요"라고 지정하는 목록 파일입니다. 프로젝트 루트에 .gitignore 파일을 만들고 안에 .env를 적어두면, git add나 git push를 해도 .env 파일은 무시됩니다.
# .gitignore 파일 내용 예시
.env
.env.local
.env.production
node_modules/
대부분의 프레임워크(Next.js, React 등)는 프로젝트를 생성할 때 .gitignore를 자동으로 포함합니다. 하지만 바이브코딩으로 프로젝트를 처음부터 만들 때는 이 파일이 빠져 있을 수 있습니다. 코드를 GitHub에 올리기 전에 .gitignore에 .env가 포함되어 있는지 반드시 확인하세요.
위험 2: 서버와 클라이언트의 구분이 무너진다
OAuth 2.0에서 토큰 교환은 반드시 서버에서 이루어져야 합니다. Client Secret이 포함되기 때문입니다. 하지만 바이브코딩 도구 중에는 프론트엔드와 백엔드의 구분 없이 코드를 생성하는 것이 있습니다.
결과적으로, 브라우저에서 실행되는 코드에 Client Secret이 들어가는 경우가 생깁니다. 브라우저의 개발자 도구를 열면 누구나 이 값을 볼 수 있습니다.
위험 3: 토큰을 안전하지 않은 곳에 저장한다
액세스 토큰을 어디에 저장하는지도 중요합니다. AI가 편의상 localStorage에 저장하는 코드를 생성할 수 있는데, localStorage는 같은 도메인의 모든 JavaScript에서 접근할 수 있어서 XSS(크로스 사이트 스크립팅) 공격에 취약합니다.
안전한 저장소는 HTTP-Only 쿠키이거나 서버 측 DB입니다.
확인 체크리스트
AI가 생성한 OAuth 관련 코드를 받았을 때, 아래 항목을 확인하면 대부분의 보안 문제를 잡을 수 있습니다.
| 확인 항목 | 안전 | 위험 |
|---|---|---|
| Client Secret 위치 | 환경 변수 (.env) | 코드 파일에 직접 입력 |
| 토큰 교환 위치 | 서버 (API Route) | 브라우저 (프론트엔드) |
| 토큰 저장소 | 서버 DB 또는 HTTP-Only 쿠키 | localStorage |
| Redirect URI | 사전 등록된 고정 URL | 동적 URL 또는 와일드카드 |
| HTTPS 사용 | 프로덕션에서 HTTPS 필수 | HTTP로 토큰 전송 |
OAuth 2.0은 어디에 쓰이나
이미 우리가 매일 사용하는 서비스에 OAuth 2.0이 적용되어 있습니다.
소셜 로그인: "Google로 로그인", "카카오로 로그인" 버튼이 모두 OAuth 2.0 기반입니다. 우리 서비스가 Google이나 카카오에 "이 사용자의 이메일과 이름을 알려주세요"라고 요청하는 것입니다. 비밀번호는 전달되지 않습니다.
API 연동: 블로그에서 LinkedIn에 자동으로 글을 올리거나, Slack에 알림을 보내거나, Google Calendar에 일정을 추가하는 기능은 모두 OAuth 2.0으로 권한을 위임받아 동작합니다.
서드파티 앱: Notion에서 "Google Drive 연동" 버튼을 누르면, Notion이 Google Drive의 파일에 접근할 수 있게 됩니다. 이때 Google 비밀번호를 Notion에 알려주지 않습니다. OAuth 2.0이 그 역할을 합니다.
인증 방식 비교 정리
지금까지 살펴본 인증 방식을 한눈에 비교합니다.
| 방식 | 핵심 원리 | 장점 | 한계 |
|---|---|---|---|
| 비밀번호 전달 | 매번 ID/PW 전송 | 구현이 단순 | 매 요청마다 노출 위험 |
| API Key | 별도 키 발급 | 비밀번호 대신 키 사용 | 권한 구분 불가, 만료 없음 |
| 세션 | 서버가 상태 저장 | 비밀번호 1회만 전송 | 서버 부담, 확장 어려움 |
| JWT | 토큰에 정보 포함 | 서버 상태 저장 불필요 | 외부 서비스 접근 불가 |
| OAuth 2.0 | 권한 위임 | 비밀번호 불필요, 권한 제한 가능 | 구현 복잡도 |
각 방식이 틀린 것이 아닙니다. 상황에 따라 적합한 방식이 다릅니다. API Key는 내부 서비스 간 통신에 여전히 널리 사용되고, 세션은 전통적인 웹 애플리케이션에서 검증된 방식입니다. OAuth 2.0은 특히 외부 서비스와의 연동에서 빛을 발합니다.
정리: 코드를 쓰지 않아도 구조는 알아야 합니다
OAuth 2.0의 핵심을 세 줄로 요약합니다.
- 비밀번호를 직접 주지 않습니다 — 사용자의 비밀번호는 원래 서비스(LinkedIn, Google 등)만 알고 있습니다
- 필요한 권한만 빌립니다 — 전체 접근이 아니라, "게시물 작성"같은 특정 권한만 요청합니다
- 기한이 있습니다 — 토큰은 만료되며, 필요하면 갱신합니다. 영구적인 접근이 아닙니다
바이브코딩과 AI 에이전트 덕분에 누구나 소프트웨어를 만들 수 있는 시대가 왔습니다. 하지만 "만들 수 있다"와 "안전하게 만들 수 있다"는 다릅니다.
AI가 생성한 코드가 동작한다고 해서 안전한 것은 아닙니다. 특히 인증과 관련된 코드에서는, 무엇이 서버에 있어야 하고 무엇이 클라이언트에 있어야 하는지, 어떤 값이 노출되면 위험한지를 이해하고 있어야 합니다.
코드를 직접 작성하지 않더라도 인증의 구조를 알고 있으면, AI가 만든 코드에서 위험 신호를 발견할 수 있습니다. 그것이 이 글의 목적입니다.
