ドキュメントには頻繁に更新が加えられ、その都度公開されています。本ページの翻訳はまだ未完成な部分があることをご了承ください。最新の情報については、英語のドキュメンテーションをご参照ください。本ページの翻訳に問題がある場合はこちらまでご連絡ください。

このバージョンの GitHub Enterprise はこの日付をもって終了となりました: 2020-11-12. 重大なセキュリティの問題に対してであっても、パッチリリースは作成されません。 パフォーマンスの向上、セキュリティの改善、新機能のためには、最新バージョンのGitHub Enterpriseにアップグレードしてください。 アップグレードに関する支援については、GitHub Enterprise supportに連絡してください。

認証の基本

さまざまな認証方法について、いくつかの例で学びます。

ここには以下の内容があります:

このセクションでは、認証の基本に焦点を当てます。 具体的には、アプリケーションのウェブフローを実装した、(Sinatra を使う) Rubyサーバーを、いくつかの方法で作成します。

このプロジェクトの完全なソースコードは、platform-samples リポジトリからダウンロードできます。

アプリケーションの登録

First, you'll need to register your application. 登録された各 OAuth アプリケーションには、一意のクライアント ID とクライアントシークレットが割り当てられます。 クライアントシークレットは共有しないでください。 共有には、文字列をリポジトリにチェックインすることも含まれます。

どのような情報を入力しても構いませんが、認証コールバック URL は例外です。 これが、アプリケーションの設定にあたってもっとも重要な情報と言えるでしょう。 認証の成功後に GitHub Enterprise Server がユーザに返すのは、コールバックURLなのです。

通常の Sinatra サーバーを実行しているので、ローカルインスタンスの場所は http://localhost:4567 に設定されています。 コールバック URL を http://localhost:4567/callback と入力しましょう。

ユーザ認証の承認

さて、簡単なサーバーの入力を始めましょう。 server.rb というファイルを作成し、以下の内容を貼り付けてください。

require 'sinatra'
require 'rest-client'
require 'json'

CLIENT_ID = ENV['GH_BASIC_CLIENT_ID']
CLIENT_SECRET = ENV['GH_BASIC_SECRET_ID']

get '/' do
  erb :index, :locals => {:client_id => CLIENT_ID}
end

Your client ID and client secret keys come from your application's configuration page. We recommend storing them as environment variables--which is exactly what we've done here.

次に、views/index.erbに以下の内容を貼り付けてください。

<html>
  <head>
  </head>
  <body>
    <p>
      Well, hello there!
    </p>
    <p>
      We're going to now talk to the GitHub API. Ready?
      <a href="https://github.com/login/oauth/authorize?scope=user:email&client_id=<%= client_id %>">Click here</a> to begin!</a>
    </p>
    <p>
      If that link doesn't work, remember to provide your own <a href="/apps/building-oauth-apps/authorizing-oauth-apps/">Client ID</a>!
    </p>
  </body>
</html>

(シナトラの仕組みに詳しくない方は、Sinatraのガイドを読むことをお勧めします。)

URLはアプリケーションに要求されたスコープscopeクエリパラメータで定義していることにも注目しましょう。 このアプリケーションでは、プライベートのメールアドレスを読み込むため、user:emailスコープをリクエストしています。

ブラウザでhttp://localhost:4567に移動します。 リンクをクリックすると、GitHub Enterprise Serverに移動し、以下のようなダイアログが表示されます。 GitHubのOAuthプロンプト

あなた自身を信用する場合は、[Authorize App]をクリックします。 おっと、 Sinatraが404エラーを吐き出しました。 いったい何が起こったのでしょうか。

さて、コールバックURLをcallbackに指定したときのことを覚えていますか。 そのときルートを設定しなかったので、GitHub Enterprise Serverはアプリケーションを認証した後、ユーザをどこにドロップするかがわからなかったのです。 では、この問題を解決しましょう。

コールバックの設定

server.rbにルートを追加して、コールバックが実行すべきことを指定します。

get '/callback' do
  # get temporary GitHub code...
  session_code = request.env['rack.request.query_hash']['code']

  # ... and POST it back to GitHub
  result = RestClient.post('https://github.com/login/oauth/access_token',
                          {:client_id => CLIENT_ID,
                           :client_secret => CLIENT_SECRET,
                           :code => session_code},
                           :accept => :json)

  # extract the token and granted scopes
  access_token = JSON.parse(result)['access_token']
end

アプリケーションの認証に成功すると、GitHub Enterprise Serverは一時的なcode値を提供します。 このコードを、access_tokenと引き換えに、POSTでGitHub Enterprise Serverに戻す必要があります。 GETおよびPOSTのHTTPリクエストをを簡素化するために、 rest-clientを使用しています。 REST経由でAPIにアクセスすることは、おそらくないということに留意してください。 もっと本格的なアプリケーションであれば、お好みの言語で書かれたライブラリを使った方がいいでしょう。

付与されたスコープの確認

Users can edit the scopes you requested by directly changing the URL. This can grant your application less access than you originally asked for. Before making any requests with the token, check the scopes that were granted for the token by the user. For more information about requested and granted scopes, see "Scopes for OAuth Apps."

付与されたスコープは、トークンの交換によるレスポンスの一部として返されます。

get '/callback' do
  # ...
  # Get the access_token using the code sample above
  # ...

  # check if we were granted user:email scope
  scopes = JSON.parse(result)['scope'].split(',')
  has_user_email_scope = scopes.include? 'user:email'
end

このアプリケーションでは、認証されたユーザのプライベートメールアドレスをフェッチするために必要なuser:emailスコープが付与されたかを確認するためscopes.include?を使用しています。 アプリケーションが他のスコープを要求していた場合は、それも確認します。

また、スコープ間には階層的な関係があるため、必要な最低限のスコープが付与されたか確認する必要があります。 たとえば、アプリケーションが userスコープを要求していた場合、user:emailスコープしか付与されていないかもしれません。 この場合、アプリケーションが要求したスコープは付与されていないかもしれませんが、付与されたスコープで十分だったでしょう。

リクエストを行う前にのみスコープを確認するだけでは不十分です。確認時と実際のリクエスト時の間に、ユーザがスコープを変更する可能性があります。 このような場合には、成功すると思っていたAPIの呼び出しが404または401ステータスになって失敗したり、情報の別のサブセットを返したりします。

この状況にうまく対応できるように、有効なトークンによるリクエストに対するすべてのAPIレスポンスには、X-OAuth-Scopesヘッダも含まれています。 このヘッダには、リクエストを行うために使用されたトークンのスコープのリストが含まれています。 In addition to that, the OAuth Applications API provides an endpoint to [check a token for validity][/v3/apps/oauth_applications/#check-an-authorization]. この情報を使用してトークンのスコープにおける変更を検出し、利用可能なアプリケーション機能の変更をユーザに通知します。

認証リクエストの実施

最後に、このアクセストークンで、ログインしたユーザとして認証のリクエストを行うことができます。

# fetch user information
auth_result = JSON.parse(RestClient.get('http(s)://[hostname]/api/v3/user',
                                        {:params => {:access_token => access_token}}))

# if the user authorized it, fetch private emails
if has_user_email_scope
  auth_result['private_emails'] =
    JSON.parse(RestClient.get('http(s)://[hostname]/api/v3/user/emails',
                              {:params => {:access_token => access_token}}))
end

erb :basic, :locals => auth_result

この結果を使って、やりたいことができます。 この例では、それらを単純にbasic.erbに直接書き出します。

<p>Hello, <%= login %>!</p>
<p>
  <% if !email.nil? && !email.empty? %> It looks like your public email address is <%= email %>.
  <% else %> It looks like you don't have a public email. That's cool.
  <% end %>
</p>
<p>
  <% if defined? private_emails %>
  With your permission, we were also able to dig up your private email addresses:
  <%= private_emails.map{ |private_email_address| private_email_address["email"] }.join(', ') %>
  <% else %>
  Also, you're a bit secretive about your private email addresses.
  <% end %>
</p>

「永続的な」認証の実装

ウェブページにアクセスするたびに、ユーザにアプリケーションへのログインを求めるというのは非常に悪いモデルです。 たとえば、http://localhost:4567/basicに直接移動してみてください。 エラーになるでしょう。

What if we could circumvent the entire "click here" process, and just remember that, as long as the user's logged into GitHub Enterprise Server, they should be able to access this application? Hold on to your hat, because that's exactly what we're going to do.

上記に上げたサーバはかなり単純なものです。 インテリジェントな認証を入れるために、トークンを保存するためセッションを使用するよう切り替えます。 これにより、認証はユーザーに意識されないものになります。

また、セッション内のスコープを永続的にしているため、そのスコープを確認した後にユーザが更新した場合や、トークンを取り消した場合に対処する必要があります。 これを行うために、rescueブロックを使用し、最初のAPI呼び出しが成功したことを確認し、トークンがまだ有効であることを確かめます。 次に、X-OAuth-Scopesレスポンスヘッダで、ユーザがuser:emailスコープを取り消していないことを確かめます。

advanced_server.rbというファイルを作成し、以下の行を貼り付けてください。

require 'sinatra'
require 'rest_client'
require 'json'

# !!! DO NOT EVER USE HARD-CODED VALUES IN A REAL APP !!!
# Instead, set and test environment variables, like below
# if ENV['GITHUB_CLIENT_ID'] && ENV['GITHUB_CLIENT_SECRET']
#  CLIENT_ID        = ENV['GITHUB_CLIENT_ID']
#  CLIENT_SECRET    = ENV['GITHUB_CLIENT_SECRET']
# end

CLIENT_ID = ENV['GH_BASIC_CLIENT_ID']
CLIENT_SECRET = ENV['GH_BASIC_SECRET_ID']

use Rack::Session::Pool, :cookie_only => false

def authenticated?
  session[:access_token]
end

def authenticate!
  erb :index, :locals => {:client_id => CLIENT_ID}
end

get '/' do
  if !authenticated?
    authenticate!
  else
    access_token = session[:access_token]
    scopes = []

    begin
      auth_result = RestClient.get('http(s)://[hostname]/api/v3/user',
                                   {:params => {:access_token => access_token},
                                    :accept => :json})
    rescue => e
      # request didn't succeed because the token was revoked so we
      # invalidate the token stored in the session and render the
      # index page so that the user can start the OAuth flow again

      session[:access_token] = nil
      return authenticate!
    end

    # the request succeeded, so we check the list of current scopes
    if auth_result.headers.include? :x_oauth_scopes
      scopes = auth_result.headers[:x_oauth_scopes].split(', ')
    end

    auth_result = JSON.parse(auth_result)

    if scopes.include? 'user:email'
      auth_result['private_emails'] =
        JSON.parse(RestClient.get('http(s)://[hostname]/api/v3/user/emails',
                       {:params => {:access_token => access_token},
                        :accept => :json}))
    end

    erb :advanced, :locals => auth_result
  end
end

get '/callback' do
  session_code = request.env['rack.request.query_hash']['code']

  result = RestClient.post('https://github.com/login/oauth/access_token',
                          {:client_id => CLIENT_ID,
                           :client_secret => CLIENT_SECRET,
                           :code => session_code},
                           :accept => :json)

  session[:access_token] = JSON.parse(result)['access_token']

  redirect '/'
end

コードの大部分は見慣れたもののはずです。 たとえば、ここでもGitHub Enterprise Server APIを呼び出すためにRestClient.getを使用し、 またERBテンプレート (この例ではadvanced.erb) に結果をレンダリングするため結果を渡しています。

また、ここではauthenticated?メソッドを使い、ユーザがすでに認証されているかを確認しています。 認証されていない場合は、authenticate!メソッドが呼び出され、OAuthのフローを実行して、付与されたトークンとスコープでセッションを更新します。

次に、 views内にadvanced.erbというファイルを作成し、以下のマークアップを貼り付けてください。

<html>
  <head>
  </head>
  <body>
    <p>Well, well, well, <%= login %>!</p>
    <p>
      <% if !email.empty? %> It looks like your public email address is <%= email %>.
      <% else %> It looks like you don't have a public email. That's cool.
      <% end %>
    </p>
    <p>
      <% if defined? private_emails %>
      With your permission, we were also able to dig up your private email addresses:
      <%= private_emails.map{ |private_email_address| private_email_address["email"] }.join(', ') %>
      <% else %>
      Also, you're a bit secretive about your private email addresses.
      <% end %>
    </p>
  </body>
</html>

コマンドラインからruby advanced_server.rbを呼び出します。このコマンドは、ポート4567 (単純なSinatraアプリケーションを使用していた時と同じポート) でサーバーを起動します。 http://localhost:4567 に移動すると、アプリケーションはauthenticate!を呼び出し、/callbackにリダイレクトします。 そして/callback/に戻され、認証が終わっているのでadvanced.erbがレンダリングされます。

GitHub Enterprise ServerのコールバックURLを/にするだけで、このラウンドトリップ経路を単純化できました。 ただし、server.rbadvanced.rbの両方が同じコールバックURLに依存しているため、動作は少し不安定になります。

また、このアプリケーションをGitHub Enterprise Serverデータにアクセスするよう認証したことがない場合、以前と同じ確認ダイアログが表示され、警告されるでしょう。