-
django github loginProject using python/Cloning Airbnb 2021. 3. 3. 14:11
url 설정
config/urls.py
urlpatterns = [ path("users", include("users.urls", namespace="users")), ]
users/urls.py
from django.urls import path from . import views app_name = "users" urlpatterns = [ path("login/github/", views.github_login, name="github-login"), path( "login/github/callback/", views.github_login_callback, name="github-callback", ) ]
{% url %}
url template tag를 이용해서 signup page로 갈 수 있게 한다. {% url 'users:github-login' %}은 /users/login/github와 같다.
github 설정
Github의 개발자 page에서 New OAuth App을 누른다.
다음과 같이 Application name, Homepage URL과 Authorization callback URL을 기입한다. Authorization callback URL은 Application이 github에 권한 요청을 하고 승인되었을 때 callback 되는 url 주소다.
생성이 완료됬었으면 Client ID와 Client secrets를 확인할 수 있다. Client secrets는 Generate button으로 생성해주고 이 둘을 .env 파일에 기입하자.
Coding
1. Request a user's GitHub identity
OAuth page 하단에 OAuth documentation이 있다. 이 문서를 이용해서 github login code를 짜면된다.
빨간색 박스는 필요한 parameter니 꼭 기입해 주자. scope는 required는 아니지만 user의 정보를 얼마나 요청할 것인지의 권한을 부여한다. (자세한 내용은 여기)
from django.shortcuts import redirect, reverse from django.contrib import messages def github_login(request): try: if request.user.is_authenticated: raise SocialLoginException("User already logged in") client_id = os.environ.get("GH_ID") redirect_uri = "http://127.0.0.1:8000/users/login/github/callback/" scope = "read:user" return redirect( f"https://github.com/login/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&scope={scope}" ) except SocialLoginException as error: messages.error(request, error) return redirect("core:home")
2. Users are redirected back to your site by GitHub
Github에 요청을 하면 내가 지정한 callback 주소로 redirect가 된다. 그렇게 되면 github_login_callback 함수가 실행이 되는데, 이 함수는 github가 보낸 code 값을 받게 된다. 이 code 값을 토대로 다시 python의 requests module을 사용해서 parameter와 같이 https://github.com/login/oauth/access_token 주소로 요청을 한다. 요청을 할 때 headers는 json을 받을 수 있게 해야 한다.
그렇게 되면 응답은 json 형식의 access_token을 준다.
def github_login_callback(request): try: if request.user.is_authenticated: raise SocialLoginException("User already logged in") code = request.GET.get("code", None) if code is None: raise GithubException("Can't get code") client_id = os.environ.get("GH_ID") client_secret = os.environ.get("GH_SECRET") token_request = requests.post( f"https://github.com/login/oauth/access_token?client_id={client_id}&client_secret={client_secret}&code={code}", headers={"Accept": "application/json"}, ) token_json = token_request.json() error = token_json.get("error", None) if error is not None: raise GithubException("Can't get access token")
3. Use the access token to access the API
위에서 얻은 token_json에서 access_token을 얻는다. 다시 빨간색 박스 url로 request를 하고 profile 정보를 얻는다. profile_request에 담겨져 있는 정보로 username이나 여러 가지 github 정보를 얻을 수 있다.
access_token = token_json.get("access_token") profile_request = requests.get( "https://api.github.com/user", headers={ "Authorization": f"token {access_token}", "Accept": "application/json", }, ) profile_json = profile_request.json() username = profile_json.get("login", None)
2번, 3번과 추가적인 정보를 합치면 밑에 코드가 된다.
두 번째 try 문은 github로 로그인을 하려는 user의 이메일 계정이 이미 회원가입이 되었는지를 확인한다. 만약 이미 계정이 있다면 해당 계정이 github 계정인지 다른 social 계정인지 아니면 airbnb app에 자체 등록한 계정인지 확인한다. github 계정이면 바로 로그인을 하고 아니면 회원가입을 한 계정으로 로그인을 하라고 알린다.
만약 계정이 없다면 계정을 생성한다.
user.avatar.save()는 avatar라는 ImageField(or FileField)의 method로 바로 avatar image를 저장한다. 인자로는 파일 이름과 content 파일을 받는다. content 파일이란 0과 1로 이뤄진 byte 파일이다. django의 ContentFile function을 사용해서 github의 image file을 content file로 변환할 수 있다.
user.set_unusable_password()는 password 설정을 하지 않는다는 것이다. 왜냐하면 github를 이용한 로그인이기 때문이다(github의 id와 password를 이용하기 때문이다.).
def github_login_callback(request): try: if request.user.is_authenticated: raise SocialLoginException("User already logged in") code = request.GET.get("code", None) if code is None: raise GithubException("Can't get code") client_id = os.environ.get("GH_ID") client_secret = os.environ.get("GH_SECRET") token_request = requests.post( f"https://github.com/login/oauth/access_token?client_id={client_id}&client_secret={client_secret}&code={code}", headers={"Accept": "application/json"}, ) token_json = token_request.json() error = token_json.get("error", None) if error is not None: raise GithubException("Can't get access token") access_token = token_json.get("access_token") profile_request = requests.get( "https://api.github.com/user", headers={ "Authorization": f"token {access_token}", "Accept": "application/json", }, ) profile_json = profile_request.json() username = profile_json.get("login", None) if username is None: raise GithubException("Can't get username from profile_request") avatar_url = profile_json.get("avatar_url", None) if avatar_url is None: raise GithubException("Can't get avatar_url from profile_request") name = profile_json.get("name", None) if name is None: raise GithubException("Can't get name from profile_request") email = profile_json.get("email", None) if email is None: raise GithubException("Can't get email from profile_request") bio = profile_json.get("bio", None) if bio is None: raise GithubException("Can't get bio from profile_request") try: user = models.User.objects.get(email=email) if user.login_method != models.User.LOGIN_GITHUB: raise GithubException(f"Please login with {user.login_method}") except models.User.DoesNotExist: user = models.User.objects.create( username=email, first_name=name, email=email, bio=bio, login_method=models.User.LOGIN_GITHUB, ) photo_request = requests.get(avatar_url) user.avatar.save(f"{name}-avatar", ContentFile(photo_request.content)) user.set_unusable_password() user.save() messages.success(request, f"{user.email} logged in with Github") login(request, user) return redirect(reverse("core:home")) except GithubException as error: messages.error(request, error) return redirect(reverse("core:home")) except SocialLoginException as error: messages.error(request, error) return redirect(reverse("core:home"))
Model
users/models.py
user model에 새롭게 추가된 field다. social_login을 하게 되면서 local, github와 kakao로 method를 나눴다.
from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): """ Custom User model """ LOGIN_EMAIL = "email" LOGIN_GITHUB = "github" LOGIN_KAKAO = "kakao" LOGIN_CHOICES = ( (LOGIN_EMAIL, "Email"), (LOGIN_GITHUB, "Github"), (LOGIN_KAKAO, "Kakao"), ) login_method = models.CharField( max_length=6, choices=LOGIN_CHOICES, default=LOGIN_EMAIL )
Exception
users/exception.py
여러 가지 exception들을 모아 놓은 파일이다.
class SocialLoginException(Exception): pass class GithubException(Exception): pass
templates
mixins/social_login.html
social_login.html이다. mixins에 만들어 놓고 signup과 login에서 include를 한다.
<div class="social-login mt-4"> <button class="button bg-gray-700 text-white"> <a href="{% url 'users:github-login' %}"> <i class="fab fa-github mr-2"></i>Login with Github </a> </button> <button class="button bg-yellow-300 text-yellow-900 mt-2"> <a href="#"> <i class="fas fa-comment mr-2"></i>Login with Kakao </a> </button> </div>
참고 자료
- 노마드 코더의 Airbnb 클론 강의
- github login(OAuth)
- ContentFile
- FieldFile.save
소스 코드
github.com/zpskek/airbnb-clone-v3/commit/1ffa848d871407b24a7ea8bc2002353f0c18fc95
'Project using python > Cloning Airbnb' 카테고리의 다른 글
django kakao login (0) 2021.03.03 django managers.py (0) 2021.03.03 django messages framework (0) 2021.03.02 Using the Django authentication system (0) 2021.03.02 django logout (0) 2021.03.02