Ssul's Blog

소셜로그인 구현 - apple 로그인 본문

dev/기능구현

소셜로그인 구현 - apple 로그인

Ssul 2023. 6. 28. 10:29

#1. 애플 개발자계정 가입/로그인(https://developer.apple.com/)

 

#2. Identifiers > Apps IDs 만들기

+ 클릭

 

app ids 선택후 continue

 

app 선택하고 continue

 

[App id 등록하는 페이지 등장]

- App ID prefix 밑에 있는 코드 기록(Team ID로 사용)

- description 대충적고,

- bundle ID는 자신의 도메인 + 앱(서비스)이름으로 구성

예) 스쿨, school.co.kr > kr.co.school.school

- sign in with apple 체크

 

 

#2. Identifiers > Services IDs만들기

-오른쪽에 services IDs선택

-identifiers옆에 +선택

 

- description대충넣고

- identifier가 중요: 나중에 코딩시 이게 client_id에 들어감

- services id의 identifier가 client_id로 들어감

 

configure 클릭

 

-위에서 만든 App id선택하고

-website URL등록(도메인과 콜벡주소 입력)

*나는 백앤드 서버에서 로그인을 진행하므로

api.school.co.kr(예시임)과 https://api.school.co.kr/auth/providers/apple/callback(본인이 개발한 콜백주소)

 

#3. key생성

- 생성한 app id로 키생성

- key id와 key값 잘기억해서 개발시 사용

 

 

[환경변수 설정 참고]

APPLE_TEAM_ID=1번의 Team ID
APPLE_CLIENT_ID=2번 services id의 identifier
APPLE_KEY_ID=3번의 Key ID
APPLE_PRIVATE_KEY=3번의 키값

 

[코드]

- 클라이언트에서 애플로그인 버튼을 누른다

- 서버에서 request_apple_loging 호출됨(여기서 애플auth에 호출해서 로그인창 으로 이동후, 로그인 성공하면 callback주소로 호출)

def request_apple_login(request):
    state = get_random_string(32, allowed_chars=string.ascii_letters + string.digits)
    query = urlencode(
        {
            "client_id": APPLE_CLIENT_ID,
            "redirect_uri": REDIRECT_URI,
            "response_type": "code",
            'response_mode': 'form_post',
            "scope": "name email",
            "access_type": "offline",
            "state": state,
        }
    )

    # 1. https://appleid.apple.com/auth/authorize 으로 로그인 요청 > 로그인 성공하면, callback으로
    response = django_redirect(f"{APPLE_OAUTH_URL}?{query}")
    cb = re.search("cb=(.+)", request.get_full_path())
    cb = APP_URL + (cb and cb[1])
    response.set_cookie("cb", cb, httponly=True)
    response.set_cookie("state", state, httponly=True)
    return response

 

- 애플아이디와 비번이 성공적으로 통과하면, 토큰 받아오기 시작

- code를 POST.get으로 가져와야 한다(form_post로 넘기기 때문에) 이걸로 이틀 고생함 ㅜㅜ

# 2. 로그인에 성공했으니, /apple/callback로 여기왔음
def authorize_with_apple(request):
    # cb=/auth/signup-sso
    cb = request.COOKIES.get("cb")
    if not cb or cb == "None":
        cb = "/"

    # 애플 client_secret만들기
    client_id = APPLE_CLIENT_ID
    team_id = APPLE_TEAM_ID
    key_id = APPLE_KEY_ID
    key = APPLE_PRIVATE_KEY.replace('\\n', '\n')

    headers = {
        'kid': key_id,
        'alg': 'ES256',
    }

    claims = {
        'iss': team_id,
        'iat': time.time(),
        'exp': time.time() + 86400 * 180,
        'aud': 'https://appleid.apple.com',
        'sub': client_id,
    }

    client_secret = jwt.encode(claims, key, algorithm='ES256', headers=headers)


    # 3. 토큰 호출해서 가져오기 APPLE_TOKEN_URL=https://appleid.apple.com/auth/token
    token_res = requests.post(
        APPLE_TOKEN_URL,
        {
        	#중요 google은 query로 넘기는데, 애플은 form_post로 넘겼기 때문에 POST사용
            "code": request.POST.get("code"),
            "client_id": APPLE_CLIENT_ID,
            "client_secret": client_secret,
            "redirect_uri": REDIRECT_URI,
            "grant_type": "authorization_code",
        },
    )
    if not token_res.ok:
        error_message = token_res.text  # 응답 데이터를 문자열로 가져옴
        return redirect(f"{APP_URL}/error?type=auth&status=423&next={cb}&error_message={error_message}")

    token_body = token_res.json()

    # 4. 사용자 정보 가져오기
    id_token = token_body.get("id_token")
    if not id_token:
        return redirect(f"{APP_URL}/error?type=auth&status=424&next={cb}")

    decoded_token = jwt.decode(id_token, options={"verify_signature": False})
    service_id = decoded_token.get('sub')

    # if not email or not service_id:
    if not service_id:
        return build_unauthorized_response("No sub in Apple ID token")

    # Everything is ok, create or update the user in your database, set the cookies and return the response
    response = build_authorized_response(cb)
    response.set_cookie("provider", "apple", httponly=True, max_age=60 * 60 * 1)
    response.set_cookie("service_id", service_id, httponly=True, max_age=60 * 60 * 1)

    return response