Featured image of post Dify Console APIをcURLで呼び出してみた(v1.9.2 CSRF Token対応)

Dify Console APIをcURLで呼び出してみた(v1.9.2 CSRF Token対応)

Cookieおいしい!

まとめ

  • セルフホスト版Dify(Community)で、非公開?のコンソールAPIがある
  • v1.9.2から、ログイン時に取得できるトークンの扱いが変わった
    • トークンをこれまでのレスポンスボディでなくcookieに入れて返してくるのでそのまま使うのが良い
# login API: https://github.com/langgenius/dify/blob/1.9.2/api/controllers/console/auth/login.py#L44
email="hogehoge@uzimihsr.com" # Difyアカウントのメールアドレス
password="xxxxxxxx"           # Difyアカウントのパスワード
base_url="http://localhost"   # localhost以外で立ててる場合はそれに合わせて変更
curl -c cookie -X POST "${base_url}/console/api/login" -H 'Content-Type: application/json' -d "{\"email\":\"${email}\",\"password\":\"${password}\"}"
csrf_token=$(grep csrf_token cookie | cut -f7)

# profile API: https://github.com/langgenius/dify/blob/1.9.2/api/controllers/console/workspace/account.py#L97
curl -sSf -X GET -b cookie -H "X-CSRF-Token: ${csrf_token}" "${base_url}/console/api/account/profile"

環境

経緯

Difyの管理用APIであるConsole APIは公式ドキュメントで公開されていないもののなかなかに便利で、
ユーザー、ワークスペース、モデルプロバイダー、ツール、アプリなどの管理をUIでなくAPIで実行できるのでDify運用の自動化に使えそう。

一応GitHubのリポジトリを確認すると、このあたりで各種APIの中身が確認できる。
https://github.com/langgenius/dify/tree/main/api/controllers/console

実際に使われている偉大な先駆者様達の例↓

ところが、
最近セルフホスト版Difyのバージョンを最新の1.9.2に上げたところlogin API(POST /console/api/login)でAPIを呼ぶためのトークンが取れなくなってしまった。
(偉大な先駆者様達のコードも1.9.2ではおそらく動かなくなっている)

$ # login API: https://github.com/langgenius/dify/blob/1.9.2/api/controllers/console/auth/login.py#L44
$ # アカウントのメールアドレス(email)とパスワード(password)をJSONでPOSTするとログインできる
$ email="hogehoge@uzimihsr.com" # Difyアカウントのメールアドレス
$ password="xxxxxxxx"           # Difyアカウントのパスワード
$ base_url="http://localhost"   # localhost以外で立ててる場合はそれに合わせて変更
$ curl -X POST "${base_url}/console/api/login" -H 'Content-Type: application/json' -d "{\"email\":\"${email}\",\"password\":\"${password}\"}"
{"result":"success"}
$ # レスポンスボディにアクセストークンが入ってない!

困ったのでちょっと調べてみた。

ソースコード調査

結論から言うとこれ。

v1.9.1まではlogin APIがアクセストークンをレスポンスボディ(.data.access_token)で返していたのが、
セキュリティ向上を目的にHttpOnly Cookieに埋め込んで返すようになったらしい。

curlの-vオプションで実際にCookieの中身を確認してみる。

$ curl -v -X POST "${base_url}/console/api/login" -H 'Content-Type: application/json' -d "{\"email\":\"${email}\",\"password\":\"${password}\"}"
Note: Unnecessary use of -X or --request, POST is already inferred.
* Host localhost:80 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:80...
* Connected to localhost (::1) port 80
> POST /console/api/login HTTP/1.1
> Host: localhost
> User-Agent: curl/8.7.1
> Accept: */*
> Content-Type: application/json
> Content-Length: 52
>
* upload completely sent off: 52 bytes
< HTTP/1.1 200 OK
< Server: nginx/1.23.1
< Date: Sat, 25 Oct 2025 07:30:12 GMT
< Content-Type: application/json
< Content-Length: 20
< Connection: keep-alive
< Set-Cookie: access_token=eyJhxxxxxxxxxxxx.eyJ1c2xxxxxxxxx.VzvJKdUxxxxxxxx; Expires=Sat, 25 Oct 2025 08:30:12 GMT; Max-Age=3600; HttpOnly; Path=/; SameSite=Lax
< Set-Cookie: refresh_token=a4ac94922exxxxxxxxxx; Expires=Mon, 24 Nov 2025 07:30:12 GMT; Max-Age=2592000; HttpOnly; Path=/; SameSite=Lax
< Set-Cookie: csrf_token=eyJhbxxxxxxxx.eyJleHAixxxxxxx.uSNFW3Fxxxxxxxx; Expires=Sat, 25 Oct 2025 08:30:12 GMT; Max-Age=3600; Path=/; SameSite=Lax
< X-Version: 1.9.2
< X-Env: PRODUCTION
<
* Connection #0 to host localhost left intact
{"result":"success"}

たしかにHttpOnlyなCookie(access_token, refresh_token)にトークンが埋め込まれている。

うまくいかない

じゃあCookieを使えばいいじゃん、ということで-cオプションでそのままCookieを保存して投げてみる。

$ # cookieをファイルに保存
$ curl -c cookie -X POST "${base_url}/console/api/login" -H 'Content-Type: application/json' -d "{\"email\":\"${email}\",\"password\":\"${password}\"}"
{"result":"success"}

$ # profile API: https://github.com/langgenius/dify/blob/1.9.2/api/controllers/console/workspace/account.py#L97
$ # 自分のアカウント情報を確認できる
$ curl -X GET -b cookie "${base_url}/console/api/account/profile"
{"code":"unauthorized","message":"CSRF token is missing or invalid.","status":401}
$ # が、失敗...

だめ。

再挑戦

トークン周辺のコードをもう少し調べる。

ということでもう一度試してみる。
Cookieを直接触ってしまっているが、
上記の通りcsrf_tokenに限りHttpOnlyじゃないのでこれは実際のUIの挙動と同じはず。

$ # cookieをファイルに保存
$ curl -c cookie -X POST "${base_url}/console/api/login" -H 'Content-Type: application/json' -d "{\"email\":\"${email}\",\"password\":\"${password}\"}"
{"result":"success"}

# cookieファイルの中のcsrf_token(not HttpOnly)の値を取得
$ csrf_token=$(grep csrf_token cookie | cut -f7)

$ # profile API: https://github.com/langgenius/dify/blob/1.9.2/api/controllers/console/workspace/account.py#L97
$ # 今度はX-CSRF-Tokenを添えて...
$ curl -sSf -X GET -b cookie -H "X-CSRF-Token: ${csrf_token}" "${base_url}/console/api/account/profile" | jq
{
  "id": "932a9004-b67c-422d-b7aa-89b11d8e04b5",
  "name": "hogehoge",
  "avatar": null,
  "avatar_url": null,
  "email": "hogehoge@uzimihsr.com",
  "is_password_set": true,
  "interface_language": "en-US",
  "interface_theme": "light",
  "timezone": "America/New_York",
  "last_login_at": 1761380052,
  "last_login_ip": "172.30.0.1",
  "created_at": 1758760919
}
$ # できた!

やったぜ。
DifyのConsole APIを呼び出して、ユーザー情報を取得することができた。

おわり

うまいこと使って、
Difyで作ったアプリだけじゃなくてDify自体の管理も自動化できたらいいな〜。
(というか公式でConsole API SDK出ないかな…)

おまけ

毛布大好きねこ