1. suttang

    Posted

    suttang
Changes in title
+Electron で Github OAuth トークンを取得する
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,123 @@
+
+wamw Friday I/O 6回目
+株式会社ワムウでは、毎週金曜日は 興味がある事柄に取り組み、その成果を何らかの形でアウトプットする日としています。
+
+今日は時間がなかったので、メモです。
+
+## はじめに
+
+下記コードは既存コードからメモ用に抜き出し、書き換えたものです。
+これがそのまま完動するわけではないので、あくまで処理の流れをメモったものとなります。
+
+
+## 前提
+
+
+レンダラプロセスから何らかのアクションにてメインプロセスへ `connect-to-github` を送るとする。
+その際メインプロセス側で下記のような形でアクセストークンを取得したい。
+
+```typescript
+// 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
+}
+```
+
+
+## 参考
+
+http://manos.im/blog/electron-oauth-with-github/