Django

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

Chansman 2025. 5. 16. 15:43

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


🔥 목표

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

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

🧭 GitHub 앱 등록 절차

🔗 GitHub OAuth App 등록하기

  1. New OAuth App 클릭
  2. 다음 항목 입력
  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'),
]

📄 참고 문서


🧱 구현해야 할 뷰 흐름 예시

  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 등의 소셜 로그인도 같은 구조로 손쉽게 연결할 수 있어요 😊