Chapter 11-5 Django Mini Project - GitHub OAuth2 로그인 구현

2025. 5. 16. 15:43·Django

📌 Django Mini Project - GitHub OAuth2 로그인 구현


🔥 목표

GitHub의 OAuth2 인증 방식을 활용해 사용자가 자신의 GitHub 계정으로 로그인할 수 있도록 구현합니다.

  • GitHub 개발자 설정에서 OAuth 앱 등록
  • client_id와 client_secret을 이용한 인증 흐름 구현

🧭 GitHub 앱 등록 절차

🔗 GitHub OAuth App 등록하기

  1. New OAuth App 클릭
  2. 다음 항목 입력
    • Application name
    • Homepage URL: 예) http://localhost:8000
    • Authorization callback URL: 예) http://localhost:8000/oauth/github/callback/
  3. 앱 등록 후 Client ID와 Client Secret 발급
  4. github oauth 검색해서 ID 요청 확인 -> 토큰 발급 해서 처리 
  5. 응답형식은 json형태를 파싱해야한다.(parse_qs 통해서)
  6. git hub에서 settings -> email address private 체크 해

⚙️ 설정 파일에 정보 저장 (settings.py)

# settings.py
NAVER_CLIENT_ID = SECRET['naver']['client_id']
NAVER_SECRET = SECRET['naver']['secret']

GITHUB_CLIENT_ID = SECRET['github']['client_id']
GITHUB_SECRET = SECRET['github']['secret']

👉 SECRET은 secret.json 또는 환경 변수 등을 활용하여 민감한 정보를 보호합니다.

"github": {
    "client_id":"your id",
    "secret": "secretkey"
  }

 

oauth_views.py 설정 parse_qs 활용

## member/oauth_views.py
from urllib.parse import parse_qs, urlencode

import requests
from django.conf import settings
from django.contrib.auth import get_user_model, login
from django.core import signing
from django.http import Http404
from django.shortcuts import redirect, render
from django.urls import reverse
from django.views.generic import RedirectView

from member.forms import NicknameForm

User = get_user_model()

NAVER_CALLBACK_URL = '/oauth/naver/callback/'
NAVER_STATE = 'naver_login'
NAVER_LOGIN_URL = 'https://nid.naver.com/oauth2.0/authorize'
NAVER_TOKEN_URL = 'https://nid.naver.com/oauth2.0/token'
NAVER_PROFILE_URL = 'https://openapi.naver.com/v1/nid/me'

GITHUB_CALLBACK_URL = '/oauth/github/callback/'
GITHUB_STATE = 'github_login'
GITHUB_LOGIN_URL = 'https://github.com/login/oauth/authorize'
GITHUB_TOKEN_URL = 'https://github.com/login/oauth/access_token'
GITHUB_PROFILE_URL = 'https://api.github.com/user'


class NaverLoginRedirectView(RedirectView):
    def get_redirect_url(self, *args, **kwargs):
        domain = self.request.scheme + '://' + self.request.META.get('HTTP_HOST', '')

        callback_url = domain + NAVER_CALLBACK_URL
        state = signing.dumps(NAVER_STATE)

        params = {
            'response_type': 'code',
            'client_id': settings.NAVER_CLIENT_ID,
            'redirect_uri': callback_url,
            'state': state
        }

        return f'{NAVER_LOGIN_URL}?{urlencode(params)}'

def naver_callback(request):
    code = request.GET.get('code')
    state = request.GET.get('state')

    if NAVER_STATE != signing.loads(state):
        raise Http404

    access_token = get_naver_access_token(code, state)
    profile_response = get_naver_profile(access_token)

    email = profile_response.get('email')

    user = User.objects.filter(email=email).first()

    if user:
        if not user.is_active:
            user.is_active = True
            user.save()

        login(request, user)
        return redirect('main')
    return redirect(
        reverse('oauth:nickname') + f'?access_token={access_token}&oauth=naver'
    )

class GithubLoginRedirectView(RedirectView):
    def get_redirect_url(self, *args, **kwargs):
        domain = self.request.scheme + '://' + self.request.META.get('HTTP_HOST', '')

        callback_url = domain + GITHUB_CALLBACK_URL
        state = signing.dumps(GITHUB_STATE)

        params = {
            'response_type': 'code',
            'client_id': settings.GITHUB_CLIENT_ID,
            'redirect_uri': callback_url,
            'state': state
        }

        return f'{GITHUB_LOGIN_URL}?{urlencode(params)}'


def github_callback(request):
    code = request.GET.get('code')
    state = request.GET.get('state')

    if GITHUB_STATE != signing.loads(state):
        raise Http404

    access_token = get_github_access_token(code, state)

    if not access_token:
        raise Http404

    profile_response = get_github_profile(access_token)

    print('profile request', profile_response)
    email = profile_response.get('email')

    user = User.objects.filter(email=email).first()

    if user:
        if not user.is_active:
            user.is_active = True
            user.save()

        login(request, user)
        return redirect('main')
    return redirect(
        reverse('oauth:nickname') + f'?access_token={access_token}&oauth=github'
    )


def oauth_nickname(request):
    access_token = request.GET.get('access_token')
    oauth = request.GET.get('oauth')

    if not access_token or oauth not in ['naver', 'github']:
        return redirect('login')

    form = NicknameForm(request.POST or None)

    if form.is_valid():
        user = form.save(commit=False)

        if oauth == 'naver':
            profile = get_naver_profile(access_token)
        else:
            profile = get_github_profile(access_token)

        email = profile.get('email')

        if User.objects.filter(email=email).exists():
            raise Http404

        user.email = email

        user.is_active = True
        user.set_password(User.objects.make_random_password())
        user.save()

        login(request, user)
        return redirect('main')

    return render(request, 'auth/nickname.html', {'form': form})


def get_naver_access_token(code, state):
    params = {
        'grant_type': 'authorization_code',
        'client_id': settings.NAVER_CLIENT_ID,
        'client_secret': settings.NAVER_SECRET,
        'code': code,
        'state': state
    }

    response = requests.get(NAVER_TOKEN_URL, params=params)
    result = response.json()
    return result.get('access_token')


def get_naver_profile(access_token):
    headers = {
        'Authorization': f'Bearer {access_token}'
    }

    response = requests.get(NAVER_PROFILE_URL, headers=headers)

    if response.status_code != 200:
        raise Http404

    result = response.json()
    return result.get('response')

def get_github_access_token(code, state):
    params = {
        'grant_type': 'authorization_code',
        'client_id': settings.GITHUB_CLIENT_ID,
        'client_secret': settings.GITHUB_SECRET,
        'code': code,
        'state': state
    }

    response = requests.get(GITHUB_TOKEN_URL, params=params)

    response_str = response.content.decode()
    response_dict = parse_qs(response_str)

    access_token = response_dict.get('access_token', [])[0]

    return access_token


def get_github_profile(access_token):
    headers = {
        'Authorization': f'Bearer {access_token}'
    }

    response = requests.get(GITHUB_PROFILE_URL, headers=headers)

    if response.status_code != 200:
        raise Http404

    result = response.json()

    if not result.get('email'):
        result['email'] = f'{result["login"]}@id.github.com'
    return result

 

login.html 수정

<!-- templates/auth/login.html -->
{% extends 'base.html' %}
{% block content %}
    <h1 class="title">로그인</h1>
    <form method="POST">
      {% csrf_token %}
      {% include 'include/form.html' %}
    <button class="btn btn-primary">로그인</button>
    </form>
    <a href="{% url 'oauth:naver_login' %}" class="btn btn-success mt-2">
     네이버 로그인
    </a>

    <a href="{% url 'oauth:github_login' %}" class="btn btn-dark mt-2">
     깃허브 로그인
    </a>

{% endblock %}

 

member/urls.py 추가

from django.urls import path
from . import oauth_views

app_name = 'oauth'

urlpatterns = [
    # naver
    path('naver/login/', oauth_views.NaverLoginRedirectView.as_view(), name='naver_login'),
    path('naver/callback/', oauth_views.naver_callback, name='naver_callback'),

    # github
    path('github/login/', oauth_views.GithubLoginRedirectView.as_view(), name='github_login'),
    path('github/callback/', oauth_views.github_callback, name='github_callback'),

    path('nickname/', oauth_views.oauth_nickname, name='nickname'),
]

📄 참고 문서

  • GitHub OAuth 앱 권한 부여 공식 문서
  • GitHub OAuth 기본 URL:
  • https://github.com/login/oauth/authorize

🧱 구현해야 할 뷰 흐름 예시

  1. GitHubLoginRedirectView
    • GitHub 로그인 URL 생성 후 리디렉션
    • 파라미터: client_id, redirect_uri, scope, state
  2. github_callback(request)
    • GitHub에서 code를 받고, access_token 요청
    • 토큰으로 사용자 프로필(https://api.github.com/user) 조회
    • 유저 존재 여부 확인 후 로그인 or 회원가입
  3. 사용자 생성 로직
    • 이메일 또는 GitHub ID를 기준으로 새로운 유저 생성
    • 무작위 비밀번호 + is_active=True 처리

💡 실전 팁

항목 설명

state 값 CSRF 방지용으로 반드시 랜덤하고 검증해야 함 (signing 활용 추천)
redirect_uri GitHub OAuth 앱 등록 시 입력한 URL과 정확히 일치해야 함
access_token 요청 https://github.com/login/oauth/access_token 로 POST 요청 필요
사용자 API https://api.github.com/user 에서 로그인한 유저 정보 확인 가능

✅ 기대 효과

  • 사용자는 GitHub 계정을 통해 간편 로그인 가능
  • 추가 회원가입 없이 개발자 대상 서비스에서 효율적인 UX 제공
  • OAuth2 구조를 직접 구현하며 인증 흐름을 실습할 수 있음

GitHub 로그인을 구현함으로써, 다양한 소셜 로그인 기능을 직접 확장할 수 있는 기반을 마련할 수 있습니다. Kakao, Google, Apple 등의 소셜 로그인도 같은 구조로 손쉽게 연결할 수 있어요 😊

'Django' 카테고리의 다른 글

Chapter 14-1 drf-yasg 사용법 완전 정리  (0) 2025.05.21
Chapter 12-1 Django TDD(Test-Driven Development)란?  (0) 2025.05.19
Chapter 11-4 Django에서 네이버 OAuth2 로그인 구현하기  (0) 2025.05.16
Chapter 11-3 Django OAuth2 완전 정복 - 인증과 권한 부여의 표준 프레임워크  (0) 2025.05.16
Chapter 11-2 Django 인스타그램 프로젝트 - 검색 기능 구현 정리  (0) 2025.05.16
'Django' 카테고리의 다른 글
  • Chapter 14-1 drf-yasg 사용법 완전 정리
  • Chapter 12-1 Django TDD(Test-Driven Development)란?
  • Chapter 11-4 Django에서 네이버 OAuth2 로그인 구현하기
  • Chapter 11-3 Django OAuth2 완전 정복 - 인증과 권한 부여의 표준 프레임워크
Chansman
Chansman
안녕하세요! 코딩을 시작한 지 얼마 되지 않은 초보 개발자 찬스맨입니다. 이 블로그는 제 학습 기록을 남기고, 다양한 코딩 실습을 통해 성장하는 과정을 공유하려고 합니다. 초보자의 눈높이에 맞춘 실습과 팁, 그리고 개발하면서 겪은 어려움과 해결 과정을 솔직하게 풀어내려 합니다. 함께 성장하는 개발자 커뮤니티가 되기를 바랍니다.
  • Chansman
    찬스맨의 프로그래밍 스토리
    Chansman
  • 전체
    오늘
    어제
    • 분류 전체보기 (798)
      • Python (32)
      • 프로젝트 (113)
      • 과제 (25)
      • Database (40)
      • 멘토링 (11)
      • 특강 (37)
      • 기술블로그 (41)
      • 기술블로그-Fastapi편 (33)
      • 기술블로그-Django편 (154)
      • 기술블로그-Flask편 (36)
      • AI 분석 (5)
      • HTML & CSS (31)
      • JavaScript (17)
      • AWS_Cloud (21)
      • 웹스크래핑과 데이터 수집 (14)
      • Flask (42)
      • Django (77)
      • Fastapi (16)
      • 연예 (14)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    btscomeback
    RM
    college reunions
    btsreunion
    newpoliticalparty
    americanlaw
    titaniumcase
    life reflection
    homebartrend
    lawsuitculture
    urbantrends
    chinanightlife
    classaction
    basalcellcarcinoma
    trumpmuskclash
    global politics
    remittance
    livebroadcast
    americaparty
    travel ban
    smartphonedurability
    btsdischarge
    chatgpterror
    뷔
    hotcoffeecase
    gpterror
    bts
    btsjungkook
    youngprofessionals
    self-growth
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Chansman
Chapter 11-5 Django Mini Project - GitHub OAuth2 로그인 구현
상단으로

티스토리툴바