GitHub 앱 및 OAuth ~ 분리된 흐름

이전 게시물에서 우리는 GitHub 앱이 설치된 리포지토리에 대한 GitHub REST API에서 정보를 얻을 수 있는 방법을 보았습니다. 이를 통해 다음 구성 요소와 같은 것을 만들 수 있습니다.



Vercel's Import Git Repository component



이 데이터는 GitHub App과 관련된 데이터이므로 해당 GitHub App에 포함된 OAuth에서 필요한 토큰을 발급합니다.

Q: 내 시스템은 이미 OAuth 앱을 통한 인증을 기반으로 합니다. 변경할 수 없습니다! 또한 타사(예: Stytch )에서 처리합니다. 하지만 여전히 OAuth에서 발급한 access_token는 두 끝점에 대해 유효하지 않습니다!

답: 해결책이 있습니다. 이전보다 약간 비싸지 만 작동합니다.

색인


  • Create OAuth App and GitHub App
  • Authenticate as GitHub App via JWT



  • Related Posts



  • OAuth 앱 및 GitHub 앱 만들기

    Obviously it's necessary to create the OAuth Appcreate the GitHub App .



  • 홈페이지 URL: http://localhost:3000

  • 콜백 URL: 인증 흐름이 완료되면 공급자가 사용자를 다시 보내야 하는 위치입니다. 아무 경로나 선택할 수 있습니다. 저는/oauth/github/login/callback을 사용하고 있습니다.

  • 생성을 완료합니다. 클라이언트 ID가 할당되었으며 클라이언트 시크릿을 생성할 수 있습니다. 그것을하고 편리하게 보관하십시오.

    에서 설명한 방법과 유사하게 클라이언트에서 access_token 를 가져와 보존할 수 있습니다.

    server.get("/oauth/github/login/callback", async (request, reply) => {
      const { code } = request.query;
    
      const exchangeURL = new URL("login/oauth/access_token", "https://github.com");
      exchangeURL.searchParams.set("client_id", process.env.CLIENT_ID);
      exchangeURL.searchParams.set("client_secret", process.env.CLIENT_SECRET);
      exchangeURL.searchParams.set("code", code);
    
      const response = await axios.post(exchangeURL.toString(), null, {
        headers: {
          Accept: "application/json",
        },
      });
    
      const { access_token } = response.data;
    
      const redirectionURL = new URL("new", "http://localhost:3000");
      redirectionURL.searchParams.set("access_token", access_token);
    
      reply.status(302).header("Location", redirectionURL).send();
    });
    


    그래도 앱 설치와 해당 리포지토리를 반환하는 두 끝점을 쿼리하려고 하면 403이 표시됩니다.

    끝점:
  • /user/installations

  • /user/installations/{installation_id}/repositories



  • GitHub 앱과 관련된 다른 종류의 access_token 가 필요합니다.

    따라서 GitHub 앱을 만듭니다. 이번에는 실제로 콜백 URL을 할당할 필요가 없습니다(하지만 설정 URL을 설정하는 것이 좋습니다). 이 게시물의 목적을 위해 앱 ID(GitHub 앱 구성 페이지의 정보 섹션)를 추적하고 fs에 개인 키(페이지 하단)를 생성 및 저장하는 데에만 관심이 있습니다.




    JWT를 통해 GitHub 앱으로 인증

    As the official docs 설명:

    Authenticating as a GitHub App lets you do a couple of things [...] You can request access tokens for an installation of the app. To authenticate as a GitHub App, generate a private key in PEM format and download it to your local machine. You'll use this key to sign a JSON Web Token (JWT) and encode it using the RS256 algorithm. GitHub checks that the request is authenticated by verifying the token with the app's stored public key.



    따라서 서버에는 JWT를 생성하는 경로/repos가 있을 수 있습니다.

    const secret = fs.readFileSync(
      path.resolve(__dirname, ".private-key.pem"),
      "utf-8"
    );
    
    server.get("/repos", async (request, reply) => {
      const now = Math.floor(Date.now() / 1000) - 60; // don't just use Date.now()
    
      const payload = jwt.sign(
        {
          iat: now - 60,
          exp: now + 10 * 60,
          iss: process.env.APP_ID,
        },
        secret,
        {
          algorithm: "RS256",
        }
      );
    
      // ...
    });
    


    그리고 JWT가 생성되면 Bearer 접두사가 붙은 인증 헤더에서 JWT를 사용합니다(token를 요청하는 대부분의 GitHub REST API 엔드포인트와 다름).


    GitHub 앱의 installation_id 가져오기



    The endpoint is /app/installations :

    // still in /repos
    
    const installations = await axios.get(
      `https://api/github.com/app/installations`,
      {
        headers: {
          Authorization: `Bearer ${payload}`,
        },
      }
    );
    


    다음과 같은 요소로 구성된 목록이 반환됩니다.

    {
      id: 25061467,
      account: {
        login: '<some-username>',
        ...
      },
      repository_selection: 'selected',
      access_tokens_url: 'https://api.github.com/app/installations/25061467/access_tokens',
      repositories_url: 'https://api.github.com/installation/repositories',
    },
    

    .account.login가 사용자의 개인 계정 또는 사용자의 조직 중 하나인 모든 항목을 필터링해야 합니다. /user/org을 통해 이 정보를 검색할 수 있습니다. OAuth 앱을 통해 검색된 access_token를 전달해야 합니다.

    기본적으로 다음과 같습니다.

    const relevantInstallations = installations.data.filter((installation) => {
      return currentUserOrganizations.includes(installation.account.login);
    });
    



    GitHub 앱의 access_token 가져오기

    For each one of the installations relevant the our user (and all user's organizations), we request a GitHub App authenticated access_token via the endpoint /app/installations/{installation_id}/access_tokens :

    const promises = relevantInstallations.map((installation) => {
        return axios.post(
          `https://api.github.com/app/installations/${installation_id}/access_tokens`,
          null,
          {
            headers: {
              Authorization: `Bearer ${payload}`,
            },
          }
        );
      });
    
    // Parallel
    const accessTokens = await axios.all(promises)
    


    Note: instead of manually assembling the url, you could use the premade installation.access_tokens_url.




    적합한 리포지토리 검색

    Iterate each access_token and use it in the Authorization header token ${access_token} at the endpoint /installation/repositories ( installation.repositories_url ):

    // Note: for each access_token!
    const response = await axios.get(
      "https://api.github.com/installation/repositories",
      {
        headers: {
          Authorization: `token ${token}`, // not Bearer
        },
      }
    );
    
    const repositories = response.data.repositories;
    

    Merge or organize all the received repositories and you're back to the first post situation. We got there by a transverse, slightly more strenuous route - nonetheless, we're reached the goal.


    관련 게시물

  • cover image

  • 콘택트 렌즈




  • GitHub
  • 좋은 웹페이지 즐겨찾기