ローカル開発環境で簡単に扱えるOIDCのIdP(Identity Provider)として、Autheliaがなかなか良かったので簡単に紹介したいと思います。
背景
ある日、私が携わっているプロダクトにOIDCによるログイン機能を追加することになりました。
このプロダクトはチームメンバーが各自のローカル環境で開発しているものなので、IdPについても実際のIdPに各自のテストクライアントを登録したりせずに、ローカルで簡単に構築できたらいいなと思いました。
(もちろん、検証環境は実際に使用するIdPを使います)
そこで、ローカルに構築して使えそうなIdPのプロダクトをいくつか探してみたところ、Autheliaが手頃でよかった、というお話になります。
Autheliaとは
Autheliaは、多要素認証やSSO(シングルサインオン)を実現するための、オープンソースのIAM(ID・アクセス管理)サーバーです。
主な特徴は以下のとおりです。
- 軽量・高速
- Go製で、トップページの謳い文句によると、圧縮されたコンテナサイズは20MB未満、通常利用時のメモリ使用量は30MB未満だそうです
- SSO(シングルサインオン)機能
- 多要素認証(ワンタイムパスワード、モバイルプッシュ通知、WebAuthnなど)
- 柔軟なアクセスコントロールポリシー
- 「このURLは2要素認証必須」とか
- 基本的には認証リバースプロキシとして動く
- SSOでよくある方式
- OIDC IdPの機能も持っている
- ただし、v4.38(2024年8月)の時点ではベータ扱いです
- OIDC実装に関するロードマップ
- 全ての設定を設定ファイル(YAML)で行う
最後の「全ての設定を設定ファイルで行う」というのが今回の要件に特にマッチしました。
Autheliaは、接続するクライアントの情報なども全て設定ファイルに記述する方式となっています。
通常の運用だと管理画面などから設定できたほうがよいのかもしれませんが、今回のローカル開発という状況では、docker compose up -d
したら全て設定済みのIdPが動いてほしいので、むしろ嬉しい仕様でした。あらかじめ開発用に設定済みのファイルを用意しておけばいいですからね。
また、OIDCのIdP機能についてはv4.38(2024年8月)の時点ではベータ扱いとなっていましたが、今回のローカル開発に使用してみた程度では特に問題なさそうでした。
もちろん、プロダクション環境でAutheliaをIdPとして運用するようなことは、ベータの間は避けたほうがよいでしょうし、また、検証環境などでは本番と同じIdPを使用したほうがよいでしょう。
Authelia以外の選択肢
Authelia以外の選択肢としては、以下のようなものを検討しました。
-
Keycloak
- この手のIAMプロダクトの中ではかなり有名で鉄板な感じがするやつです
- 今回の要件に対してオーバースペックすぎるのと、Web画面上でセットアップを進めなければいけないので見送りました
-
ZITADEL(クラウドサービスもありますが、セルフホストできます)
- 今回の調査で初めて知ったのですが、Keycloakと対比した記事がよく出てくるぐらいすごそうなIAMプロダクトです
- Keycloakと同じ理由で見送りましたが、個人的にとても興味を惹かれました
-
dex
- 複数の異なる認証ソースを統合するポータルというかプロキシのようなプロダクトです
- dex自身が認証ソースを持つわけではなく、上流のIdPに委ねる形なので、今回の用途には合いませんでした
- LDAPをソースにすれば使えそうですが、それはそれで面倒
-
node-oidc-server ※ライブラリ
- Node.jsでOIDCサーバーを構築するためのライブラリです(単体で起動して使うようなプロダクトではないです)
- いいプロダクトが見つからなければ、最終手段として、これを使って簡単な実装をするのもありかなと考えていました
Autheliaのセットアップ
前置きが長くなりましたが、早速Autheliaのセットアップをしてみたいと思います。
今回はDockerを利用してサービスを立ち上げます。
今回使用したバージョンは次のとおりです。
- Authelia 4.38.9
全体のファイル構成は次のとおりです。
.
├── authelia
│ ├── config ← 設定ファイル置き場
│ │ ├── configuration.yml ← 設定ファイル
│ │ └── users_database.yml ← ユーザー定義ファイル
│ └── data ← データ置き場
│ ├── db.sqlite3 ← データベース(今回はSQLite)
│ └── notification.txt ← 実際の通知メールの代わりに保存されるテキスト
└── compose.yaml
Docker (compose.yaml)
compose.yaml
は次のとおりです。
ボリュームの部分は設定ファイル置き場とデータファイル置き場になります。
データ置き場はディレクトリだけ用意すれば、Autheliaが勝手にファイルを置きます。
Autheliaにhttp://127.0.0.1:9091
でアクセスできるようにする想定です。
name: authelia-sandbox
services:
authelia:
image: authelia/authelia:4.38.9
# restart: unless-stopped
ports:
- 9091:9091
volumes:
- ./authelia/config:/config:ro
- ./authelia/data:/data
Autheliaの設定
Autheliaの設定ファイルは、公式がコメント付きのテンプレートファイルを用意してくれているので、これをコピーして書き換えていきます。
コメント付きで1400行前後と、なかなかボリュームのある設定ファイルとなっています。
ローカルOIDCのための設定
今回の目的であるローカル環境用のOIDCとして最低限の設定値を載せておきます。
テンプレートのコメントを全て残したまま載せると膨大な量になってしまうので必要な項目のみに絞っています(それでも150行弱)。
各項目を丁寧に説明していると長くなってしまうので、項目名から察してもらうか公式マニュアルを参照しながら設定してください。
私もテンプレートのコメントと公式マニュアルを交互に見ながら設定しました。
途中で出てくる秘密鍵の生成やクライアントシークレットのハッシュ値の生成については、生成コマンドを後で紹介します。
それ以外のキーやシークレットは平文で設定します(文字数や文字種に制限がある場合あり)。
ファイル名はconfiguration.yml
とします。
theme: 'light'
server:
address: 'tcp://:9091/'
log:
level: 'debug'
telemetry:
metrics:
enabled: false
# TOTPの設定
totp:
disable: false
# TOTPのissuer名
issuer: 'localhost'
# WebAuthnの設定
webauthn:
disable: false
# WebAuthnの表示名
display_name: 'Authelia Sandbox'
identity_validation:
reset_password:
# パスワードリセットに使用するJWTのシークレット
jwt_secret: 'authelia_sandbox_secret'
# 認証バックエンドの設定
authentication_backend:
password_reset:
disable: false
# ファイルベースのユーザーデータベースの設定(他にはLDAPが使えます)
file:
# ユーザーを定義するYAMLファイル
path: '/config/users_database.yml'
watch: false
search:
email: false
case_insensitive: false
# パスワードのアルゴリズム
password:
algorithm: 'argon2'
argon2:
variant: 'argon2id'
iterations: 3
memory: 65536
parallelism: 4
key_length: 32
salt_length: 16
# パスワードポリシー
password_policy:
standard:
enabled: false
min_length: 8
max_length: 0
require_uppercase: true
require_lowercase: true
require_number: true
require_special: true
zxcvbn:
enabled: false
min_score: 3
privacy_policy:
enabled: false
require_user_acceptance: false
policy_url: ''
# アクセスコントロールの設定(今回はあまり関係ない気がする)
access_control:
default_policy: 'deny'
rules:
- domain:
- '127.0.0.1'
- 'localhost'
- '*.localhost'
policy: 'one_factor'
# AutheliaのセッションとCookieの設定
# セッションストレージはインメモリのほかにRedisが使える
session:
# セッションデータのシークレット
secret: 'authelia_sandbox_secret'
# ここは立ち上げる環境に合わせてドメインとURLを設定する
# 本当は localhost としたかったが、ドットが含まれている必要があるようなので、127.0.0.1 とした
# もちろん、hosts 等を書き換えて適当なドメインを振ってもよい
cookies:
- name: 'authelia_sandbox_session'
domain: '127.0.0.1'
authelia_url: 'https://127.0.0.1:9091'
name: 'authelia_sandbox_session'
same_site: 'lax'
inactivity: '5m'
expiration: '1h'
remember_me: '1M'
# Autheliaの管理するデータのストレージ(SQLite, MySQL, PostgreSQL)
storage:
encryption_key: 'authelia_sandbox_encryption_key'
# SQLite
local:
path: '/data/db.sqlite3'
# 通知メールの設定
# SMTPの他に、テキストファイルに保存することができる
notifier:
disable_startup_check: false
filesystem:
# メール送信の代わりに保存されるテキストファイル
filename: '/data/notification.txt'
identity_providers:
oidc:
# HMACシークレット
hmac_secret: 'this_is_a_secret_abc123abc123abc'
# JWKs
jwks:
- key_id: 'authelia_sandbox'
algorithm: 'RS256'
use: 'sig'
# JWTの署名に使用する秘密鍵
key: |
-----BEGIN PRIVATE KEY-----
MIIJQQIBADANB...Eb0X26yBScK7gK
-----END PRIVATE KEY-----
# クライアントアプリ(OIDC RP)の設定
# grant_type等はお好みで設定してください
# ここでは単純なOIDCログインのために、id_tokenのみのimplicitフローを許可しました
clients:
- client_id: 'authelia_sandbox_client'
client_name: 'Authelia Sandbox Client'
# シークレットは平文ではなくハッシュ値で設定する必要がある
# `authelia crypto hash generate pbkdf2` で生成できる
client_secret: '$pbkdf2-sha512$310000$c8p78n7...'
public: false
# コールバックのURL
# ここでは auth2c というコマンド用のコールバックURLを設定している
redirect_uris:
- 'http://localhost:9876/callback'
scopes:
- 'openid'
- 'groups'
- 'email'
- 'profile'
grant_types:
- 'authorization_code'
- 'implicit'
response_types:
- 'code'
- 'id_token'
- 'code id_token'
authorization_policy: 'one_factor'
require_pkce: true
pkce_challenge_method: 'S256'
JWKsで使用する秘密鍵の生成
identity_providers.oidc.jwks.*.key
に指定する秘密鍵はopenssl genrsa
コマンドで生成します。
openssl genrsa -out authelia/secrets/oidc_key.pem 4096
クライアントシークレットのハッシュ値の生成
identity_providers.oidc.clients.*.client_secret
に指定する値は、autheliaに内蔵されているコマンドで生成できます。
authelia crypto hash generate pbkdf2
# Dockerの場合はこんな感じ
docker run -it --rm authelia/authelia:4.38.9 authelia crypto hash generate pbkdf2
実行すると対話的に値の入力を促されるので、クライアントシークレットの平文を入力します。
確認の入力を促されるので再度入力すると、ハッシュ値が得られます。
> authelia crypto hash generate pbkdf2
Enter Password:
Confirm Password:
Digest: $pbkdf2-sha512$310000$8uMYYde8u...
ユーザーデータベースファイルの作成
Autheliaが管理するユーザーデータ(要するにログインユーザー)を用意します。
今回は開発用に固定のユーザーを何人か用意できればよいので、YAMLファイルで静的に管理することにします(LDAPを利用することも出来ます)。
フォーマットは次のような感じです(公式マニュアルはこちら)。
users:
ユーザーID:
disabled: 無効化するかどうか
displayname: 表示名
password: パスワードのハッシュ値
email: メールアドレス
groups:
- 所属グループ
- ...
users:
sandbox:
disabled: false
displayname: "user1"
password: "$argon2id$v=19$m=65536,t=3,p=4$hsJou2WZ..."
email: "user1@example.com"
groups:
- "sandbox"
ユーザーパスワードの生成
パスワードの部分はクライアントシークレットの時と同じように、autheliaのコマンドで生成できます。
アルゴリズムの部分は設定ファイルで指定したアルゴリズムと合わせてください。
authelia crypto hash generate argon2
# Dockerの場合はこんな感じ
docker run -it --rm authelia/authelia:4.38.9 authelia crypto hash generate argon2
Autheliaの起動
ここまで準備できましたら、後はDockerで起動するだけです。
docker compose up -d
動作確認
無事に起動できたなら、早速動作確認を行ってみましょう。
動作確認にはoauth2cというCLIツールが便利です。
macOSの場合は brew install cloudentity/tap/oauth2c
でインストールできます。
次のコマンドを実行すると、ブラウザが開いてAutheliaのログイン画面が表示されるはずです。
oauth2c http://127.0.0.1:9091 \
--client-id authelia_sandbox_client \
--response-types id_token \
--response-mode form_post \
--grant-type implicit \
--scopes openid,email
ユーザーデータベースファイルに定義したユーザーでログインしてOIDCのリクエストに同意すると、コンソールにID Tokenとフローの途中経過のステータスが表示されているはずです。
おわりに
Autheliaを使うことで手軽に開発用のIdPを立ち上げることができ、無事にOIDCログイン機能も完成しました🎉
最初は設定ファイルが膨大で若干怯みましたが、全体像が掴めるようになってくると、わりとすんなり設定できるようになります。
起動も速いので、試行錯誤もそれほど苦にはなりませんでした。
また、oauth2cコマンドはOAuth2/OIDCの動作確認にとても便利でした。
わざわざサンプルアプリ等を作らなくてもコマンド一発で動作確認できるのは良いですね。
最近はOIDC連携によるログインも多くなってきましたし、今後の開発でも活用していきたいと思っています。