Rails 6 で omniauth-cognito-idp を使って Amazon Cognito 認証を実装するサンプル


要点

Amazon Cognitoを使えば、deviseに依存せずに、ユーザ登録・ログイン・ログアウト・パスワードリセット・ソーシャルログイン・n段階認証・SAMLなど、様々な機能がアプリケーションコードから分離するかたちで追加可能です。Amazon Cognitoの他にも、Auth0やFirebase Authentication等があり、IDaaS(Identity as a Service)と呼ばれます。

本記事では、Railsアプリケーションで動作検証をする場合の手順を記載しています。

  1. Amazon Cognitoのユーザープールの作成とクライアントの設定の基本的な手順を解説(作業時間: 10分程度)
  2. 動作検証するためのサンプルアプリの実装方法を解決(作業時間: 10分程度)

なお、記事中のスクリーンショットに記載されたクライアントIDやシークレットは、既に削除されているため利用することはできません。ご自身で設定して動作検証してみてください。

環境

% ruby -v
ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-linux]

% bin/rails version
Rails 6.0.3.2

手順1: ユーザープールの作成とクライアントの設定

作業時間: 10分程度

Amazon Cognito のコンソールから「ユーザープールを作成する」を押下

プール名を入力

サンプルなのでデフォルトを確認するを押下

プールの作成

作成完了

プール ID をメモする:

ドメイン名を設定

ナビゲーションの ドメイン名 をクリックし、認証フォームのURLを設定。設定した、 https://{your prefix}.auth.ap-northeast-1.amazoncognito.com をメモする:

アプリクライアントの作成

ナビゲーションの アプリクライアント をクリックし、 アプリクライアントの追加 をクリック:

アプリクライアントIDアプリクライアントのシークレット をメモ:

アプリクライアントの設定

ナビゲーションの アプリクライアントの設定を押下し、コールバックURLとサインアウトURLを設定(それ以外の設定はスクリーンショットを参照):

これでcognito側の設定は完了です(なお、スクリーンショットの値は利用できません)。

手順2: アプリケーションの実装

作業時間: 10分程度

rails new

サンプルなので割愛

omniauth-cognito-idp を追加

gem 'omniauth-cognito-idp'

クレデンシャルの追加

今回はサンプルなので development で、ユーザプール作成時に生成した諸々をセットする:

$ bin/rails credentials:edit -e development
aws:
  access_key_id: 123
  secret_access_key: 345
  # 上記でメモした「ドメイン名」に含まれるリージョン
  region: ap-northeast-1
  # 上記でメモした「アプリクライアントID」
  cognito_client_id: *****YOUR CLIENT ID*****
  # 上記でメモした「アプリクライアントのシークレット」
  cognito_client_secret: *****YOUR SECRET*****
  # 上記でメモした「ドメイン名」
  cognito_user_pool_site: https://*****YOUR PREFIX*****.auth.ap-northeast-1.amazoncognito.com
  # 上記でメモした「プールID」
  cognito_user_pool_id: *****YOUR POOL ID*****

イニシャライザの追加

# config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  provider(
    :cognito_idp,
    Rails.application.credentials.aws[:cognito_client_id],
    Rails.application.credentials.aws[:cognito_client_secret],
    client_options: {
      site: Rails.application.credentials.aws[:cognito_user_pool_site]
    },
    scope: 'email openid',
    user_pool_id: Rails.application.credentials.aws[:cognito_user_pool_id],
    aws_region: Rails.application.credentials.aws[:region],
  )
end

HomeControllerの実装

root で表示する画面:

$ bin/rails g controller home show --skip-assets --skip-helper
<h1>Home#show</h1>
<p>Find me in app/views/home/show.html.erb</p>

<h1>RoR Cognito Sample</h1>
<p>Step 1 - Login.</p>
<%= button_to 'Login', 'auth/cognito-idp', method: :post %>
# config/routes.rb
Rails.application.routes.draw do
  root 'home#show'
end

CognitoIdpControllerの実装

OmniAuthで認証後の処理をするコールバック:

$ bin/rails g controller cognito_idp --skip-template-engine --skip-assets --skip-helper
class CognitoIdpController < ApplicationController
  def callback
    # 実際は provider と uid を使って、Userレコードを作成/参照し、user_id をsessionにセットするが
    # サンプルなのでauth hashをセット
    session[:userinfo] = request.env['omniauth.auth']

    redirect_to '/dashboard'
  end
end
# config/routes.rb
Rails.application.routes.draw do
  root 'home#show'
  get 'auth/cognito-idp/callback' => 'cognito_idp#callback'
end

セッションストアでCookieを利用して session[:userinfo] をセットしようとすると最大容量を超過するので、セッションストアをメモリに変更:

# config/initializers/session_store.rb
Rails.application.config.session_store :cache_store

config/environments/development.rbconfig.cache_store = :memory_store を追加。

ApplicationControllerに認証に関する振る舞いの追加:

実際はcurrent_userなども実装するがサンプルなのでuserinfoがsessionにセットされているかどうかを確認:

class ApplicationController < ActionController::Base
  private

  def authenticate_user!
    return redirect_to(root_path) unless signed_in?
  end

  def signed_in?
    session[:userinfo].present?
  end

  helper_method :signed_in?
end

DashboardControllerの実装

ログイン後の画面を実装する:

$ bin/rails g controller dashboard show --skip-assets --skip-helper
class DashboardController < ApplicationController
  before_action :authenticate_user!

  def show
  end
end
# config/routes.rb
Rails.application.routes.draw do
  root 'home#show'
  get 'auth/cognito-idp/callback' => 'cognito_idp#callback'
  get 'dashboard' => 'dashboard#show'
end
<h1>Dashboard#show</h1>
<p>Find me in app/views/dashboard/show.html.erb</p>
<p><%= session[:userinfo].inspect %></p>

<p><%= link_to 'logout', logout_path, method: :delete %></p>

SessionsControllerの実装:

ログアウト処理を実装(cognito側もログアウトする必要がある):

$ bin/rails g controller sessions --skip-template-engine --skip-assets --skip-helper
class SessionsController < ApplicationController
  before_action :authenticate_user!

  def destroy
    reset_session
    redirect_to cognito_logout_url
  end
end

direct ルーティングを使って、cognito側のログアウトを実装:

# config/routes.rb
Rails.application.routes.draw do
  root 'home#show'
  get 'auth/cognito-idp/callback' => 'cognito_idp#callback'
  get 'dashboard' => 'dashboard#show'
  delete 'logout' => 'sessions#destroy'

  direct :cognito_logout do
    query = {
      client_id: Rails.application.credentials.aws[:cognito_client_id],
      logout_uri: root_url,
    }.to_param
    "#{Rails.application.credentials.aws[:cognito_user_pool_site]}/logout?#{query}"
  end
end

動作検証

rails server を起動して localhost:3000 にアクセス:

ルートを表示

Login を押下:

cognitoのログインフォームが表示されるのでサインアップする

メールアドレスを確認して認証コードを入力するとログインが完了します。

参考

以上