Node.js · 2026-03-30

Node.js로 구현하는 OAuth2 클라이언트와 보안 팁

Node.js 환경에서 authorization code 흐름을 중심으로 OAuth2 클라이언트 구현 방법과 PKCE, 토큰 관리, CSRF 방어 등 현실적 보안 고려사항을 예제 코드와 함께 정리한 기술자료

작성일 : 2026-03-30 ㆍ 작성자 : 관리자
post
목차

개요

OAuth2는 사용자 인증과 권한 부여를 분리한 표준 프로토콜이다. 서버사이드 애플리케이션에서는 authorization code 흐름이 주로 사용되며, Node.js 환경에서는 Express 같은 프레임워크와 함께 구현되는 경우가 많다. 이 글은 authorization code 흐름의 핵심 흐름을 설명하고, 실무에서 자주 놓치는 보안 고려사항과 함께 간단한 Node OAuth2 클라이언트 예제를 제공한다.

OAuth2 authorization code 흐름 요약

authorization code 흐름은 클라이언트가 사용자 대신 인증 서버로부터 권한을 얻는 과정이다. 흐름은 대략 다음과 같다.

  • 클라이언트가 인증 서버로 authorization request를 보냄.
  • 사용자가 인증 및 동의를 완료하면 인증 서버가 authorization code를 클라이언트의 redirect URI로 전달.
  • 클라이언트가 authorization code와 함께 token endpoint로 요청하여 access token(및 refresh token)을 발급받음.

핵심 구현 포인트

PKCE 적용

공개 클라이언트 또는 추가 보안이 필요한 경우 PKCE(Proof Key for Code Exchange)를 사용하면 authorization code를 탈취당하더라도 토큰 교환에 실패하도록 설계된다. 기본 개념은 코드 검증자(code_verifier)와 해시된 코드 챌린지(code_challenge)를 사용하는 것이다.

state 파라미터

CSRF 공격을 방지하기 위해 authorization request에 state 값을 포함하고, 콜백에서 동일한 값을 검증해야 한다. 이 값은 암호화된 세션 값이나 서명된 토큰 형태가 바람직하다.

redirect URI 검증

인증 서버에 등록된 redirect URI와 정확히 일치해야 하며, 와일드카드 사용은 피하는 것이 안전하다.

Node.js 예제: Express 기반 간단 클라이언트

아래 예제는 authorization code + PKCE를 사용한 단순한 흐름을 구현한 코드이다. 실제 배포 시에는 환경변수로 클라이언트 시크릿이나 엔드포인트를 관리하고 HTTPS가 적용되어야 한다.

const express = require('express');
const crypto = require('crypto');
const fetch = require('node-fetch');

const CLIENT_ID = process.env.CLIENT_ID;
const CLIENT_SECRET = process.env.CLIENT_SECRET; // 비밀 값은 서버에서만 사용
const AUTHORIZATION_ENDPOINT = 'https://authorization.server/authorize';
const TOKEN_ENDPOINT = 'https://authorization.server/token';
const REDIRECT_URI = 'https://your.app/callback';

function base64url(buf) {
  return buf.toString('base64')
    .replace(/=/g, '')
    .replace(/\+/g, '-')
    .replace(/\//g, '_');
}

function generateCodeVerifier() {
  return base64url(crypto.randomBytes(32));
}

function generateCodeChallenge(verifier) {
  const hash = crypto.createHash('sha256').update(verifier).digest();
  return base64url(hash);
}

const app = express();

app.get('/auth', (req, res) => {
  const codeVerifier = generateCodeVerifier();
  const codeChallenge = generateCodeChallenge(codeVerifier);
  const state = base64url(crypto.randomBytes(16));

  // 세션 또는 서버 측 저장소에 codeVerifier와 state를 저장하는 로직 필요

  const params = new URLSearchParams({
    response_type: 'code',
    client_id: CLIENT_ID,
    redirect_uri: REDIRECT_URI,
    code_challenge: codeChallenge,
    code_challenge_method: 'S256',
    state
  });

  res.redirect(`${AUTHORIZATION_ENDPOINT}?${params.toString()}`);
});

app.get('/callback', express.urlencoded({ extended: true }), async (req, res) => {
  const { code, state } = req.query;

  // 저장된 state와 비교하여 CSRF 방지
  // 저장된 codeVerifier를 조회

  const tokenResponse = await fetch(TOKEN_ENDPOINT, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code,
      redirect_uri: REDIRECT_URI,
      client_id: CLIENT_ID,
      code_verifier: /* 조회한 code_verifier */ ''
    })
  });

  const tokenData = await tokenResponse.json();
  // access_token, refresh_token 저장 및 만료 관리 필요

  res.json(tokenData);
});

app.listen(3000);

토큰 관리와 저장

access token은 짧은 유효기간을 유지하고, refresh token은 안전한 서버측 저장소에 저장한다. 브라우저 저장소(localStorage 등)에 장기간 저장하는 방식은 탈취 위험이 있어 피하는 편이 권장된다. 토큰은 가능한 범위(scope)를 최소화하고, 만료 시간 체크를 통해 자동 갱신 전략을 적용한다.

보안 고려사항 정리

  • HTTPS 적용: 모든 통신은 TLS를 통해 암호화되어야 한다.
  • PKCE 사용: 공개 클라이언트와 추가 보안을 위해 PKCE를 적용한다.
  • state 검증: CSRF 공격 방지를 위해 반드시 state 값을 검증한다.
  • redirect URI 고정: 인증 서버에 등록된 URI와 정확히 일치하게 설정한다.
  • 클라이언트 시크릿 보호: 서버 사이드에서만 사용하고 로그나 코드 레포지토리에 노출하지 않는다.
  • 토큰 최소 권한화: 필요한 scope만 요청하고 불필요한 권한은 배제한다.
  • 세션 관리: 세션 타임아웃과 세션 고정(session fixation) 방어를 적용한다.
  • 로그 및 모니터링: 비정상 토큰 요청이나 실패 시도를 모니터링한다.

실무 체크리스트

  • 인증 서버와 클라이언트 등록 정보 일치 여부 확인
  • PKCE와 state 적용 여부 점검
  • 토큰 저장 방식(서버 저장소, 암호화) 검토
  • refresh token 사용 정책과 폐기 정책 수립
  • HTTPS 및 보안 헤더(Content-Security-Policy 등) 적용

결론

Node OAuth2 클라이언트 구현은 비교적 명확한 흐름을 따르지만, 세부 보안 조치를 간과하면 취약점이 발생할 수 있다. authorization code와 PKCE 조합, state 검증, 안전한 토큰 저장 및 갱신 정책이 핵심이며, 배포 환경에서는 TLS와 철저한 비밀값 관리가 필수적이다. 제공된 예제는 기본 흐름을 설명하기 위한 것으로, 실제 시스템에는 추가적인 검증과 로깅, 예외 처리가 적용되어야 한다.

Node OAuth2 클라이언트 예제 OAuth2 보안 고려사항 Node authorization code Node.js Node.js OAuth2 PKCE Node.js OAuth2 토큰 관리 Express OAuth2 예제 OAuth2 인증 코드 흐름