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

認証の基本

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

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

Did this doc help you?

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

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

アプリケーションの登録

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

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

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

ユーザ認証の承認

非推奨の注意: GitHubは、クエリパラメータを使ったAPIの認証を廃止します。 APIの認証は、HTTPの基本認証で行うべきです。予定された一時停止を含む詳しい情報についてはブログポストを参照してください。

クエリパラメータを使ったAPIの認証は、利用はできるものの、セキュリティ上の懸念からサポートされなくなりました。 その代わりに、インテグレータはアクセストークン、client_idもしくはclient_secretをヘッダに移すことをおすすめします。 GitHubは、クエリパラメータによる認証の削除を、事前に通知します。

さて、簡単なサーバーの入力を始めましょう。 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

クライアント ID とクライアントシークレットは、アプリケーションの設定ページから取得されます。 これらの値は、GitHub Enterprise やその他あらゆる公開の場には、決して保存しないでください。 これらの値は、環境変数として保存することをお勧めします。この例でも、そのようにしています。

次に、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に移動し、以下のようなダイアログが表示されます。 GitHubのOAuthプロンプト

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

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

コールバックの設定

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

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

将来的に、ユーザはあなたがリクエストしたスコープを編集できるようになり、アプリケーションのアクセス権は、最初に要求したものより少なくなるかもしれません。 ですから、このトークンでリクエストを行う前に、ユーザからトークンに付与されたスコープを確認すべきです。

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

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ヘッダも含まれています。 このヘッダには、リクエストを行うために使用されたトークンのスコープのリストが含まれています。 それに加えて、OAuthアプリケーションAPIは、[check a token for validity][/v3/apps/oauth_applications/#check-a-token]にエンドポイントを提供します。 この情報を使用してトークンのスコープにおける変更を検出し、利用可能なアプリケーション機能の変更をユーザに通知します。

認証リクエストの実施

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

# 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に直接移動してみてください。 エラーになるでしょう。

「ここをクリック」というプロセスをすべてなくし、ユーザがGitHub Enterpriseにログインしている限りそれを記憶して、このアプリケーションにアクセスできるとしたらどうでしょうか。 実のところ、これからやろうとしていることはまさにそういうことなのです。

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

また、セッション内のスコープを永続的にしているため、そのスコープを確認した後にユーザが更新した場合や、トークンを取り消した場合に対処する必要があります。 これを行うために、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 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のコールバックURLを/にするだけで、このラウンドトリップ経路を単純化できました。 ただし、server.rbadvanced.rbの両方が同じコールバックURLに依存しているため、動作は少し不安定になります。

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

Did this doc help you?