1. 준비
REST API 키
를 사용하겠습니다.2. Get Authorization Code
3. Get Access Token
Client는 정해진 시간(24시간 미만) 이내에 cliend_id, redirect url, authorization code와 client_secret을 포함한 요청을 Resource Server에 보내면 access_token을 획득할 수 있습니다.
프론트엔드에서 cliend_id와 redirect_url을 Resource Server
(카카오 API)에 요청하고 백엔드에서 access_token을 획득합니다.
class KakaoCallback(View):
def get(self, request):
try:
# 카카오에서 토큰 발급
kakao_token = request.headers.get("Authorization")
if kakao_token == None:
return JsonResponse({"message":"INVALID ACCESS TOKEN"}, status=401)
profile_request = requests.get(
"https://kapi.kakao.com/v2/user/me",
headers = {"Authorization": f"Bearer {kakao_token}"},
timeout = 2
)
# Client에서 access_token 발급
access_token = jwt.encode(
{'user_id' : user.id,
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=24)
}, SECRET_KEY, ALGORITHM)
4. Access 사용자 정보 및 Use API
Client는 access_token을 이용해 사용자가 권한을 허락한 정보에 접근합니다.
프론트엔드는 위 과정 중 1~3번에 해당하는 작업을 진행해 access_token을 백엔드에 전달합니다.
백엔드는 프론트엔드에서 전달받은 access_token을 이용해 Resource Server에 등록된 사용자의 정보(unique_index)를 가져옵니다.
위에서 언급한 unique index와 인증을 진행한 Resource Server의 종류(예 : kakao 등)의 종류를 user table에 저장해 사용자를 생성하고 식별합니다.
백엔드에서 Kakao Login API를 활용하는 코드는 다음과 같습니다.
import requests, jwt, datetime
from django.views import View
from django.http import JsonResponse
from .models import User
from my_settings import SECRET_KEY, ALGORITHM
class KakaoCallback(View):
def get(self, request):
try:
# 카카오가 발행한 토큰을 받습니다.
kakao_token = request.headers.get("Authorization")
if kakao_token == None:
return JsonResponse({"message":"INVALID ACCESS TOKEN"}, status=401)
profile_request = requests.get(
"https://kapi.kakao.com/v2/user/me",
headers = {"Authorization": f"Bearer {kakao_token}"},
timeout = 2
)
# Resource Server에서 User의 정보를 가져갑니다.
profile_json = profile_request.json()
email = profile_json.get("kakao_account").get("email", None)
nickname = profile_json.get("properties").get("nickname")
profile_image = profile_json.get("kakao_account").get("profile").get("profile_image_url", None)
gender = profile_json.get("kakao_account").get("gender", None)
kakao_id = profile_json.get("id")
if email is None:
return JsonResponse({'message': 'EMAIL_REQUIRED'}, status = 405)
# 가입이 되어있지 않다면 인증된 유저의 정보를 바탕으로 가입합니다.
user, is_created = User.objects.get_or_create(
kakao_id = kakao_id,
defaults={
'email' : email,
'nickname' : nickname,
'profile_image' : profile_image,
'gender' : gender
}
)
user.save()
# Client(나의 앱)에서 토큰을 발행 해 유저인지 인가합니다.
access_token = jwt.encode(
{'user_id' : user.id,
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=24)
}, SECRET_KEY, ALGORITHM)
results = {
'email' : email,
'nickname' : nickname,
'profile_image' : profile_image,
'gender' : gender,
'kakao_id' : kakao_id
}
return JsonResponse({
'message' : 'SUCCESS',
'token' : access_token,
'results' : results
}, status = 201)
# 유저가 정보를 허락하지 않았다면 get을 할 수 없어서 다음의 에러를 호출합니다.
except AttributeError:
return JsonResponse({'message' : 'CANNOT_GET_ATTRIBUTE'}, status = 400)
#토큰이 만료될 시 다음의 에러를 호출합니다.
except jwt.ExpiredSignatureError:
return JsonResponse({'message' : 'EXPIRED_TOKEN'}, status = 400)
만약 백엔드에서 모든 과정(OAuth 서버로 API 사용)을 처리한다면 CORS 에러가 발생합니다.
다음은 백엔드에서 localhost/users/login/kakao
를 요청한 후 localhost/users/login/kakao/callback
을 받도록 프론트엔드가 해야할 과정을 백엔드에서 모두 처리한 로직입니다.
users/urls
# users/urls
from django.urls import path
from users.views import KakaoLogin, KakaoCallback
urlpatterns = [
path("/login/kakao", KakaoLogin.as_view() ),
path("/login/kakao/callback", KakaoCallback.as_view()),
]
users/views
# users.views
필요한 라이브러리 import
.
.
.
class KakaoLogin(View):
def get(self, request):
client_id = CLIENT_ID
redirect_uri = "http://127.0.0.1:8000/users/login/kakao/callback"
kakao_auth_api = "https://kauth.kakao.com/oauth/authorize?response_type=code"
return redirect(
f"{kakao_auth_api}&client_id={client_id}&redirect_uri={redirect_uri}"
)
class KakaoCallback(View):
def get(self, request):
try:
code = request.GET.get("code")
client_id = CLIENT_ID
redirect_uri = "http://127.0.0.1:8000/users/login/kakao/callback"
kakao_token_api = "https://kauth.kakao.com/oauth/token"
.
.
.
Why Error...?
CORS를 사용하면 서로 다른 두 철처간의 리소스의 공유를 안전하게 허용할 수 있지만, 프론트에서 form
을 통해 요청하는데 form
은 CORS의 동일한 출처 정책을 시행하지 않기에 해당 에러를 내뱉습니다.
동일한 출처 정책을 시행하지 않는 태그들은 <script>
, <forms>
, <img>
, <a>
, <frame>
, <iframe>
입니다.
아래는 프론트엔드의 코드이며 <forms>
를 사용한 것을 알 수 있습니다.
// Example of request to kakaop oauth using a Form
const requestWithForm = () => {
var form = document.createElement('form');
form.setAttribute('method', 'GET');
form.setAttribute('action', 'https://kapi.kakao.com/v2/user/me');
var idInput = document.createElement('input');
idInput.setAttribute('type', 'hidden');
idInput.setAttribute('name', 'client_id');
idInput.setAttribute('value', 'myapplicationskakaoclientid');
form.appendChild(idInput);
//.. all other parameters (input elements) I need in my request.
document.body.appendChild(form);
form.submit();
}