3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

DoorkeeperのAuthorization Code Flowが導入されている状態からPKCEを導入する手順

Posted at

これは何?

前回こちら の記事でPKCEのフローをまとめました。
今回は実際にdoorkeeperというgemを使って実装していこうと思います。

doorkeeper gemでPKCEを導入する手順

以前 こちら の記事でdoorkeeper gemでOAuthプロバイダ機能を作成する記事を投稿しました。

今回は前回の状態からPKCEを導入する手順を記載します。
(※ doorkeeper gem のwikiにもPKCEについて記載があるのでご確認お願いします。)
(https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-PKCE-flow)

導入手順

# wikiに記載されているように以下のコマンドを実行します。
bundle exec rails generate doorkeeper:pkce

# 実行すると以下のmigrationファイルが作成されます。
db/migrate/20210330224805_enable_pkce.rb

migrationファイルの中身は以下のようになっており oauth_access_grantsテーブルへのカラム追加となっています。

# frozen_string_literal: true

class EnablePkce < ActiveRecord::Migration[6.0]
  def change
    add_column :oauth_access_grants, :code_challenge, :string, null: true
    add_column :oauth_access_grants, :code_challenge_method, :string, null: true
  end
end

migrateを実行します。

rails db:migrate RAILS_ENV=development

これで oauth_access_grants テーブルへ code_challenge、code_challenge_method を保存することができます。

続いてOAuth Applicationを作成していきます。

http://localhost:3000/oauth/applications

へアクセスし[New Application]をクリックします。

pkce_oauthアプリ作成1.jpg

アプリケーションの情報を入力して登録します。
pkce_oauthアプリ作成2.jpg

pkce_oauthアプリ作成3.jpg

今回Client側のアプリのURLは http://localhost:3001 としcallbackURLを http://localhost:3001/pkce_callback として準備しました。

画面に表示されている CLIENT_ID(UID)、CLIENT_SECRET、scopes をClient側のアプリへ設定しておきます。

ここでPKCEの仕組みを以下へ記載します。

  1. code_verifier を使って code_challenge_method に合った code_challenge を生成し、code_challenge, code_challenge_method をパラメータへ指定して認可画面をリクエストします。
  2. 認可サーバは code_challenge, code_challenge_method を保存し、Clientへ認可コードを返します。
  3. Clientは認可コードと code_verifier を使ってアクセストークンを取得します。
  4. 認可サーバで受け取った code_verifierを保存してある code_challenge_methodで変換して code_challengeのになるか検証します。

それではPKCEを使うためClientアプリケーションの OAuth2::Clientの部分を実装していきます。

以下は認可画面のURLを作成する処理のサンプルになります。

# 認可画面のURLを作成する処理
def pkce_authorization
  session[:code_verifier] = SecureRandom.alphanumeric(100)

  client = OAuth2::Client.new(
    ENV["CLIENT_ID_PKCE"],
    ENV["CLIENT_SECRET_PKCE"],
    site: ENV["SITE"],
    authorize_url: ENV["AUTHORIZE_URL"],
    token_url: ENV["TOKEN_URL"]
  )
  code_challenge = Base64.urlsafe_encode64(OpenSSL::Digest::SHA256.digest(session[:code_verifier]), padding: false)

  authorize_url = client.auth_code.authorize_url(
    redirect_uri: ENV["CALLBACK_URI_PKCE"],
    scope: ENV["SCOPE"],
    code_challenge: code_challenge,
    code_challenge_method: "S256"
  )
  redirect_to authorize_url
end

code_verifier の値は、[A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~" からなるランダムな文字列であり、最低43文字、最大128文字の長さが必要となります。

今回は

code_verifier = SecureRandom.alphanumeric(100)

のように100文字の英数字を設定します。ref:https://tools.ietf.org/html/rfc7636

code_challengecode_challenge_method によって値が変わります。
code_challenge_methodplain にした場合
code_challenge = code_verifier となります。
code_challenge_methodS256 にした場合
code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) となります。
ref:https://tools.ietf.org/html/rfc7636

これをrubyで実装すると

code_challenge = Base64.urlsafe_encode64(OpenSSL::Digest::SHA256.digest(session[:code_verifier]), padding: false)

で実装できます。

code_challenge_method は 2種類あるのですが plaincode_verifiercode_challenge が同じ値のため特に plain を選ぶ理由がなければセキュリティリスクを考えて S256 を使用しましょう。

続いて認可コードが返ってきてからアクセストークンを取得する部分のサンプルが以下になります。

# 認可コードよりアクセストークンを取得
def pkce_callback
  client = OAuth2::Client.new(
    ENV["CLIENT_ID_PKCE"],
    ENV["CLIENT_SECRET_PKCE"],
    site: ENV["SITE"],
    authorize_url: ENV["AUTHORIZE_URL"],
    token_url: ENV["TOKEN_URL"]
  )

  code_verifier = session[:code_verifier]
  session.delete(:code_verifier)
  @access_token = client.auth_code.get_token(
    params[:code],
    redirect_uri: ENV["CALLBACK_URI_PKCE"],
    code_verifier: code_verifier
  )

  render :callback
end

実際に認可画面から認可後、アクセストークンを取得できるか確認します。

認可画面
pkce_認可画面.jpg

認可コード取得後、アクセストークン取得
pkce_認可コードからアクセストークン取得.jpg

念のため、認可サーバ側で code_challenge, code_challenge_method が保存されているか確認しておきます。

Doorkeeper::AccessGrant.all

  Doorkeeper::AccessGrant Load (0.3ms)  SELECT "oauth_access_grants".* FROM "oauth_access_grants" LIMIT ?  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Doorkeeper::AccessGrant id: 1, resource_owner_id: 1, application_id: 2, token: "ygCPJhHoenE3vfiY1StLqCVBRJWTZfWnZFdOPUcTn7M", expires_in: 600, redirect_uri: "http://localhost:3001/pkce_callback", created_at: "2021-05-03 07:32:25", revoked_at: "2021-05-03 07:32:25", scopes: "public write", code_challenge: "oDJceObZlNuTivuP6EocfJZt2ODCdyjOh5IyUXK_4Rw", code_challenge_method: "S256">]>

正常にcode_challenge, code_challenge_methodが保存されていることが確認できました!

最後に

doorkeeper gemを使用していればPKCEの導入は非常に簡単にできました。
Authorization Code Flowとの共存も可能なため実際の業務でもPKCEは導入しやすいと思います!

3
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?