인증 vs 인가: 누구나 호출할 수 있는 엔드포인트
이 두 단어는 비슷하게 들리고, 진짜 침해는 그 차이 속에 산다.
- **인증(Authentication)**은 *당신은 누구인가?*다 — 로그인, 신원 증명.
- **인가(Authorization)**는 *당신은 무엇을 할 수 있는가?*다 — 이 로그인한 사용자가 이 데이터에 이 동작을 수행해도 되는지.
AI는 인증은 꽤 잘한다; 라이브러리가 대부분을 처리한다. 인가는 AI가 일상적으로 실패하는 곳인데, 인가는 당신 앱의 규칙에 특화되어 있고 AI는 그 규칙을 모르기 때문이다. 교과서적인 재앙:
// VULNERABLE: 로그인했는지는 점검하지만, 당신이 누구인지는 점검하지 않는다
app.get("/admin/export-all-users", requireLogin, (req, res) => {
res.json(db.getAllUsers()); // 로그인한 사용자라면 누구나 이걸 칠 수 있다
});
그 엔드포인트는 "보호"되어 있다 — 로그인해야만 한다. 하지만 30초 전에 가입한 사람을 포함해 어떤 로그인 사용자든 그것을 호출해 모든 사용자의 데이터를 내려받을 수 있다. 인증은 있고, 인가는 없다. 해결책은 동작 그 자체에 권한을 점검하는 것이다:
// SAFE: 이 사용자가 실제로 admin인지 확인한다
app.get("/admin/export-all-users", requireLogin, (req, res) => {
if (!req.user.isAdmin) return res.status(403).send("Forbidden");
res.json(db.getAllUsers());
});
같은 함정이 도처에서 축소판으로 나타난다: 주문 #1234를 반환하면서 그 주문이 요청한 사용자의 것인지 점검하지 않는 엔드포인트. 누구나 URL의 숫자를 바꿔서 남의 주문을 읽을 수 있다. 규칙은 화려하지 않고 절대적이다: 데이터를 다루는 모든 엔드포인트에서 인가를 점검하고, 클라이언트가 보낸 ID를 신뢰하지 말라. 숨겨진 URL이 숨겨져 있다는 이유로 안전하다고 가정하지 말라 — "아무도 이게 존재하는 줄 모른다"는 보안 통제가 아니다.