Skip to main content

GitHub App を使って [Login with GitHub] ボタンを作成する

このチュートリアルに従って Ruby コードを記述し、GitHub App の Web アプリケーション フローを介してユーザー アクセス トークンを生成します。

はじめに

このチュートリアルでは、Web サイトの [GitHub でログイン] ボタンを作成する方法について説明します。 Web サイトでは、GitHub App を使って、Web アプリケーション フローを介してユーザー アクセス トークンを生成します。 その後、Web サイトではそのユーザー アクセス トークンを使って、認証されたユーザーの代わりに API 要求を行います。

このチュートリアルでは Ruby を使いますが、Web 開発に使われる任意のプログラミング言語で Web アプリケーション フローを使うことができます。

Web アプリケーション フローとユーザー アクセス トークンについて

アプリのアクションをユーザーの属性にする場合は、アプリでユーザー アクセス トークンを使う必要があります。 詳しくは、「ユーザーに代わって GitHub アプリで認証する」を参照してください。

GitHub App 用のユーザー アクセス トークンを生成するには、Web アプリケーション フローとデバイス フローの 2 つの方法があります。 アプリが Web インターフェイスにアクセスできる場合は、Web アプリケーション フローを使う必要があります。 アプリから Web インターフェイスにアクセスできない場合は、代わりにデバイス フローを使う必要があります。 詳細については、「GitHub アプリのユーザー アクセス トークンの生成」および「GitHub アプリを使用して CLI を構築する」を参照してください。

前提条件

このチュートリアルでは、GitHub App を既に登録済みであることを前提としています。 GitHub App の登録について詳しくは、「GitHub App の登録」を参照してください。

このチュートリアルに従う前に、アプリのコールバック URL を設定する必要があります。 このチュートリアルでは、既定の URL が http://localhost:4567 のローカル Sinatra サーバーを使います。 たとえば、ローカル Sinatra アプリケーションの既定の URL で動作するように、コールバック URL を http://localhost:4567/github/callback にできます。 アプリをデプロイする準備ができたら、コールバック URL をライブ サーバー アドレスを使うように変更できます。 アプリのコールバック URL の更新について詳しくは、「GitHub App 登録の変更」と「ユーザー承認コールバック URL について」を参照してください。

このチュートリアルでは、Ruby と Ruby テンプレート システム ERB の基本的な知識があることを前提としています。 詳しくは、RubyERB を参照してください。

依存関係のインストール

このチュートリアルでは、Ruby の gem である Sinatra を使って、Ruby で Web アプリケーションを作成します。 詳しくは、Sinatra の README をご覧ください。

このチュートリアルでは、Ruby の gem である dotenv を使って、.env ファイルに保存されている値にアクセスします。 詳しくは、dotenv の README をご覧ください。

このチュートリアルに従うには、Ruby プロジェクトに Sinatra と dotenv の gem をインストールする必要があります。 たとえば、これは Bundler を使って行うことができます。

  1. Bundler がまだインストールされていない場合は、ターミナルで次のコマンドを実行します。

    gem install bundler
    
  2. アプリに Gemfile がまだない場合は、ターミナルで次のコマンドを実行します。

    bundle init
    
  3. アプリに Gemfile.lock がまだない場合は、ターミナルで次のコマンドを実行します。

    bundle install
    
  4. ターミナルで次のコマンドを実行して gem をインストールします。

    bundle add sinatra
    
    bundle add dotenv
    

クライアント ID とクライアント シークレットを保存する

このチュートリアルでは、クライアント ID とクライアント シークレットを環境変数に保存し、ENV.fetch を使ってそれらにアクセスする方法について説明します。 アプリをデプロイするときに、クライアント ID とクライアント シークレットの保存方法を変更することをお勧めします。 詳しくは、「クライアント シークレットを安全に保存する」を参照してください。

  1. GitHub の任意のページの右上隅にある、自分のプロファイル写真をクリックします。

  2. アカウント設定にアクセスしてください。

    • 個人用アカウントが所有するアプリの場合は、[設定] をクリックします。
    • 組織が所有するアプリの場合:
      1. [自分の組織] をクリックします。
      2. 組織の右側にある [設定] をクリックします。
  3. 左側のサイドバーで [ 開発者設定] をクリックします。

  4. 左側のサイドバーで、 [GitHub Apps] をクリックします。

  5. 作業したい GitHub App の横にある [編集] を選びます。

  6. アプリの設定ページで、ご自分のアプリのクライアント ID を見つけます。 これは、次の手順で .env ファイルに追加します。 クライアント ID は、アプリ ID とは異なることに注意してください。

  7. アプリの設定ページで、 [新しいクライアント シークレットの生成] をクリックします。 クライアント シークレットは、次の手順で .env ファイルに追加します。

  8. .env というファイルを Gemfile と同じレベルに作成します。

  9. プロジェクトに .gitignore ファイルがまだない場合は、.gitignore ファイルを Gemfile と同じレベルに作成します。

  10. .gitignore ファイルに .env を追加します。 こうすることで、クライアント シークレットを誤ってコミットするのを防ぐことができます。 .gitignore ファイルについて詳しくは、「ファイルを無視する」を参照してください。

  11. 次の内容を .env ファイルに追加します。 YOUR_CLIENT_ID は、ご自分のアプリのクライアント ID に置き換えます。 YOUR_CLIENT_SECRET は、ご自分のアプリのクライアント シークレットに置き換えます。

    CLIENT_ID="YOUR_CLIENT_ID"
    CLIENT_SECRET="YOUR_CLIENT_SECRET"
    

ユーザー アクセス トークンを生成するコードを追加する

ユーザー アクセス トークンを取得するには、まずユーザーにアプリの認可を求める必要があります。 ユーザーがアプリを認可すると、アプリのコールバック URL にリダイレクトされます。 コールバック URL への要求に code クエリ パラメーターを含めます。 アプリでそのコールバック URL を提供する要求を受け取ると、code パラメーターをユーザー アクセス トークンと交換できます。

次の手順に従って、ユーザー アクセス トークンを生成するコードを記述します。 スキップして最終的なコードに進むには、「完全なコード例」を参照してください。

  1. .env ファイルと同じディレクトリに Ruby ファイルを作成して、ユーザー アクセス トークンを生成するコードを保持します。 このチュートリアルでは、ファイルに app.rb という名前を付けます。

  2. app.rb の先頭に、次の依存関係を追加します。

    Ruby
    require "sinatra"
    require "dotenv/load"
    require "net/http"
    require "json"
    

    sinatra 依存関係と dotenv/load 依存関係で、前にインストールした gem を使います。 net/httpjson は Ruby 標準ライブラリに含まれています。

  3. 次のコードを app.rb に追加して、.env ファイルからアプリのクライアント ID とクライアント シークレットを取得します。

    Ruby
    CLIENT_ID = ENV.fetch("CLIENT_ID")
    CLIENT_SECRET = ENV.fetch("CLIENT_SECRET")
    
  4. 次のコードを app.rb に追加して、ユーザーにアプリの認証を求めるリンクを表示します。

    Ruby
    get "/" do
      link = '<a href="http(s)://HOSTNAME/login/oauth/authorize?client_id=<%= CLIENT_ID %>">Login with GitHub</a>'
      erb link
    end
    
  5. 次のコードを app.rb に追加して、アプリのコールバック URL への要求を処理し、要求から code パラメーターを取得します。 CALLBACK_URL を、アプリのコールバック URL からドメインを除いた値に置き換えます。 たとえば、コールバック URL が http://localhost:4567/github/callback の場合は、CALLBACK_URL/github/callback に置き換えます。

    Ruby
    get "CALLBACK_URL" do
      code = params["code"]
      render = "Successfully authorized! Got code #{code}."
      erb render
    end
    

    現段階でこのコードによってレンダリングされるのは、メッセージと code パラメーターだけです。 次の手順で、このコード ブロックを拡張します。

  6. 必要に応じて、進行状況をチェックします。

    app.rb は次のようになります。CALLBACK_URL はアプリのコールバック URL からドメインを除いた値です。

    Ruby
    require "sinatra"
    require "dotenv/load"
    require "net/http"
    require "json"
    
    CLIENT_ID = ENV.fetch("CLIENT_ID")
    CLIENT_SECRET = ENV.fetch("CLIENT_SECRET")
    
    get "/" do
      link = '<a href="http(s)://HOSTNAME/login/oauth/authorize?client_id=<%= CLIENT_ID %>">Login with GitHub</a>'
      erb link
    end
    
    get "CALLBACK_URL" do
      code = params["code"]
      render = "Successfully authorized! Got code #{code}."
      erb render
    end
    
    1. ターミナルで、app.rb が保存されているディレクトリから、ruby app.rb を実行します。 ローカルの Sinatra サーバーが起動するはずです。

    2. ブラウザーで http://localhost:4567 にアクセスします。 "Login with GitHub" というテキストのリンクが表示されるはずです。

    3. "Login with GitHub" のリンクをクリックします。

      アプリを認可していない場合は、リンクをクリックすると http(s)://HOSTNAME/login/oauth/authorize?client_id=CLIENT_ID に移動します。CLIENT_ID はアプリのクライアント ID です。 これは、ユーザーにアプリの認可を求める GitHub ページです。 ボタンをクリックしてアプリを認可すると、アプリのコールバック URL に移動します。

      以前にアプリを認可し、認可が取り消されていない場合は、認可プロンプトをスキップし、代わりにコールバック URL に直接移動します。 認可ダイアログを表示したい場合は、以前の認可を取り消すことができます。 詳しくは、「GitHub Apps の承認の確認と取り消し」を参照してください。

    4. "Login with GitHub" リンクをクリックし、アプリを認可するように求められたら、そうすることで表示されるコールバック URL ページに、次のようなテキストが表示されます: "Successfully authorized! Got code agc622abb6135be5d1f2."

    5. Sinatra が実行されているターミナルで、Ctrl+C キーを押してサーバーを停止します。

  7. app.rb の内容を次のコードに置き換えます。CALLBACK_URL は、アプリのコールバック URL からドメインを除いた値です。

    このコードでは、code パラメーターをユーザー アクセス トークンと交換するロジックを追加します。

    • parse_response 関数で、GitHub API からの応答を解析します。
    • exchange_code 関数で、code パラメーターをユーザー アクセス トークンと交換します。
    • コールバック URL 要求のハンドラーで exchange_code を呼び出して、code パラメーターをユーザー アクセス トークンと交換するようになります。
    • コールバック ページに、トークンが生成されたことを示すテキストが表示されるようになります。 トークン生成が成功しなかった場合は、ページにそのエラーが示されます。
    Ruby
    require "sinatra"
    require "dotenv/load"
    require "net/http"
    require "json"
    
    CLIENT_ID = ENV.fetch("CLIENT_ID")
    CLIENT_SECRET = ENV.fetch("CLIENT_SECRET")
    
    def parse_response(response)
      case response
      when Net::HTTPOK
        JSON.parse(response.body)
      else
        puts response
        puts response.body
        {}
      end
    end
    
    def exchange_code(code)
      params = {
        "client_id" => CLIENT_ID,
        "client_secret" => CLIENT_SECRET,
        "code" => code
      }
      result = Net::HTTP.post(
        URI("http(s)://HOSTNAME/login/oauth/access_token"),
        URI.encode_www_form(params),
        {"Accept" => "application/json"}
      )
    
      parse_response(result)
    end
    
    get "/" do
      link = '<a href="http(s)://HOSTNAME/login/oauth/authorize?client_id=<%= CLIENT_ID %>">Login with GitHub</a>'
      erb link
    end
    
    get "CALLBACK_URL" do
      code = params["code"]
    
      token_data = exchange_code(code)
    
      if token_data.key?("access_token")
        token = token_data["access_token"]
    
        render = "Successfully authorized! Got code #{code} and exchanged it for a user access token ending in #{token[-9..-1]}."
        erb render
      else
        render = "Authorized, but unable to exchange code #{code} for token."
        erb render
      end
    end
    
  8. 必要に応じて、進行状況をチェックします。

    1. ターミナルで、app.rb が保存されているディレクトリから、ruby app.rb を実行します。 ローカルの Sinatra サーバーが起動するはずです。
    2. ブラウザーで http://localhost:4567 にアクセスします。 "Login with GitHub" というテキストのリンクが表示されるはずです。
    3. "Login with GitHub" のリンクをクリックします。
    4. アプリを認可するように求められたら、そうします。
    5. "Login with GitHub" リンクをクリックし、アプリを認可するように求められたら、そうすることで表示されるコールバック URL ページに、次のようなテキストが表示されます: "Successfully authorized! Got code 4acd44861aeda86dacce and exchanged it for a user access token ending in 2zU5kQziE."
    6. Sinatra が実行されているターミナルで、Ctrl+C キーを押してサーバーを停止します。
  9. これでユーザー アクセス トークンを取得したので、このトークンを使って、ユーザーに代わって API 要求を行うことができます。 次に例を示します。

    この関数を app.rb に追加して、/user REST API エンドポイントでユーザーに関する情報を取得します。

    Ruby
    def user_info(token)
      uri = URI("http(s)://HOSTNAME/api/v3/user")
    
      result = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
        body = {"access_token" => token}.to_json
    
        auth = "Bearer #{token}"
        headers = {"Accept" => "application/json", "Content-Type" => "application/json", "Authorization" => auth}
    
        http.send_request("GET", uri.path, body, headers)
      end
    
      parse_response(result)
    end
    

    user_info 関数を呼び出し、ユーザーの名前と GitHub ログインを表示するようにコールバック ハンドラーを更新します。 CALLBACK_URL を、アプリのコールバック URL からドメインを除いた値に置き換えることを忘れないでください。

    Ruby
    get "CALLBACK_URL" do
      code = params["code"]
    
      token_data = exchange_code(code)
    
      if token_data.key?("access_token")
        token = token_data["access_token"]
    
        user_info = user_info(token)
        handle = user_info["login"]
        name = user_info["name"]
    
        render = "Successfully authorized! Welcome, #{name} (#{handle})."
        erb render
      else
        render = "Authorized, but unable to exchange code #{code} for token."
        erb render
      end
    end
    
  10. コードを次のセクションの完全なコード例に照らしてチェックします。 コードをテストするには、完全なコード例の後の「テスト」セクションで説明されている手順に従います。

完全なコード例

次に、前のセクションで概要を説明したコードの完全な例を示します。

CALLBACK_URL を、アプリのコールバック URL からドメインを除いた値に置き換えます。 たとえば、コールバック URL が http://localhost:4567/github/callback の場合は、CALLBACK_URL/github/callback に置き換えます。

Ruby
require "sinatra"
require "dotenv/load"
require "net/http"
require "json"

CLIENT_ID = ENV.fetch("CLIENT_ID")
CLIENT_SECRET = ENV.fetch("CLIENT_SECRET")

def parse_response(response)
  case response
  when Net::HTTPOK
    JSON.parse(response.body)
  else
    puts response
    puts response.body
    {}
  end
end

def exchange_code(code)
  params = {
    "client_id" => CLIENT_ID,
    "client_secret" => CLIENT_SECRET,
    "code" => code
  }
  result = Net::HTTP.post(
    URI("http(s)://HOSTNAME/login/oauth/access_token"),
    URI.encode_www_form(params),
    {"Accept" => "application/json"}
  )

  parse_response(result)
end

def user_info(token)
  uri = URI("http(s)://HOSTNAME/api/v3/user")

  result = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
    body = {"access_token" => token}.to_json

    auth = "Bearer #{token}"
    headers = {"Accept" => "application/json", "Content-Type" => "application/json", "Authorization" => auth}

    http.send_request("GET", uri.path, body, headers)
  end

  parse_response(result)
end

get "/" do
  link = '<a href="http(s)://HOSTNAME/login/oauth/authorize?client_id=<%= CLIENT_ID %>">Login with GitHub</a>'
  erb link
end

get "CALLBACK_URL" do
  code = params["code"]

  token_data = exchange_code(code)

  if token_data.key?("access_token")
    token = token_data["access_token"]

    user_info = user_info(token)
    handle = user_info["login"]
    name = user_info["name"]

    render = "Successfully authorized! Welcome, #{name} (#{handle})."
    erb render
  else
    render = "Authorized, but unable to exchange code #{code} for token."
    erb render
  end
end

テスト

このチュートリアルでは、アプリ コードが app.rb という名前のファイルに保存されていること、ローカル Sinatra アプリケーションの既定の URL http://localhost:4567 を使っていることを前提としています。

  1. ターミナルで、app.rb が保存されているディレクトリから、ruby app.rb を実行します。 ローカルの Sinatra サーバーが起動するはずです。

  2. ブラウザーで http://localhost:4567 にアクセスします。 "Login with GitHub" というテキストのリンクが表示されるはずです。

  3. "Login with GitHub" のリンクをクリックします。

    アプリを認可していない場合は、リンクをクリックすると http(s)://HOSTNAME/login/oauth/authorize?client_id=CLIENT_ID に移動します。CLIENT_ID はアプリのクライアント ID です。 これは、ユーザーにアプリの認可を求める GitHub ページです。 ボタンをクリックしてアプリを認可すると、アプリのコールバック URL に移動します。

    以前にアプリを認可し、認可が取り消されていない場合は、認可プロンプトをスキップし、代わりにコールバック URL に直接移動します。 認可ダイアログを表示したい場合は、以前の認可を取り消すことができます。 詳しくは、「GitHub Apps の承認の確認と取り消し」を参照してください。

  4. "Login with GitHub" リンクをクリックし、アプリを認可するように求められたら、そうすることで表示されるコールバック URL ページに、次のようなテキストが表示されます: "Successfully authorized! Welcome, Mona Lisa (octocat)."

  5. Sinatra が実行されているターミナルで、Ctrl+C キーを押してサーバーを停止します。

次の手順

クライアント シークレットを安全に保存する

アプリのクライアント シークレットは決して公開しないでください。 このチュートリアルでは、クライアント シークレットを gitignore の .env ファイルに保存し、ENV.fetch を使って値にアクセスしました。 アプリをデプロイする際に、クライアント シークレットを保存する安全な方法を選び、それに応じて値を取得するようにコードを更新する必要があります。

たとえば、アプリケーションをデプロイするサーバー上の環境変数にシークレットを保存できます。 Azure Key Vault などのシークレット管理サービスを使うこともできます。

デプロイ用にコールバック URL を更新する

このチュートリアルでは、http://localhost:4567 で始まるコールバック URL を使いました。 ただし、Sinatra サーバーを起動する場合は、http://localhost:4567 はお使いのコンピューターでローカルにのみ使うことができます。 アプリをデプロイする前に、運用環境で使うコールバック URL を使うようにコールバック URL を更新する必要があります。 アプリのコールバック URL の更新について詳しくは、「GitHub App 登録の変更」と「ユーザー承認コールバック URL について」を参照してください。

複数のコールバック URL を処理する

このチュートリアルでは、1 つのコールバック URL を使いましたが、アプリには最大 10 個のコールバック URL を含めることができます。 複数のコールバック URL を使う場合は、次のようにします。

  • 追加のコールバック URL をアプリに追加します。 コールバック URL の追加について詳しくは、「GitHub App 登録の変更」を参照してください。
  • http(s)://HOSTNAME/login/oauth/authorize にリンクする場合は、redirect_uri クエリ パラメーターを使って、ユーザーを目的のコールバック URL にリダイレクトします。 詳しくは、「GitHub アプリのユーザー アクセス トークンの生成」を参照してください。
  • アプリのコードで、get "CALLBACK_URL" do で始まるコード ブロックと同じように各コールバック URL を処理します。

追加のパラメーターの指定

http(s)://HOSTNAME/login/oauth/authorize にリンクする際、追加のクエリ パラメーターを渡すことができます。 詳しくは、「GitHub アプリのユーザー アクセス トークンの生成」を参照してください。

従来の OAuth トークンとは異なり、ユーザー アクセス トークンではスコープを使わないため、scope パラメーターを使ってスコープを指定することはできません。 代わりに、きめ細かいアクセス許可が使用されます。 ユーザー アクセス トークンを使用すると、ユーザーとアプリの両方が持っているアクセス許可のみが付与されます。

アプリのニーズに合わせてコードを調整する

このチュートリアルでは、認証されたユーザーに関する情報を表示する方法を示しましたが、このコードを調整して他のアクションを実行することもできます。 行う API 要求でアプリに追加のアクセス許可が必要な場合は、忘れずにアプリのアクセス許可を更新してください。 詳しくは、「GitHub アプリのアクセス許可を選択する」を参照してください。

このチュートリアルでは、すべてのコードを 1 つのファイルに保存しましたが、関数とコンポーネントを別のファイルに移動することもできます。

トークンを安全に保存する

このチュートリアルでは、ユーザー アクセス トークンを生成します。 ユーザー アクセス トークンの有効期限をオプトアウトしない限り、ユーザー アクセス トークンは 8 時間後に期限切れになります。 ユーザー アクセス トークンを再生成できる更新トークンも受け取ります。 詳しくは、「ユーザー アクセス トークンを更新する」を参照してください。

GitHub の API とさらにやり取りする予定がある場合は、将来使用できるようにトークンを保存しておく必要があります。 ユーザー アクセス トークンまたは更新トークンを保存する場合は、安全に保存する必要があります。 トークンは決して公開しないでください。

詳しくは、「GitHub App を作成するためのベスト プラクティス」を参照してください。

ベスト プラクティスに従う

GitHub App に関するベスト プラクティスに従うようにする必要があります。 詳しくは、「GitHub App を作成するためのベスト プラクティス」を参照してください。