React-Django 깃허브 로그인

김범기·2024년 6월 14일

django

목록 보기
11/12

개요

개인 프로젝트를 진행하면서 소셜로그인을 구현해야겠다는 생각으로 진행했다.
구글 로그인과 카카오 로그인을 하고 깃허브 로그인 까지 진행해본다.
초보 개발자(호소인)이므로 그냥 내가 나중에 보고 참고하기 위한 글이기에 제대로 안적혀 있을 수 있으니 참고하길 바람.

깃허브 로그인

깃허브도 검색하니, 어렵진 않아보였고, 카카오와 구글 로그인의 경험으로 금방 할 수 있었다.

초기 준비 및 로그인 구현을 이번에는 아래를 참고해서 작성했다.

여기서도 client_id그리고 redirect_uri가 필요하며 또한 client_secret key가 필요하다

참고 블로그1

깃허브 로그인 문서

React Django에서 작업

React

우선 깃허브 로그인 버튼을 만들어 주었다.

<div onClick={() => {navigateGithubLogin()}}>
	<img src={process.env.PUBLIC_URL+"images/githubLogo1.png"} alt=""/>
	<span>Github 로그인</span>
	<div></div>
</div>

인가 코드를 받아야 한다.

이를 위해 navigateGithubLogin()를 아래처럼 구성해서 진행해주었다.

const githubParams :GithubParamsType = {
    "client_id" : githubClientId,
    "redirect_uri" : githubRedirectUri,
    "scope" : "read:user, user:email",
  }
  const gitParams = new URLSearchParams(githubParams).toString()
  function navigateGithubLogin(){
    window.location.href = `https://github.com/login/oauth/authorize?${gitParams}`
  }

카카오와 구글에서 했던것처럼 프론트에서 redirect_uri에 해당하는 컴포넌트를 만들고, 그곳으로 잠깐이지만, 이동해서 코드를 받도록 했다.
해당 컴포넌트에서

const params = new URLSearchParams(search);
  const code = params.get("code");

를 통해서 code를 받아오고

이 code를 Django(back server)으로 github에서 토큰과 유저 정보를 받아서,DB의 유저를 불러와 다시 jwt를 발급해서 사용하는 방식을 이용했다.
그리고 받은 것은 쿠키에 저장하여 이용하도록 했다.
그래서 redirect_uri에 있는 코드는 아래와 같다.

async function handleGetGithubLoginCode(code:string) {
    try {
      const respose = await getGithubLoginCode(code);
      const responseData = respose?.data
      console.log(responseData)
      const tmt_token = responseData.access
      const tmt_refresh_token = responseData.refresh
      const nickname = responseData.userInfo.nickname
      const social = responseData.userInfo.social
      const email = responseData.userInfo.email
      const profile_image_url = responseData.userInfo.profile_image_url
      Cookies.set('tmt_token', tmt_token);
      Cookies.set('tmt_refresh_token', tmt_refresh_token);
      Cookies.set('provide', social);
      Cookies.set('userinfo', JSON.stringify({
        profile_image_url: profile_image_url,
        nickname: nickname,
        email: email
      }));
      navigate('/')

    } catch (error) {
      console.log(error);
    }
  }

getGithubLoginCode()

는 아래처럼 연결시켰다.

// 깃허브 로그인을 위한 
export async function getGithubLoginCode(code:string) { // async, await을 사용하는 경우
  console.log('getGithubLoginCode: ',code)
  try {
    const response = await axios.post(`${base_url}accounts/github/login/`, {code}) // Backtick(`)을 이용해 이렇게 요청할 수도 있다.
    console.log("response===", response)
    return response
  } catch (e:any) {
    // 실패 시 처리
    console.error(e.response.status);
  }
}

Django

우선 urls.py에서 아래처럼 작성을 했다.

    path('github/login/', views.github_login, name='github_login'),

views.py에서는 github_login을 아래처럼 작성했다.

전체적인 틀은 아래처럼 했다.

  1. 코드를 받아온다.
  2. 코드를 통해 토큰과 유저정보를 받아온다.
  3. 이메일도 필요하므로 이메일 정보도 받아온다.
  4. 정보를 통해 유저 데이터를 가져온다.
  5. 유저 데이터가 없으면 생성.
  6. JWT 발행!

Django에서의 전체 코드는 아래처럼 구성을 했다.

# 깃허브 로그인
@api_view(["POST"])
def github_login(request):
    if request.method == "POST":
        client_id = os.environ.get("SOCIAL_AUTH_GITHUB_CLIENT_ID")
        redirect_uri = os.environ.get("SOCIAL_AUTH_GITHUB_REDIRECT_URI")
        client_secret = os.environ.get("SOCIAL_AUTH_GITHUB_CLIENT_SECRET")
        # 로그인 시도
        try:
            # 토큰과 유저정보 받아오기
            request_data = request.data
            code = request_data.get("code")
            print('code===',code)
            # 유저 토큰 가져오기
            github_token = requests.post(f"https://github.com/login/oauth/access_token?code={code}&client_id={client_id}&client_secret={client_secret}&redirect_uri={redirect_uri}",
                headers={"Accept": "application/json"},
            )
            access_token = github_token.json().get("access_token")
            # 유저 정보 데이터 가져오기
            user_data = requests.get("https://api.github.com/user",
                headers={"Authorization": f"Bearer {access_token}"}
            )
            user_data = user_data.json()
            # 유저 이메일 데이터 가져오기
            user_emails = requests.get("https://api.github.com/user/emails",
                headers={
                    "Authorization" : f"Bearer {access_token}",
                    "Accept" : "application/json"
                }
            )
            user_emails = user_emails.json()
            # print("user_emails===", user_emails)
            email = user_emails[0].get('email')
            social_id= user_data.get("id")
            social = "github"
            # email
            nickname = user_data.get("login")
            profile_image_url = user_data.get("avatar_url")
            # DB에 유저가 있는지 확인
            try:
                user = User.objects.get(email=email, social="github")
                refresh_token = RefreshToken.for_user(user) # jwt발급

                response_data =  {
                    'refresh': str(refresh_token),
                    'access': str(refresh_token.access_token),
                    'message': '가져오기 성공',
                    'userInfo' : {
                        'nickname' : nickname or user.nickname,
                        'social' :user.social,
                        'email' : user.email,
                        'profile_image_url' : profile_image_url or user.profile_image_url
                    }
                }
                return Response(response_data, status=status.HTTP_200_OK)
            # DB에 유저가 없으면
            except User.DoesNotExist:
                # 닉네임이 없으면 닉네임을 설정
                if not nickname:
                    nickname = f"{random.choice(nickname_a)}{random.choice(nickname_b)}{random.randint(1000, 9999)}"
                data = {
                    'email' : email,
                    'username' : email + str(random.randint(100000, 1000000)),
                    'nickname' : nickname,
                    'social' : 'github',
                    'social_id' : str(social_id),
                    'profile_image_url' : profile_image_url
                }
                # DB에 유저 생성
                try:
                    # user 생성
                    user = User.objects.create(
                        email=data.get("email"),
                        username=data.get("username"),
                        nickname=data.get("nickname"),
                        social=data.get("social"),
                        social_id=data.get("social_id"),
                        profile_image_url=data.get("profile_image_url")
                    )
                    user.set_unusable_password()    # 소셜로그인이니까 No password!
                    user.save() # user 저장
                    print('저장성공')
                    # user꺼내기
                    user = User.objects.get(username=data.username, social=data.social, email=email)
                    print('꺼내기성공')
                    refresh_token = RefreshToken.for_user(user) # 자체 jwt 발급
                    print('토큰 발급성공')

                    response_data = {
                        'refresh': str(refresh_token),
                        'access': str(refresh_token.access_token),
                        'message': '저장성공',
                        'userInfo' : {
                            'nickname' : data.get("nickname"),
                            'social' : data.get("social"),
                            'email' : data.get("email"),
                            'profile_image_url' : data.get("profile_image_url")
                        }
                    }
                    print('완전 성공')
                    return Response(response_data, status=status.HTTP_201_CREATED)
                # DB에서 유저 생성 실패
                except:
                    response_data = {
                        'message': '연결되고 유저생성 실패'
                    }
                    return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
        # 로그인 시도 실패
        except:
            response_data = {
                'message': '로그인 시도 실패'
            }
            return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
    response_data = {
        'message': '로그인 실패'
    }
    return Response(response_data, status=status.HTTP_400_BAD_REQUEST)

여기서 3번의 요청을 github로 보낸다.

첫번째

requests.post(f"https://github.com/login/oauth/access_token?code={code}&client_id={client_id}&client_secret={client_secret}&redirect_uri={redirect_uri}",
	headers={"Accept": "application/json"},
)

code, client_id, client_secret, redirect_uri를 통해 토큰을 받아온다.
header에는 "Accept": "application/json"을 추가한다.

두번째

requests.get("https://api.github.com/user",
	headers={"Authorization": f"Bearer {access_token}"}
)

유저의 정보를 앞서 받은 토큰을 이용해서 받아온다.

header에는 첫번째에서 발급받은 토큰을 , "Authorization": f"Bearer {access_token}" 대입하여 추가한다.

세번째*

requests.get("https://api.github.com/user/emails",
	headers={
		"Authorization" : f"Bearer {access_token}",
		"Accept" : "application/json"
	}
)

두번째 에서 받아온 유저정보에는 이메일 정보가 없기에, 이메일 정보를 받아왔다.

Django에서는 이 3번의 절차를 거쳐서 깃허브의 유저정보를 받아와서 내 user DB에 존재유무를 확인하고 존재하면 가져오고 JWT발급, 존재하지 않으면, DB에 user정보를 기입한 뒤 JWT를 발급하는 방식을 이용했다.

JWT 발급
JWT발급은 아래 simple-jwt에서 제공하는 방식인 creating tokens manually의 코드를 이용했다.
여기서 user는

user = User.objects.get(어쩌고=저쩌고)

를 가져온 것이다.

     refresh = RefreshToken.for_user(user)

     return {
         'refresh': str(refresh),
         'access': str(refresh.access_token),
     }

로그아웃?

로그아웃은 dj-rest-auth에 있는 endpoint를 그대로 이용했다.

`${base_url}/dj-rest-auth/logout/`

이를 이용해주어도 토큰 로그아웃이 되는 것을 확인 할 수 있었다.

문제?!

수정을 하나 하긴 해야할 듯 하다.
분명 저장도 잘되는것을 확인했는데, 저장하고, 즉시 들고 오는것이 불가능한 건지 에러가발생했다.
db를지우고 다시시도도 해봐야겠다.
물론 이미 DB로 저장됬으므로 처음이 안되는거지 이후에는 잘 들고온다.

문제해결

user = User.objects.get(username=data.username, social=data.social, email=email)

이 부분이 잘 못 되어있었다.
수정후 꺼낼 때, 아래처럼 해야한다. data.get("") 또는 data[""]의 방식으로 해야되는데 이 부분을 잘 못 작성했다. 아래처럼 수정하니 문제는 해결되었다.

user = User.objects.get(username=data.get("username"), social=data.get("social"), email=email)

profile
반드시 결승점을 통과하는 개발자

0개의 댓글