RailsとkeycloakでOIDC認証を試してみた

株式会社 Vitalizeで webエンジニアをしている @nagaemon です。
現在は長野の支社にお世話になっており、現在はエンジニア + 地方創生事業に携わっています。
好きな言語はrubyです。

## keycloakとは
Keycloakはオープンソースのアイデンティティ・アクセス管理ソフトウェアです
概要はこちらの記事がわかりやすいので、ご覧ください。
[Keycloakとは](https://qiita.com/daian183/items/30f01e162e03567ff21b)

## 今回の目的
Qiitaでもkeycloak関連の記事はありますが、 Keycloak ✖️ Railsの記事がなかったので、自分の備忘録も兼ねて記事を書こうと思いました。

今回はkeycloakのOIDC（openIDconnect）を使って実際にrailsを用いてログイン機能が作れるのかどうかを試してみます。

## サンプルファイル
https://github.com/e601201/keycloak_with_rails

## 開発環境
### Keycloak 
- keycloak: 21.0.2
- database: mysql:8.0.33
### Rails 
- Rails 7.0.4

### その他
- Docker 24.0.2




## Keycloak側の設定


### Dockerでの立ち上げ方法
keycloakの[Geting started内のDocker](https://www.keycloak.org/getting-started/getting-started-docker)を参考にDocker-composeを作成。


``` docker-compose.yml
version: '3.8'

volumes:
  mysql_data:
      driver: local

services:
  mysql:
      image: mysql:8.0.33
      container_name: mysql
      volumes:
        - mysql_data:/var/lib/mysql
      environment:
        MYSQL_ROOT_PASSWORD: password
        MYSQL_DATABASE: keycloak
        MYSQL_USER: keycloak
        MYSQL_PASSWORD: password
      ports:
        - 3306:3306

  keycloak:
      image: quay.io/keycloak/keycloak:21.0.2
      container_name: keycloak
      environment:
        KEYCLOAK_ADMIN: admin
        KEYCLOAK_ADMIN_PASSWORD: admin
        KC_DB: mysql
        KC_DB_URL: jdbc:mysql://mysql:3306/keycloak
        KC_DB_URL_DATABASE: keycloak
        KC_DB_USERNAME: keycloak
        KC_DB_PASSWORD: password
        KC_HOSTNAME_STRICT: 'false'
        KC_HTTP_ENABLED: 'true'
        KC_HOSTNAME_STRICT_HTTPS: 'false'

      ports:
        - 8080:8080
      command:
        - "start"
        - "--auto-build"
```

``` shell:shell
$ docker compose up
```
## コマンド実行後にhttp://localhost:8080 にアクセス

keycloakの管理ページを確認できます。
![スクリーンショット 2023-08-07 15.34.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165799/0849919c-a53b-93e9-0231-b5410a2ed41d.png)

Administration Console にアクセスしましょう

ユーザ名とパスワードはdocker-composeファイルで指定していた ”admin" を使いましょう
![スクリーンショット 2023-08-07 13.17.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165799/dae68fdd-53d7-e45e-43e3-7d157d9ca571.png)

![スクリーンショット 2023-08-07 15.35.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165799/20c677d0-2138-e82b-cd86-9c7daa3e1926.png)

### レルム、クライアント、ユーザーの作成
レルム　クライアント　ユーザーはkeycloakの[Geting started内のDockerでの立ち上げ方法](https://www.keycloak.org/getting-started/getting-started-docker)に記載されてある方法を参考にしましょう。命名について、自分は今回下記のように作成しました。

- realm:  demo-realm 
- clients: demo-client
- user: demo-user

加えて、今回はローカルで試すので
Valid redirect URIs は http://127.0.0.1:3000/*
Set Web origins は http://127.0.0.1:3000
に変更しておきましょう
![スクリーンショット 2023-08-07 13.21.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165799/6bbf8d0b-3163-69ae-1a5d-3997f9ebc371.png)

## クライアント認証の設定
上記の設定に加えて、Rails等のサーバーサイドアプリケーションでの認証ですので。OIDCプロバイダ（keycloak）とのアクセス方法をConfidential Access Type (機密アクセスタイプ)に変更しましょう
具体的には、クライアント（サイドバー） → 設定タブ選択　→ Capability config内のクライアント認証をONにしてもらえればOKです。

![スクリーンショット 2023-08-07 14.08.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165799/3ae9d39a-0a80-9239-b97a-339d0d5193f4.png)

その後、クレデンシャルタブ内のクライアント認証を「Client Id and Secret」を選択。
クライアントシークレットをどこかにメモしておきましょう。Railsの設定時に利用します。
![スクリーンショット 2023-08-07 14.16.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165799/cc58d00d-d28c-7927-a473-5fead5d2e846.png)



## Rails側の設定
適当にディレクトリを作成後

``` shell:shell
$ cd dir #=> 自分の好きなディレクトリ 
$ rails new .
```
### Gemの利用
今回は
- gem 'omniauth-keycloak'
    - https://github.com/ccrockett/omniauth-keycloak
- gem "omniauth-rails_csrf_protection"
    - https://github.com/cookpad/omniauth-rails_csrf_protection
を利用しました。

``` ruby: Gemfile
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby "3.2.2"

# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.0.6"

#---------省略---------
gem 'omniauth-keycloak'
gem 'omniauth-rails_csrf_protection'
# ↑↑上記を記入↑↑
group :development, :test do
  # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
  gem "debug", platforms: %i[ mri mingw x64_mingw ]
end
#---------省略---------
```
その後

``` shell: shell
bundle install
```

omniauth自体の使い方は[omniauthのgithubページ](https://github.com/omniauth/omniauth)に懇切丁寧に書かれています。

そちらを参考に少しいじります。

```ruby:今回作ったappのディレクトリ/config/initializers/omniauth.rb
provider :keycloak_openid,
  'demo-client', #=> クライアント名
  'xxxxxxxxxxxxxxxx',#=> 事前にメモしておいたシークレットコード
  client_options: {base_url: '',#=> これをつけないと/authに飛んでしまうので、つける
  site: 'http://localhost:8080',#=> サイト
  realm: 'demo-realm'},#=> レルム名
  name: 'keycloak'
end
```

```ruby:今回作ったappのディレクトリ/config/routes.rb
Rails.application.routes.draw do
  root "static_pages#top"
  get '/auth/:provider/callback', to: 'sessions#create'
  get '/login', to: 'sessions#new'
  get '/top', to: 'static_pages#top'
  get '/user', to: 'static_pages#user'
  delete '/logout', to: 'sessions#delete'
end
```

```/auth/:provider/callback ``` とエンドポイントを指定すると、omniauthがよしなにやってくれます。

sessions_controllerは下記のように作りました。

```ruby:今回作ったappのディレクトリ/app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  protect_from_forgery
  def new
    render :new
  end

  def create
    user_info = request.env['omniauth.auth']
    uid = user_info&.uid
    email = user_info&.info&.email
    @user = User.find_or_create_by(email:, uid:)
    if @user.persisted?
      session[:user_id] = @user.id
      redirect_to user_path
    else
      redirect_to root_path
    end
  end

  def delete
    session[:user_id] = nil
    redirect_to  top_path
  end 
end
```

ビューはこのように加筆
``` ruby:

<!DOCTYPE html>
<html>
# ------省略-------
<body>
    <main class="container mx-auto mt-28 px-5">
    <div class="bg-indigo-200">
        <% if user_signed_in? %>
          <%= button_to "ログアウト", logout_path, class: "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full", method: :delete %>
        <% else %>
          <%= button_to "keycloak_ログイン", '/auth/keycloak', class: "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full", data: {turbo: false} %>
        <% end %>
    </div>
      <%= yield %>
    </main>
  </body>

</html>
```

user_signed_in?などはヘルパーにしました。最後にサンプルをgithubにあげます。

### 設定が終わったら
``` shell:shell
$ rails s
```
でサーバーを立ち上げましょう。

http://localhost:3000

トップのログイン画面
![スクリーンショット 2023-08-07 15.20.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165799/e28a59c1-171e-a083-0653-a55b4f654428.png)

keycloak_ログインボタンを押すとkeycloakのログイン画面へリダイレクトされます。
![スクリーンショット 2023-08-07 15.20.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165799/c17ec98f-fc07-2ae8-cfab-c4fcb43af1a3.png)

keycloakで作成したuserの名前とパスワードを入力するとログイン後のuserページに遷移できます。
![スクリーンショット 2023-08-07 15.20.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165799/ceb1fb2f-d969-505b-c3d1-1d2d0397a9af.png)

結構簡単に実装ができましたので、皆様も是非お試しください。