IDサーバのOSS、ORY Kratosを試してみる

[Digital Identity技術勉強会 #iddance](https://qiita.com/advent-calendar/2021/iddance) 7日目の記事です。

ID Stackを手掛けるORYの最新作、ORY Kratosを触ってみたので、どんな感じで使えそうか書いていきます。

なお、執筆時点でのバージョンは v0.8.0-alpha.3 です。

# ORY Kratosとは

ユーザ管理と認証を実装しているヘッドレスなIDサーバのOSSです。

[https://github.com/ory/kratos](https://github.com/ory/kratos)

実装されている主な機能としては以下です。

- ログイン
- ログアウト
- ユーザ登録
- プロフィール管理
- アカウントリカバリー
- Email検証
- MFA

UIはご自分で、というものなので、最小構成としては以下のようになります。Your Appと書いた部分はサーバサイド・ブラウザ・ネイティブアプリケーションです。Cookieを共有するため、KratosとYour Appはトップレベルドメインは同じである必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83325/f6507841-4588-f6ea-f8fb-c2a1b2fa8ef4.png)


前段にプロキシを挟んであげると、同じドメインできますし、 認証認可等、全く保護されていないAdmin APIを叩きたいときに [ORY Oathkeeper](https://github.com/ory/oathkeeper) のようなIAPを使えば、認可をつけることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83325/f80c4ea6-027e-c7b9-6684-7ca8eb948dd2.png)

# ログインやユーザ登録などFormの流れ

Kratosに送信するためのUIを自分で提供する都合上、Formを表示するための実装が結構面倒です。どのFormも大体同じ流れです。

1. User AgentがYour Appにアクセス。
2. Your AppはORY Kratosにリダイレクト。
3. KratosはFlow IDをURLパラメータにつけてYour Appにリダイレクトで戻す。
4. Your AppはFlow IDを使ってKratosのAPIを叩く。APIレスポンスを元にFormを組み立てる。メッセージやデフォルト値などもKratosのAPIレスポンス指示に書いてある。
5. ユーザがフォームを入力し、Kratosに送信。
6. 送信が成功したらリダイレクト。リダイレクト先は設定もしくは、Step 2のときにURLパラメータで指定された値。エラーがあれば、Step 2に戻る。

ここでは簡易的に書きましたが、[公式ドキュメント](https://www.ory.sh/kratos/docs/self-service/)ではもっと詳しくシーケンス図で説明されています。

大まかな流れ自体は[ORY Hydra](https://github.com/ory/hydra)を使ったことある人であれば、ちょっと似ているので馴染みがあると思いますが、ここで面食らうのは「Kratosの支持通りにFormを組み立てる」ところです。

通常、ログインやユーザー登録などはフォームごとに実装することが多いと思いますが、Kratosの場合は、Kratosの指示（JSON）から自サービスのUIデザインのFormを組み立てるしくみを実装する必要があります。

実装サンプルは[こちら](https://github.com/ory/kratos-selfservice-ui-node/tree/master/views)。サンプルのpartialsディレクトリの中の実装を自サービスのデザインに合わせて実装するイメージです。ただ、このサンプルの通りに実装すると、ViewにKratosの複雑性を持ち込んでしまうことになるので、フロントエンド・サーバサイドで分業するなら、サーバサイドでKratosのUI指示を変換することを検討したほうがいいかもしれません。

# 各機能の特徴

## ユーザ登録・プロフィール管理

ユーザ登録やプロフィール設定では、ユーザのスキーマを予め定義しておき、その定義に従ってユーザ登録・プロフィール変更のフォームの項目が生成されます。スキーマは１つだけでなく、複数定義することができます。例えばEmailとパスワードだけのスキーマを定義する場合は以下のようになります。

```json
{
  "$id": "http://mydomain.com/schemas/v2/customer.schema.json",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "A customer (v2)",
  "type": "object",
  "properties": {
    "traits": {
      "type": "object",
      "properties": {
        "email": {
          "title": "E-Mail",
          "type": "string",
          "format": "email",

          // This tells Ory Kratos that the field should be used as the "username" for the username+password flow.
          // It is an extension to the regular JSON Schema vocabulary.
          "ory.sh/kratos": {
            "credentials": {
              "password": {
                "identifier": true
              }
            }
          }
        }
      }
    }
  }
}
```

注意が必要なのは、ここで定義したものはすべてユーザ自身が入力できてしまうことです。システム側で管理したいユーザに値を触られたくないプロパティは今のところ、Kratosに持たせることはできないので、それを実現するにはYour App側で管理することになります。

一応、issueは上がっていますが、実装優先順位的にすぐに実装されなさそうなので、早く欲しい場合はコントリビュートが必要です。

[https://github.com/ory/kratos/issues/47](https://github.com/ory/kratos/issues/47)

## ログイン

KratosではPasswordを使ったログインとOpenID Connectを使用したソーシャルサインインに対応しています。OpenID Connectといえば、ORYにはOpenID Providerを実装したORY Hydraがありますが、Hydraとのシームレスな連携は未実装で、連携できるようにYour Appに実装する必要があります。

将来的に実装されるようですが、これも優先度が低く未実装です。とはいえ、この実装はそこまで面倒ではないので、自分で実装してまってもいいかなと思います。

[https://github.com/ory/kratos/issues/273](https://github.com/ory/kratos/issues/273)

### MFA / 2FA

Kratosは TOTP、WebAuthn、Lookup Secretに対応しています。これらはKratosの設定で有効化できます。また、AAL（Authenticator Assurance Level）も対応されており、現状ではAAL1（パスワード or OIDCログイン）もしくはAAL2（パスワード or OIDCログイン + TOTP or WebAuthn or Lookup Secret）を要求できます。ログインのフロー開始時、Kratosにリダイレクトする際にパラメータで `aal=aal2` のように指定することができ、その後、セッションを取得した際にもAALを確認することができます。

```json
{
  ...
  "authenticator_assurance_level": "aal2",
  ...
}
```

[https://www.ory.sh/kratos/docs/reference/api#operation/toSession](https://www.ory.sh/kratos/docs/reference/api#operation/toSession)

## アカウントリカバリー

Emailを入力したら、リカバリーメールが飛び、メール内のリンクをクリックするとセッションが発行され、パスワードを設定できるページに飛びます。将来的にはEmail以外も対応しそうですが、今の時点ではEmailのみです。

[https://www.ory.sh/kratos/docs/guides/account-recovery-password-reset](https://www.ory.sh/kratos/docs/guides/account-recovery-password-reset)

# その他

## 組織アカウントとの相性が悪い

Kratosではユーザが編集できない情報を設定できないので、例えば組織IDを紐づけたり、組織の管理者がアカウントを作成して、Emailは変更されたくないなどの要件があったときは、Your Appで組織IDを持つようにしたり、Emailが更新されないように、KratosにForm送信が届く前にバリデーションをするなどの工夫が必要になります。

また、招待も一応できるものの、ユーザ体験としては微妙です。Admin APIを使ってユーザを作成し、アカウントリカバリーフローを使ってパスワードを設定させるというものですが、Admin APIを使ったユーザ作成時には Kratosがメールを送信してくれないので、自分で送信することになります。このメールをユーザがクリックしてもKratosとしてはEmail Verifiedにならないので、Kratosからどこかのタイミング（リカバリーフロー中もしくはパスワード設定後）もう一度、 メールが送信され、このメール中のリンクを踏むとEmail Verifiedになります。

[https://www.ory.sh/kratos/docs/admin/managing-users-identities](https://www.ory.sh/kratos/docs/admin/managing-users-identities)

以上を踏まえるとこんな感じの構成になります。Proxyから Kratosに直接プロキシされていたリクエストはYour Appを通って、必要に応じてバリデーションなどの追加処理を行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83325/d8ed169f-3a96-c77b-4214-7642e011d074.png)

# まとめ

ヘッドレスIDサーバのOSS、ORY Kratosの紹介をしました。

まだalphaバージョンで、足りない部分はたくさんありますが、それでも自分で一から実装するくらいなら、使ってもいいんじゃないかと思います。