쇼피파이 임베디드 앱 설치

2024년 7월 11일 목요일

Today I Learned

날짜

2024년 7월 11일 목요일

내용

쇼피파이 앱 설치

인스타그램 댓글 가져오는 서비스가 쇼피파이 검수에서 반려당했다. 설치가 안된다길래 뭔소린가 싶어서 해보니 진짜 안됐다. 갑자기 왜… 겨우 세션토큰 만들어놨는데… 결국 하루종일 이것만 처리했다.

문제가 발생한 이유는, 이번에 개발한 앱이 임베디드라서 그렇다. 쇼피파이 어드민 내에 삽입되는 형태고, 서비스 내에서 주고 받는 요청은 항상 세션토큰이 헤더에 담겨있어야 한다. 이 세션 토큰은 프론트에서 앱 브릿지로 생성해야 한다.

기존 프로덕트 리뷰 같은 서비스들은 앱 설치 과정에서 프론트가 개입하지 않는다.

기존 설치 과정

  1. 쇼피파이에서 설치한다는 요청을 /shops/shopify 로 보낸다. 이떄 샵 URL을 포함한 여러 데이터를 받는데, 리다이렉트 과정에서 항상 query parameter로 포함시킨다.
  2. 그 요청에 담긴 데이터(shop url)로 이미 샵 데이터가 존재하는지 확인한다. 있으면 로그인창으로 보내고, 없으면 shops/shopify/oauth/authorize 로 리다이렉트시킨다.
  3. 여기선 유저에게 권한 제공에 동의하는지를 묻는 permission_url을 생성하고, 이 주소로 리다이렉트 시킨다. 따라서 설치하는 유저는 permission_url을 화면에서 보게된다.

    1
    2
    3
    4
    5
    6
    
     # permission_url 생성 함수
     permission_url = session.create_permission_url(
             SHOPIFY_ACCESS_SCOPE,
             f"https://{REVIEW_SERVICE_URL}/instagram-comments/shopify/callback",
             state,
         )
    
  4. 화면에서 동의하면, 쇼피파이는 permission_url에 query로 담겨있는 “redirect_url”로 리다이렉트 시킨다. 우리는 shops/shopify/callback 이라는 주소로 설정해두었다. 이 주소로 요청이 올때 ‘code’ 라는 이름의 데이터 뿐만 아니라 여러가지 고유값이 같이 담기는데, 이 값을 이용해 shopify offline access token을 생성한다. 우리가 쇼피파이에서 이 샵의 정보를 가져올 떄는 이 토큰을 이용해야 한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
     # shopify offline access token 생성 과정
     params = {
             "code": code,
             "state": state,
             "shop": shop,
             "host": host,
             "timestamp": timestamp,
             "hmac": hmac,
         }
            
       shopify.Session.setup(
           api_key=SHOPIFY_INSTAGRAM_COMMENTS_API_KEY,
           secret=SHOPIFY_INSTAGRAM_COMMENTS_API_SECRET,
       )
        
       session = shopify.Session(shop, API_VERSION)
        
       shopify_access_token = session.request_token(params)
    
  5. 토큰을 생성하면 샵 데이터를 생성하여 데이터베이스에 저장한다. 이후 다시 1번인 shops/shopify 로 리다이렉트시킨다.
  6. 이번엔 샵데이터가 존재하니 2번에서 로그인창으로 이동하게 된다.

이 과정이 기존 서비스들의 설치 과정이다. 별 생각없이 이 로직을 새로 만든 임베디드 앱에도 적용했는데 문제가 됐다. 임베디드 앱의 로직을 살펴보자.

임베디드 앱 설치 과정

  1. 쇼피파이에서 설치한다는 요청을 프론트 주소로 보낸다. 이떄 기존 설치과정과 마
  2. 프론트는 앱브릿지를 이용해 세션토큰을 생성한다. 이 토큰을 서버 instagram-comments/shopify/verify 로 보낸다.
  3. 서버는 받은 세션토큰을 jwt로 해싱하여 올바른 토큰인지 확인한다. 올바르다면 True를 반환한다.
  4. 기존설치과정의 1번과 마찬가지로 프론트에선 여러 데이터를 받았었는데, True를 반환받은 프론트는 쇼피파이로부터 받은 데이터를 서버 instagram-comments/shopify 로 보낸다.
  5. 여기서부터는 기존 설치과정의 1과 동일하다. 단, 샵 데이터가 있을 경우 로그인주소로 보내지 않고 프론트에게 그 샵의 id를 반환해준다. 그전까지는 기존 설치과정과 마찬가지로 리다이렉트의 반복이다.
  6. 프론트는 샵 아이디(응답)를 반환받으면, 유저를 서비스 페이지로 이동시킨다.

문제는 임베디드 앱설치 과정에서 5~6번에서 나타났다. 서버 입장에선 요청을 받아 리다이렉트 과정을 거치면서 필요한 데이터를 만든다. 근데 프론트 입장에선 응답을 받았다. 그게 307 temporary redirect일 뿐이다. 리다이렉트도 응답은 응답인 것. 근데, 응답이 당연히 200에 샵의 아이디를 담겨있을 거라고 생각했던 프론트 입장에선 원하는 데이터를 얻지 못했다. 여기서 로직이 꼬여버린 것이었다.

따라서 서버가 해야할 것은, 리다이렉트 응답을 반환하지 않고 응답 전에 원하는 데이터를 다 생성해야 했다. 기존처럼 여러 함수들을 옮겨다닐 수 없게 됐다. 서버가 얻어야할 가장 중요한 데이터는 shopify offline access token이다. 이걸 받기 위해선 ‘code’ 라는 데이터가 반드시 필요하다. 이건 쇼피파이 화면(permission_url)에서 유저가 설치하겠다고 확인버튼을 누르면 쇼피파이가 보내주는 값이다. 리다이렉트 응답을 보낼 수 없는데, 유저에게 앱에게 권한 제공을 동의하는 화면을 보여줘서 응답을 받아야 code 데이터를 받을 수 있는 답 없는 상황에 빠져버렸다.

그런데, 애초에 이 로직이 시작된 것은 유저가 그 화면에서 동의했다는 의미다. 클라이언트 입장에선 이미 동의를 했다. 설치하겠다고 하자마자 나타나는 화면이 앱에게 권한을 제공하는 창이다. 기존 설치 과정에선 이 code를 shopify/callback 이라는 서버 엔드포인트로 보내고 있었고, 임베디드 앱에선 프론트로 보내고 있었을 뿐이다. 프론트는 세션토큰이 멀쩡하다면, 자신이 받은 모든 데이터를 서버에게 보내주고 있었다. 알고보니 프론트에선 “id_token” 이라는 정체 불명의 값을 보내고있었다. 단지 서버쪽 엔드포인트에서 그 파라미터를 처리하지 않고 있을 뿐이었다.

id_token으로 shopify offline access token을 요청하여 받을 수 있다는 Docs를 뒤늦게야 발견했다. 임베디드 앱을 제대로 알아보지 않은 업보였던걸까, 하루종일 고생한 끝에 문제를 해결할 수 있었다. 남은 궁금증은, 여전히 permission_url을 만들때 query로 담는 redirect_url이 instagram-comments/shopify/callback 이라는 사실이다. 근데 저 엔드포인트는 필요가 없다. 기존에야 code라는 데이터를 받아야 하는 곳이었지만 이 임베디드 앱에선 프론트가 받아주니 필요가 없다. 저 필요없는 엔드포인트로 리다이렉트를 설정해뒀는데 왜 작동되는거지…? 내가 놓치고 있는게 뭔가 있나..

회고

오늘 해결 못했으면 진짜 울뻔했다.