LoginSignup
0

More than 5 years have passed since last update.

Electron で Github OAuth トークンを取得する

Last updated at Posted at 2017-09-22

wamw Friday I/O 6回目
株式会社ワムウでは、毎週金曜日は 興味がある事柄に取り組み、その成果を何らかの形でアウトプットする日としています。

今日は時間がなかったので、メモです。

はじめに

下記コードは既存コードからメモ用に抜き出し、書き換えたものです。
これがそのまま完動するわけではないので、あくまで処理の流れをメモったものとなります。

前提

レンダラプロセスから何らかのアクションにてメインプロセスへ connect-to-github を送るとする。
その際メインプロセス側で下記のような形でアクセストークンを取得したい。

// in main process

ipcMain.on('connect-to-github', async (event: Electron.Event) => {
  const credentials: GithubOAuthCredentials = {
    scopes: ['user', 'public_repo', 'repo'],
    client_id: process.env.GITHUB_CLIENT_ID || '',
    client_secret: process.env.GITHUB_CLIENT_SECRET || ''
  }

  const token: string = await getToken(credentials)
  event.sender.send('github-connected', token)
})

GithubOAuthCredentials はどこかで適当に。

interface GithubOAuthCredentials {
  scopes: string[]
  client_id: string
  client_secret: string
}

アクセストークン取得方法

アクセストークンを取得するためには code を取得する必要がある。
通常のWebアプリケーションの場合、アプリケーションの画面上でOAuth認証画面へ遷移し、認証後にリダイレクトURIへ遷移し返ってくる、という挙動から問題はないのだが、Electronの場合リダイレクトURIなどというものが存在しないため困ることになる。

これを解決するために「リダイレクトした、もしくはどこかの画面に遷移した際URIに code が存在したらその値を取得しウィンドウを閉じる」などの動きを作る必要がある。

上記の getToken は下記のような形だとする。

export async function getToken(credentials: GithubOAuthCredentials): Promise<string> {
  const code = await getCode(credentials)
  const uri = `${accessTokenUri}?code=${code}&client_id=${credentials.client_id}&client_secret=${credentials.client_secret}`
  const headers = { 'Accept': 'application/json' }
  const response = await fetch(uri, { headers })
  const json = await response.json()
  const accessToken: string = json.access_token

  return accessToken
}

この場合の getCode は下記のような動きとなる。
createOAuthWindow は適当なウィンドウを作成する関数。
getCode が要求された場合、新規ウィンドウを立ち上げてそのウィンドウ上でGithubの認証を通す。
認証が通った後、リダイレクトがなされた場合、もしくは認証済みの場合単に遷移するのでその code を取得し、開いたウィンドウを閉じる。

const oauthWindow = Electron.BrowserWindow | null

function getCode(credentials: GithubOAuthCredentials): Promise<string> {
  return new Promise((resolve, reject) => {
    if (!oauthWindow || oauthWindow.isDestroyed) {
      oauthWindow = createOAuthWindow()
      oauthWindow.on('close', () => {
        oauthWindow = null
      })
      oauthWindow.webContents.on('did-get-redirect-request', (event: Electron.Event, oldUrl: string, newUrl: string): void => {
        const code = extractCode(newUrl)
        if (code) {
            oauthWindow.destroy()
        }
        resolve(code)
      })
      oauthWindow.webContents.on('will-navigate', (event: Electron.Event, url: string): void => {
        const code = extractCode(url)
        if (code) {
            oauthWindow.destroy()
        }
        resolve(code)
      })
    }
    const uri = `${authorizeUri}?client_id=${credentials.client_id}&scope=${credentials.scopes}`
    oauthWindow.loadURL(uri)
    oauthWindow.show()
  })
}

extractCode はこんな感じ。

function extractCode(url: string): string {
  const rawCode: RegExpExecArray | null = /code=([^&]*)/.exec(url) || null
  const code: string = (rawCode && rawCode.length > 1) ? rawCode[1] : ''
  const error: RegExpExecArray | null = /\?error=(.+)$/.exec(url) || null

  if (error) {
    throw new Error(error[1])
  }

  return code
}

参考

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0