프로젝트에 apple 로그인을 적용하였다.
처음 접해본 방식이라 블로깅을 하려 한다.
Apple 로그인 과정 중 앱(Ios)을 통해 code와 id_token을 받아, 유효성 검사를 진행한다.
유효성 검사를 통해 Apple Server의 유저 고유 값(id)를 받을 수 있었다.
kid
를 추출kid
가 일치하는 값 찾기code
, client_id
, client_secret
등 Apple에서 요구하는 여러 값을 해당 url로 post
하면 나오는 id_token
을 유지한다.public key
생성3
에서 만든 id_token
을 4
에서 만든 public key
로 복호화를 하면 sub(AppleID)
가 추출된다.아래는 Apple 로그인을 라이브러리화 하여 만든 코드이다.
import datetime
import os
import jwt
import requests
from django.core.exceptions import BadRequest
from rest_framework.exceptions import NotFound
from jwt.algorithms import RSAAlgorithm
from dotenv import load_dotenv
from .base import BaseSNSAuth
load_dotenv()
class AppleOAuth(BaseSNSAuth):
key_id = os.environ.get('KEY_ID')
client_id = os.environ.get('CLIENT_ID')
JWKS_url = "https://appleid.apple.com/auth/keys"
token_audience_url = 'https://appleid.apple.com'
code_check_url = "https://appleid.apple.com/auth/token"
def get_jwk(self, kid=None):
"""
apple public key(jwk) GET
"""
jwks = super().get_json_response(path=self.JWKS_url)['keys']
if not isinstance(jwks, list):
raise NotFound
if kid:
return next((jwks for jwks in jwks if jwks['kid'] == kid), None)
else:
return jwks
def get_audience(self):
return self.client_id
def get_private_key(self):
with open('**********.p8') as keyfile:
private_key = keyfile.read()
return private_key
def encode_apple_jwt(self):
now = datetime.datetime.now()
key_id = self.key_id
team_id = os.environ.get('TEAM_ID')
bundle_id = self.client_id
headers = {'kid': key_id}
payload = {
'iss': team_id,
'iat': now,
'exp': now + datetime.timedelta(days=2),
'aud': self.token_audience_url,
'sub': bundle_id
}
apple_jwt = jwt.encode(headers=headers, payload=payload, algorithm='ES256', key=self.get_private_key())
return apple_jwt
def get_apple_token(self, code, redirect_url='https://******.co.kr'):
auth_check = requests.post(url=self.code_check_url,
headers={'content-type': 'application/x-www-form-urlencoded'},
data={
'client_id': self.client_id,
'client_secret': self.encode_apple_jwt(),
'code': code,
'grant_type': 'authorization_code',
'redirect_uri': redirect_url,
})
response = auth_check.json()
return response
def decode_id_token(self, id_token):
try:
kid = jwt.get_unverified_header(id_token)['kid']
jwk = self.get_jwk(kid)
public_key = RSAAlgorithm.from_jwk(jwk)
decoded_id_token = jwt.decode(
jwt=id_token, key=public_key, algorithms=['RS256'], audience=self.get_audience())
except Exception:
raise ValueError('Wrong number of segments in token')
return decoded_id_token
def validate_decode_token(self, code):
decoded_token = self.get_apple_token(code)
id_token = decoded_token['id_token']
refresh_token = decoded_token['refresh_token']
return self.decode_id_token(id_token), refresh_token
def get_user_sns_id(data):
apple = AppleOAuth()
print(data)
client_decode_data = apple.decode_id_token(data['token_key'])
if client_decode_data:
valid_id_tkn, refresh_tkn = apple.validate_decode_token(data['code'])
sns_id = client_decode_data.get('sub')
if sns_id == valid_id_tkn.get('sub'):
return sns_id, 'apple', refresh_tkn
else:
raise ValueError('Wrong number of segments in token')