🧩 GitHub App vs OAuth App 완벽 비교

GitHub에서 애플리케이션을 통합하거나 자동화를 구축할 때 OAuth AppGitHub App 중 어떤 것을 선택해야 할지 고민되는 경우가 많습니다. 두 모델은 겉보기엔 비슷하지만, 동작 주체, 권한 구조, 보안 모델, 구현 방식이 완전히 다릅니다. 이 글에서는 핵심 개념부터 실제 구현 세부사항까지 Markdown 형식으로 정리했습니다.


📘 1. 개념 요약

항목GitHub AppOAuth App
주체(Acting identity)앱 자체(봇) 또는 사용자(on-behalf)항상 사용자 컨텍스트로 동작
권한 모델세분화된 권한(Fine-grained permissions)단일 Scope 기반, 광범위 권한
설치/승인 방식조직 또는 리포지토리에 “설치”사용자가 “승인(Authorize)”
토큰 유효기간짧은 유효기간 (약 1시간)장기 또는 무기한 유효
자동화 및 독립성사용자 탈퇴 후에도 지속 작동사용자 권한 변경 시 중단 가능
웹훅 구성한 번 설정 → 설치된 모든 Repo 이벤트 자동 수신각 Repo별 수동 설정 필요
추천 사용 시나리오조직 단위 통합, 봇, CI/CD, 자동화사용자 로그인, 개인 Repo 접근

💡 2. 왜 두 모델이 존재하나?

  • GitHub은 “OAuth App = 사용자 중심”, “GitHub App = 조직 중심” 으로 역할을 구분했습니다.

  • 과거 OAuth App은 권한이 너무 넓고 토큰이 장기 유효해 보안 리스크가 컸습니다.

  • GitHub App은 이를 개선해

    • 짧은 토큰 수명,
    • 세분화된 권한 설정,
    • 조직별 설치 및 제거 제어 를 제공합니다.

🔐 즉, 새로운 통합이나 자동화 프로젝트라면 GitHub App이 사실상 표준입니다.


🧮 3. 설치 및 권한 요청 흐름

✅ GitHub App

1
2
3
4
1. "New GitHub App" 생성
2. 앱 이름, 설명, 웹훅 URL, 권한 설정
3. 조직/사용자가 리포지토리에 설치 (설치 범위 선택)
4. 필요 시 사용자 on-behalf 권한 부여
  • 세분화된 권한 지정 가능 (예: Issues: Read, Pull Requests: Write)
  • 설치 후 어떤 리포지토리에 접근할지 세밀하게 설정 가능

✅ OAuth App

1
2
3
4
1. "New OAuth App" 생성
2. Client ID / Client Secret / Redirect URI 지정
3. 사용자가 승인 (Authorize)
4. 승인된 사용자 계정의 모든 접근 가능한 Repo 사용 가능
  • scope 단위로 권한 요청 (예: repo, user, read:org)
  • 권한 범위가 포괄적이므로 주의 필요

🔑 4. 인증 및 토큰 구조

GitHub App

  • JWT 인증

    • 앱은 Private Key로 JWT를 생성 → GitHub에 인증
  • 설치 토큰(Installation Token)

    • /app/installations/:id/access_tokens API로 생성
    • 유효기간: 약 1시간
  • API 호출 주체

    • 앱 자체(bot 계정)로 호출 가능
    • 필요 시 사용자 토큰(on-behalf) 발급 가능

OAuth App

  • OAuth 2.0 표준 플로우

    • 사용자 로그인 → 승인 → 코드 교환 → 액세스 토큰 획득
  • 토큰 특성

    • 만료되지 않거나 매우 긴 수명을 가짐
    • 일부 구현에는 refresh token 미제공
  • API 호출 주체

    • 항상 사용자 권한(context)으로 동작

⚙️ 5. 권한(Permissions) 및 리포지토리 접근

항목GitHub AppOAuth App
권한 범위권한 단위별 세밀 조정 가능 (read/write 구분)scope 단위로 광범위
설치 범위특정 리포지토리만 선택 가능사용자 접근 가능한 모든 Repo
리포지토리 조회/installation/repositories API 지원전역 사용자 Repo 접근
최소 권한 원칙(Least Privilege)지원 O지원 X

📡 6. 웹훅(Webhook) 처리 구조

GitHub App

  • 앱 생성 시 1회 등록 → 모든 설치 리포지토리 이벤트 자동 수신
  • installation 이벤트로 설치/제거 알림
  • CI/CD 자동화에 최적화

OAuth App

  • 각 리포지토리마다 별도로 웹훅을 설정해야 함
  • 사용자 토큰 만료나 삭제 시 웹훅 정리 자동화 필요

🧱 7. API 호출 비교

항목GitHub AppOAuth App
호출 주체앱 봇(@my-app[bot])사용자
API 범위설치된 리포지토리만 접근사용자 접근 가능한 모든 Repo
Git 접근설치 토큰으로 Git HTTP 인증 가능repo scope 필요
보안 제어세밀하고 단기 토큰 기반장기 토큰 기반, 위험도 높음

🤖 8. Identity (봇 vs 사용자)

  • GitHub App

    • @my-app[bot] 계정 자동 생성
    • 사용자와 독립적으로 존재
    • 사용자 탈퇴 후에도 자동화 지속 가능
  • OAuth App

    • 별도 봇 계정 없음
    • 사용자 권한에 종속
    • 사용자 계정 변경 시 앱 기능 중단 가능

🧩 9. 코드 플로우 및 구현 예시

GitHub App - 실제 구현 코드

1단계: JWT 생성 및 Installation Token 발급

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
const jwt = require('jsonwebtoken');
const axios = require('axios');
const fs = require('fs');

// Private Key로 JWT 생성
function generateJWT(appId, privateKeyPath) {
  const privateKey = fs.readFileSync(privateKeyPath, 'utf8');
  const payload = {
    iat: Math.floor(Date.now() / 1000) - 60,  // 현재 시간 - 60초
    exp: Math.floor(Date.now() / 1000) + 600, // 10분 유효
    iss: appId
  };
  return jwt.sign(payload, privateKey, { algorithm: 'RS256' });
}

// Installation Token 발급
async function getInstallationToken(appId, installationId, privateKeyPath) {
  const jwtToken = generateJWT(appId, privateKeyPath);

  const response = await axios.post(
    `https://api.github.com/app/installations/${installationId}/access_tokens`,
    {},
    {
      headers: {
        'Authorization': `Bearer ${jwtToken}`,
        'Accept': 'application/vnd.github.v3+json'
      }
    }
  );

  return response.data.token; // 약 1시간 유효
}

// API 호출 예시
async function createIssue(installationToken, owner, repo, title, body) {
  const response = await axios.post(
    `https://api.github.com/repos/${owner}/${repo}/issues`,
    { title, body },
    {
      headers: {
        'Authorization': `token ${installationToken}`,
        'Accept': 'application/vnd.github.v3+json'
      }
    }
  );
  return response.data;
}

2단계: Webhook 이벤트 처리

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  const digest = 'sha256=' + hmac.update(payload).digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(digest)
  );
}

// Express.js 예시
app.post('/webhook', (req, res) => {
  const signature = req.headers['x-hub-signature-256'];
  const isValid = verifyWebhookSignature(
    JSON.stringify(req.body),
    signature,
    process.env.WEBHOOK_SECRET
  );

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  // 이벤트 처리
  const event = req.headers['x-github-event'];
  if (event === 'issues') {
    console.log('Issue event:', req.body.action);
    // 자동화 로직 실행
  }

  res.status(200).send('OK');
});

OAuth App - 실제 구현 코드

OAuth 플로우 구현

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
const express = require('express');
const axios = require('axios');

const CLIENT_ID = process.env.GITHUB_CLIENT_ID;
const CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET;
const REDIRECT_URI = 'http://localhost:3000/callback';

// 1단계: 사용자 인증 요청
app.get('/login', (req, res) => {
  const authUrl = `https://github.com/login/oauth/authorize?` +
    `client_id=${CLIENT_ID}&` +
    `redirect_uri=${REDIRECT_URI}&` +
    `scope=repo,user`;
  res.redirect(authUrl);
});

// 2단계: 콜백 처리 및 토큰 교환
app.get('/callback', async (req, res) => {
  const code = req.query.code;

  try {
    // Authorization Code를 Access Token으로 교환
    const tokenResponse = await axios.post(
      'https://github.com/login/oauth/access_token',
      {
        client_id: CLIENT_ID,
        client_secret: CLIENT_SECRET,
        code: code,
        redirect_uri: REDIRECT_URI
      },
      {
        headers: { 'Accept': 'application/json' }
      }
    );

    const accessToken = tokenResponse.data.access_token;

    // 3단계: 사용자 정보 조회
    const userResponse = await axios.get('https://api.github.com/user', {
      headers: {
        'Authorization': `token ${accessToken}`,
        'Accept': 'application/vnd.github.v3+json'
      }
    });

    res.json({
      user: userResponse.data,
      token: accessToken  // 주의: 실제로는 안전하게 저장해야 함
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// 4단계: API 호출 (사용자 권한으로)
async function getUserRepositories(accessToken) {
  const response = await axios.get('https://api.github.com/user/repos', {
    headers: {
      'Authorization': `token ${accessToken}`,
      'Accept': 'application/vnd.github.v3+json'
    },
    params: {
      sort: 'updated',
      per_page: 100
    }
  });
  return response.data;
}

🧠 10. 보안 및 운영 시 유의점

GitHub App

  • ✅ Private Key 안전 보관 (Secret Manager 필수)
  • ✅ 짧은 토큰 유효기간 대비 자동 갱신 로직 구현
  • ✅ 권한 최소화 및 리뷰 절차 적용
  • ✅ Webhook 서명 검증 필수

OAuth App

  • ⚠️ repo scope 요청 시 모든 리포지토리에 쓰기 권한 → 매우 위험
  • ⚠️ 토큰 장기 유효로 인한 유출 위험
  • ⚠️ 사용자 권한 변경(퇴사 등) 시 연동 실패 가능
  • ⚙️ Webhook 제거 자동화 권장

🧭 11. 실제 선택 가이드

상황추천 앱
사용자 로그인 (“Sign in with GitHub”)OAuth App
조직/CI/CD 통합, 자동화GitHub App
다중 리포지토리 접근 제어GitHub App
간단한 개인 툴OAuth App
보안이 중요한 엔터프라이즈 환경GitHub App

💼 12. 실사용 시나리오

시나리오 1: CI/CD 파이프라인 구축 (GitHub Actions 대안)

요구사항:

  • 푸시 이벤트마다 자동 빌드 및 테스트
  • PR에 자동 코멘트 추가
  • 빌드 실패 시 Issue 자동 생성

추천: GitHub App

이유:

  • Webhook이 자동으로 모든 설치된 Repo에서 수신됨
  • 사용자 탈퇴 후에도 지속 작동
  • 세밀한 권한으로 checks:write, issues:write, pull_requests:write만 요청
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Webhook 처리 예시
app.post('/webhook', async (req, res) => {
  const event = req.headers['x-github-event'];

  if (event === 'push') {
    // 빌드 트리거
    await triggerBuild(req.body.repository);
  } else if (event === 'pull_request' && req.body.action === 'opened') {
    // PR 자동 검토 코멘트
    await addPRComment(req.body.pull_request);
  }

  res.status(200).send('OK');
});

시나리오 2: “Sign in with GitHub” 로그인 구현

요구사항:

  • 사용자가 GitHub 계정으로 로그인
  • 사용자 프로필 정보 표시
  • 사용자의 개인 Repo 목록 조회

추천: OAuth App

이유:

  • 사용자 컨텍스트로 동작하는 것이 자연스러움
  • 단순한 로그인/인증 시나리오
  • 복잡한 설치 과정 불필요
1
2
3
4
5
6
7
// 로그인 버튼 클릭 시
function loginWithGitHub() {
  window.location.href =
    'https://github.com/login/oauth/authorize?' +
    `client_id=${CLIENT_ID}&` +
    `scope=read:user,user:email`;
}

시나리오 3: 조직 전체 보안 감사 봇

요구사항:

  • 모든 Repo의 코드 스캔
  • 취약점 발견 시 Issue 자동 생성
  • 조직 관리자에게 주간 리포트 전송
  • 새 Repo 생성 시 자동 감사 추가

추천: GitHub App

이유:

  • 조직 레벨 설치로 모든 Repo 자동 커버
  • 사용자 독립적으로 작동 (봇 계정)
  • installation 이벤트로 새 Repo 감지
  • 세밀한 권한: contents:read, issues:write, security_events:read

시나리오 4: 개인 프로젝트 백업 도구

요구사항:

  • 내 모든 Repo를 정기적으로 백업
  • 로컬 디스크에 저장
  • 간단한 CLI 도구

추천: OAuth App ⚠️ (또는 Personal Access Token)

이유:

  • 개인 사용 목적
  • 복잡한 설치 불필요
  • 사용자 권한으로 충분

시나리오 5: PR 자동 리뷰어 봇 (AI 코드 리뷰)

요구사항:

  • PR 생성 시 자동으로 코드 분석
  • 리뷰 코멘트 추가
  • 승인/변경 요청 자동 제출
  • 여러 조직에서 사용 가능

추천: GitHub App

이유:

  • PR 리뷰는 GitHub App만 가능 (pull_request_review 이벤트)
  • 봇 계정으로 명확한 식별
  • Marketplace 배포 가능
  • 사용자별 설치로 과금 모델 구현 용이
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
app.post('/webhook', async (req, res) => {
  if (req.body.action === 'opened' && req.headers['x-github-event'] === 'pull_request') {
    const { pull_request, repository } = req.body;

    // AI 코드 분석
    const analysis = await analyzeCode(pull_request.diff_url);

    // 리뷰 코멘트 생성
    await createReview({
      owner: repository.owner.login,
      repo: repository.name,
      pull_number: pull_request.number,
      comments: analysis.comments,
      event: analysis.approved ? 'APPROVE' : 'REQUEST_CHANGES'
    });
  }

  res.status(200).send('OK');
});

🚀 13. 결론

  • OAuth App은 “사용자 로그인 및 단순 접근용”
  • GitHub App은 “조직, 자동화, 보안 중심 통합용”
  • 앞으로의 GitHub API 및 Marketplace 통합은 GitHub App 중심으로 발전 중입니다.

✅ 핵심 요약: 새 프로젝트를 시작한다면 GitHub App을 기본값으로 생각하세요. OAuth App은 단순 로그인/인증 시나리오에 한정하는 것이 좋습니다.


❓ 14. 자주 묻는 질문 (FAQ)

Q1: GitHub App과 OAuth App을 동시에 사용할 수 있나요?

A: 네, 가능합니다. 실제로 많은 서비스가 두 가지를 혼용합니다.

  • OAuth App: 사용자 로그인 처리
  • GitHub App: 백그라운드 자동화 및 봇 기능

예를 들어, Slack의 GitHub 통합은 OAuth로 사용자를 인증하고, GitHub App으로 알림을 전송합니다.


Q2: GitHub App의 Installation Token은 어떻게 갱신하나요?

A: Installation Token은 자동 갱신되지 않습니다. 유효기간(약 1시간)이 지나면 새로 발급받아야 합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 토큰 캐싱 및 자동 갱신 예시
let cachedToken = null;
let tokenExpiry = null;

async function getValidToken(appId, installationId, privateKey) {
  const now = Date.now();

  if (cachedToken && tokenExpiry > now + 60000) {
    return cachedToken; // 1분 이상 남았으면 재사용
  }

  // 새 토큰 발급
  cachedToken = await getInstallationToken(appId, installationId, privateKey);
  tokenExpiry = now + 3600000; // 1시간 후 만료

  return cachedToken;
}

Q3: OAuth App에서 GitHub App으로 마이그레이션하려면?

A: 점진적 마이그레이션을 추천합니다.

  1. 1단계: GitHub App 생성 및 테스트 환경에 설치
  2. 2단계: 기존 OAuth App과 병행 운영
  3. 3단계: 새 기능은 GitHub App API로 구현
  4. 4단계: 사용자에게 GitHub App 설치 안내
  5. 5단계: OAuth App 권한 단계적 축소 및 제거

주의사항:

  • API 엔드포인트가 다를 수 있음 (예: /user/repos vs /installation/repositories)
  • 인증 헤더 형식 변경 (token vs Bearer)
  • Webhook URL 변경 필요

Q4: GitHub App은 개인 프로젝트에도 사용할 수 있나요?

A: 네, 가능합니다. 하지만 간단한 개인 도구라면 **Personal Access Token (PAT)**이 더 편할 수 있습니다.

GitHub App 사용이 적합한 경우:

  • 여러 Repo에 설치할 계획
  • 자동화가 사용자 독립적으로 동작해야 함
  • 세밀한 권한 제어 필요

PAT이 적합한 경우:

  • 단일 사용자 전용
  • 빠른 프로토타이핑
  • 스크립트나 CLI 도구

Q5: GitHub App의 rate limit은 어떻게 되나요?

A: GitHub App은 OAuth App보다 훨씬 높은 rate limit을 제공합니다.

앱 유형Rate Limit (시간당)
OAuth App5,000 requests
GitHub App15,000 requests (기본)
GitHub App (조직 설치)최대 180,000 requests

: GitHub App은 설치된 조직의 크기에 비례해 rate limit이 증가합니다.


Q6: Webhook 서명 검증은 왜 필요한가요?

A: 보안을 위해 필수입니다. 서명 검증 없이는 악의적인 공격자가 가짜 이벤트를 전송할 수 있습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// ❌ 위험: 서명 검증 없음
app.post('/webhook', (req, res) => {
  // 누구나 이 엔드포인트로 임의의 데이터를 보낼 수 있음!
  processEvent(req.body);
});

// ✅ 안전: 서명 검증 포함
app.post('/webhook', (req, res) => {
  const signature = req.headers['x-hub-signature-256'];
  if (!verifySignature(req.body, signature, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  processEvent(req.body);
});

Q7: GitHub App을 Marketplace에 배포하려면?

A: 다음 요구사항을 충족해야 합니다.

  1. ✅ 공개 GitHub App 생성
  2. ✅ 명확한 권한 설명 및 최소 권한 원칙 준수
  3. ✅ Webhook URL이 HTTPS로 제공
  4. ✅ 서비스 약관 및 개인정보 보호정책 링크
  5. ✅ 가격 모델 설정 (무료 또는 유료)
  6. ✅ 리뷰 승인 대기

수익 모델 예시:

  • 무료 티어 + 프리미엄 기능
  • 설치된 Repo 수에 따른 과금
  • 월간 구독 모델

Q8: Private Key 유출 시 대처 방법은?

A: 즉시 다음 단계를 실행하세요.

  1. 🚨 GitHub App 설정에서 Private Key 재발급
  2. 🔄 모든 서버에 새 Private Key 배포
  3. 🗑️ 이전 Private Key 완전 삭제
  4. 📊 의심스러운 API 호출 로그 분석
  5. 📢 필요시 사용자에게 보안 알림

예방:

  • Private Key를 Git에 커밋하지 않기 (.gitignore 추가)
  • Secret Manager 사용 (AWS Secrets Manager, HashiCorp Vault 등)
  • 환경 변수로 관리
  • 정기적 Key 로테이션

📚 참고 문서