GitHub App e OAuth ~ Guida Pratica

GitHub App e OAuth ~ Guida Pratica

Scritto da Francesco Di Donato 26 aprile 2022 6 minuti di lettura

Cosa possiamo costruire

Un’interfaccia che dà a un utente autenticato su GitHub (o a qualsiasi organizzazione dell’utente) la possibilità di vedere quali dei suoi repository hanno un dato GitHub App installato.

Componente Import Git Repository di Vercel

Componente Import Git Repository di Vercel 🔗

Premessa

Ho trovato due modi diversi per raggiungere l’obiettivo.

  1. Fornitore autonomo (GitHub App)
  2. Flusso disgiunto (OAuth App, GitHub App).

Se conosci altre modalità , ti prego di contattarmi (twitter) 🔗.

In questo post implementiamo la prima modalità .

Crea un GitHub App

Per trovare l’elenco dei repository che hanno il GitHub App 🔗 installato, dobbiamo prima crearne uno 🔗.

Ai fini di questo post, dobbiamo solo sapere che per autenticare un utente tramite GitHub, devi registrare la tua OAuth app; tuttavia, ogni GitHub App ha un OAuth al suo interno 🔗.

Ecco perché chiamo (arbitrariamente) questo metodo autonomo - utilizziamo solo un singolo GitHub App.

Configurazione della registrazione del GitHub App

  • URL della homepage: http://localhost:3000 🔗
  • URL di callback: Dove il fornitore deve rimandare l’utente una volta completato il flusso di autenticazione. Puoi scegliere qualsiasi percorso, sto usando /oauth/github/login/callback
  • Setup URL: Dove il fornitore deve rimandare l’utente una volta che il GitHub è stato installato/disinstallato/cambiato il permesso. Puoi scegliere qualsiasi percorso, sto usando /new.

Autorizzazione di sola lettura dei contenuti

Configura l’app in modo che abbia il diritto di leggere i contenuti. Altrimenti, non potrà essere installata su repository specifici.

Troverai anche una sezione Webhook. Ai fini di questo post non ci interessa, quindi per continuare puoi semplicemente contrassegnarla come inattiva.

Infine crea il GitHub App. È stato assegnato un App ID, un Client ID ed è possibile generare un Client Secret - Tutte le informazioni di cui abbiamo bisogno, ma tra poco.


GitHub OAuth

Autenticare un utente con GitHub 🔗 è semplice.

Se qualcosa non è chiaro ti prego di fare riferimento al repo secondario 🔗.

Prima di tutto, il frontend presenta un CTA che punta a un percorso sul server.

<a id="oauth-github">Autenticati tramite GitHub</a>
<script>
    const CLIENT_ID = "Iv1.395930440f268143";

    const url = new URL("/login/oauth/authorize", "https://github.com");
    url.searchParams.set("client_id", CLIENT_ID);

    document.getElementById("oauth-github")
      .setAttribute("href", url);
</script>

Nota: sicurezza Per rimanere conciso, riporto solo client_id poiché è richiesto; tuttavia, in un contesto di produzione assicurati di rivedere la documentazione ufficiale, specialmente riguardo a scope e state.

Rendilo carino, aggiungi l’icona di GitHub - l’utente preme e viene portato alla pagina seguente:

Schermata di autenticazione di GitHub

Ora, quando viene premuto il pulsante verde Autorizza , l’utente viene reindirizzato all’URL di callback impostato durante la creazione del GitHub App.

Assicuriamoci che il server sia in grado di ascoltare /oauth/github/login/callback. Fai attenzione a questo passaggio fondamentale: GitHub reindirizza e aggiunge un parametro di query, un code necessario per autenticare l’utente.

server.get("/oauth/github/login/callback", async (request, reply) => {
  const { code } = request.query;

  // ...
});

code deve essere scambiato con un access_token, che verrà memorizzato nel client e associato alle richieste all’API REST di GitHub.

Ora, prima di procedere, torna alla pagina di configurazione del tuo GitHub App e Genera un nuovo Client Secret. Quindi, usa dotenv per questo.

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;

Per brevità , l’esempio non riporta la gestione degli errori. È lasciato al buon senso del lettore.

Quindi, il token viene consegnato al client che infine viene reindirizzato a qualche percorso /new. Ma - ecco il nostro eroe della storia - riceve anche l’access_token come parametro di query.

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();

Estrailo nel client:

<script>
  const access_token = new URL(window.location).searchParams.get(
    "access_token"
  );

  localStorage.setItem("access_token", access_token);
</script>

Puoi memorizzarlo in qualsiasi Web Storage, in memoria, dipende davvero da come deve essere utilizzata la tua interfaccia. Anche se un cookie HttpOnly e Secure aiuterebbe a mitigare gli attacchi XSS.

Il client dell’utente autenticato può infine associare l’access_token quando fa legittimamente una richiesta all’API REST di GitHub 🔗. Ci siamo quasi.

Miglioramento: popup invece di reindirizzamento Di solito altri OAuth in natura non reindirizzano duramente la pagina corrente. Supponiamo di lavorare su una SPA, pensa a quanto sia triste scoprire che questo flusso svuota il tuo store in memoria. Facile da risolvere, il reindirizzamento avverrà comunque ma in un popup creato (post) che, prima di dissolversi, passerà l’access_token di nuovo alla scheda principale.


Interrogare l’API REST di GitHub

I dati di cui abbiamo bisogno vengono restituiti da due endpoint.

  1. /user/installations 🔗
  2. /user/installations/:installation_id/repositories 🔗

Il secondo viene chiamato con le informazioni ricevute dal primo.

La documentazione afferma:

Devi utilizzare un token di accesso OAuth da utente a server, creato per un utente che ha autorizzato il tuo GitHub App, per accedere a questo endpoint.

L’access_token restituito da una OAuth App “semplice” non è valido per questi endpoint. Tuttavia, c’è un modo per farlo funzionare usando un OAuth e tre altri endpoint diversi. Questo è mostrato nel secondo post.

const githubAPI = axios.create({
  baseURL: "https://api.github.com",
  headers: {
    Accept: "application/vnd.github.v3+json",
  },
});

const authorizationConf = {
  headers: {
    authorization: `token ${access_token}`,
  },
};

(async () => {
  const installationResponse = await githubAPI.get(
    "user/installations",
    authorizationConf
  );
})();

Impostare application/vnd.github.v3+json è raccomandato dalla documentazione ufficiale. È il tipo di media personalizzato dell’API di GitHub.

Riceviamo indietro un elenco contenente… aspetta, è vuoto. Questo perché il GitHub App non è stato ancora installato da nessuna parte.

Facciamolo facile da installare:

<a
 href="https://github.com/apps/<github-app-name>/installations/new"
>
  Cambia permessi
</a>

Cliccandoci sopra mostra la seguente schermata:

Schermata di cambiamento permessi del GitHub App

Scegli il tuo account personale o uno delle tue organizzazioni. O entrambi. Installalo da qualche parte.

Schermata di accesso ai repository

Ora /user/installations restituisce un elenco di installazioni. Una per ogni account (account personale || organizzazione) che ha almeno un repo con il github app installato in esso.

Ogni elemento ha la proprietà id. Questo è l’installation_id necessario per il prossimo endpoint.

const promises = installationsResponse.data.installations.map(
  (installation) => {
    return githubAPI.get(
      `user/installations/${installation.id}/repositories`,
      authorizationConf
    );
  }
);

// parallel
const responses = await axios.all(promises);

const repositories = responses.map((response) => {
  return response.data.repositories;
});

Ecco a te tutti i repositories dell’utente autenticato o di una delle sue organizzazioni che hanno il GitHub App installato.


Per riassumere, ora puoi:

  • Autenticare gli utenti tramite l’autenticazione OAuth di GitHub
  • Reindirizzare gli utenti per installare il tuo GitHub App
  • Mostrare loro un menu a discesa contenente tutte le organizzazioni oltre all’account personale
  • Sapere quali repository mostrare

Lascio a te tutto il divertimento di implementare l’interfaccia utente con il tuo framework preferito. Se hai bisogno di guardare il tour completo, nel repo secondario 🔗 ho usato tecnologie che tutti conoscono.