OAuth 2.0 IdP

目的

これまで SHIRASAGI では API 機能を提供してきました。 しかし、ユーザーID/パスワード方式の認証しかなく、あまり広く利用される様な状況ではありません。 ここで、一般的に利用されている OAuth 2.0 IdP 機能を SHIRASAGI が提供することで、広く API クライアントを開発できるようにします。 この機能は v1.17.0 以降の SHIRASAGI で利用することができます。

SHIRASAGI の OAuth 2.0 IdP

アプリケーション

次の二つのタイプのアプリケーションをサポートしています。

アプリケーションは SHIRASAGI のシステムにログイン後、システム設定 ー 認証 ー OAuthアプリ から作成することができます。 デモサイトでは https://demo.ss-proj.org/.sys/auth/oauth2_applications

フロー

次の3 種類のフローをサポートしています。

エンドポイント

サンプルコード

Authorization Code Flow

このフローではクライアントタイプのアプリケーションを利用しますので、 事前に SHIRASAGI の管理画面からクライアントタイプのアプリケーションを作成しておいてください。

# ドメイン
your_domain = "https://your-domain/"
# 認可エンドポイントの URL
authorize_url = URI.join(your_domain, "/.mypage/login/oauth2/authorize")
# トークンエンドポイントの URL
token_url = URI.join(your_domain, "/.mypage/login/oauth2/token")
# SHIRASAGI 管理画面で作成したアプリケーションのクライアントID
client_id = ...
# SHIRASAGI 管理画面で作成したアプリケーションのクライアントシークレット
client_secret = ...
# SHIRASAGI 管理画面で作成したアプリケーションを作成する際に登録したリダイレクト URL
redirect_uri = ...
# SHIRASAGI 管理画面で作成したアプリケーションを作成する際に選択した権限
scopes = %w(...)

authorize_request = {
  response_type: "code",
  client_id: client_id,
  redirect_uri: redirect_uri,
  scope: scopes.join(" ")
}
# コンソールに表示される URL にブラウザでアクセスする
puts "#{authorize_url}?#{authorize_request.to_query}"

# コンソールに表示される URL にブラウザでアクセスすると、シラサギ側のログイン画面が表示される。
# そして、ログインに成功すると redirect_uri に戻ってくる。
# redirect_uri には code というパラメータが含まれているので、記録する
authorize_code = ...

token_resp = Faraday.new(url: token_url).post do |req|
  req.headers['Authorization'] = "Basic #{Base64.strict_encode64([ client_id, client_secret ].join(":"))}"
  req.params['grant_type'] = 'authorization_code'
  req.params['code'] = authorize_code
  req.params['redirect_uri'] = redirect_uri
end

access_token = JSON.parse(token_resp.body).then { |json| json["access_token"] }
puts "access_token=#{access_token}"

# 取得したアクセストークンを用いてユーザーアカウント情報を取得する

account_resp = Faraday.new(url: URI.join(your_domain, "/.u/user_account.json")).get do |req|
  req.headers['Authorization'] = "Bearer #{access_token}"
end

account_json = JSON.parse(account_resp.body)
puts "email=#{account_json['email']}"

Implicit Flow

このフローではクライアントタイプのアプリケーションを利用します。

# ドメイン
your_domain = "https://your-domain/"
# 認可エンドポイントの URL
authorize_url = URI.join(your_domain, "/.mypage/login/oauth2/authorize")
# SHIRASAGI 管理画面で作成したアプリケーションのクライアントID
client_id = ...
# SHIRASAGI 管理画面で作成したアプリケーションを作成する際に登録したリダイレクト URL
redirect_uri = ...
# SHIRASAGI 管理画面で作成したアプリケーションを作成する際に選択した権限
scopes = %w(...)

authorize_request = {
  response_type: "token",
  client_id: client_id,
  redirect_uri: redirect_uri,
  scope: scopes.join(" ")
}
# コンソールに表示される URL にブラウザでアクセスする
puts "#{authorize_url}?#{authorize_request.to_query}"

# コンソールに表示される URL にブラウザでアクセスすると、シラサギ側のログイン画面が表示される。
# そして、ログインに成功すると redirect_uri に戻ってくる。
# redirect_uri には access_token というパラメータが含まれているので、記録する
access_token = ...

# 取得したアクセストークンを用いてユーザーアカウント情報を取得する

account_resp = Faraday.new(url: URI.join(your_domain, "/.u/user_account.json")).get do |req|
  req.headers['Authorization'] = "Bearer #{access_token}"
end

account_json = JSON.parse(account_resp.body)
puts "email=#{account_json['email']}"

urn:ietf:params:oauth:grant-type:jwt-bearer

クライアントタイプアプリケーションでもサービスタイプアプリケーションでも利用可能です。 本サンプルではサービスタイプアプリケーションを利用します。

# ドメイン
your_domain = "https://your-domain/"
# トークンエンドポイントの URL
token_url = URI.join(your_domain, "/.mypage/login/oauth2/token")
# SHIRASAGI 管理画面で作成したアプリケーションのクライアントID
client_id = ...
# SHIRASAGI 管理画面で作成したアプリケーションを作成する際に登録した公開鍵のペアになる秘密鍵
key = OpenSSL::PKey::RSA.new(::File.read("/path/to/private_key.pem"))
# SHIRASAGI 管理画面で作成したアプリケーションを作成する際に選択した権限
scopes = %w(...)
# 成り済ましたいユーザーのメールアドレス
user_email = ...

jwt_assertion = JSON::JWT.new(
  # issuer
  iss: client_id,
  # subject
  sub: user_email,
  # scope
  scope: scopes.join(" "),
  # audience
  aud: token_url,
  # expires at
  exp: 1.hour.from_now.to_i,
  # issued at
  iat: Time.zone.now.to_i
)
jwt_assertion = jwt_assertion.sign(key)

token_resp = Faraday.new(url: token_url).post do |req|
  req.params['grant_type'] = "urn:ietf:params:oauth:grant-type:jwt-bearer"
  req.params['assertion'] = jwt_assertion.to_s
end

access_token = JSON.parse(token_resp.body).then { |json| json["access_token"] }
puts "access_token=#{access_token}"

# 取得したアクセストークンを用いてユーザーアカウント情報を取得する

account_resp = Faraday.new(url: URI.join(your_domain, "/.u/user_account.json")).get do |req|
  req.headers['Authorization'] = "Bearer #{access_token}"
end

account_json = JSON.parse(account_resp.body)
puts "email=#{account_json['email']}"

本フローでは任意の任意のユーザーに成り済ますことができます。ログイン画面が表示されることはありません。非常に強力なアプリケーションタイプとフローとなります。

RSA 鍵の作成に関して openssl コマンドがインストールされている環境であれば、次のコマンドを実行することで秘密鍵と公開鍵とを作成することができます。

# 秘密鍵の作成
openssl genrsa 2048 > private_key.pem
# 公開鍵の作成
openssl rsa -in private_key.pem -pubout -out public_key.pem