概要
Webサイトに埋め込んだDifyチャットアプリ上でユーザーの LINE認証(LINEログイン)や LINE通知(Messaging API)などのフローを展開したい場合、コールバック処理とPush送信を担う中継サーバーが必要になる。
Google Apps Script(GAS) は公開Webアプリ、スプレッドシート、定期実行、メール送信を無償で一体運用でき、DifyとLINEの間をつなぐ役割を最小構成で果たせる。
この記事ではチャットからLINEログインへ遷移させ、ユーザーのLINE userIdを取得する方法を記録しておく。
※尚、LINEトーク上でチャットを展開する想定ではないため、DifyのLINEプラグインは使わない。
役割分担
- Dify(司令塔): 会話UI、意思決定、HTTP呼び出し。外部からのリダイレクト(OAuthコールバック)を受けてサーバー処理する役割は持たない。
- LINE(認証局/配達網): LINEログインでユーザー本人性を確認し、Messaging APIで特定ユーザーにPush配達。
- GAS(受付/中継): WebアプリURLでLINEログインのコールバックを受け、認可コード→アクセストークン→ユーザーID取得、スプレッドシート保存。定期実行で在庫を監視し、メール/LINEに通知。
ポイント:
- コールバック先はブラウザから直接叩かれる公開エンドポイントが必要
- 受け取ったcodeを裏で交換・保存するサーバー処理が必要
これをDify内に作るのは難しのでGASで行う。
全体アーキテクチャ
- チャット上でユーザーにLINEログインURLを提示
- ユーザーがURLをクリックし、遷移先でLINEログインに同意
- LINEがGASのWebアプリ(doGet)へリダイレクト
- GASがcodeをトークンに交換し、ユーザープロファイルからuserIdを取得
事前準備(最低限)
1) LINE Developers(プロバイダー/チャネル作成)**
- LINEログイン チャネル
- チャネルID / チャネルシークレットを控える
- コールバックURLにGASのWebアプリURL(後述)を登録
- Scope:
profile openid
- Messaging APIは本稿では不要(通知は扱わない)
2) GASプロジェクト
- スプレッドシートを用意(例:
UserMappings
シートにconversation_id(or user_id) / line_user_id / linked_at
) -
Script Propertiesに以下を保存
LINE_LOGIN_CHANNEL_ID
LINE_LOGIN_CHANNEL_SECRET
- Webアプリとしてデプロイ →URLを控えて以下に登録する
- LINEログインチャネルのコールバックURL
- Difyチャットフローの環境変数(後述)
3) Difyチャットフロー
- チャットフローアプリの作成
-
環境変数の作成(スタジオ画面右上のENVアイコンから)
-
line_login_channel_id
: LINEログインのチャネルID -
gas_webapp_url
: GASのウェブアプリURL(※シークレット設定可能)
-
実装
ステップ1:DifyでLINEログインURLを提示
CodeノードでURLを生成して、回答ノードでリンクを出す。stateにはsys.user_id
を使う。
【Codeノード】の設定
-
入力変数:
- client_id:
line_login_channel_id
(環境変数) - redirect_uri:
gas_webapp_url
(環境変数) - user_id:
sys.user_id
(システム変数)
- client_id:
-
Python3コード:
import urllib.parse def main(client_id: str, redirect_uri: str, user_id: str) -> dict: base = "https://access.line.me/oauth2/v2.1/authorize" params = { "response_type": "code", "client_id": client_id, # LINEログインチャネルID "redirect_uri": redirect_uri, # GAS WebアプリURL(コールバック) "state": user_id, # Difyの sys.user_id "scope": "profile openid" } login_url = f"{base}?{urllib.parse.urlencode(params)}" return { "line_login_url": login_url }
-
出力変数:
line_login_url
【回答ノード】
[LINEアカウントを連携する]({{[上のコードノード].line_login_url}})
でリンクを案内
Difyのコードノード(Python) は、入力変数を引数とするmain関数を呼び出す。
戻り値は出力変数名に対応するdictを返す。
ステップ2:GASのdoGetがcodeとstateを受け取る
遷移先画面でユーザーがLINEログインに同意すると、GASのdoGet()
関数がステップ1でログインURLに組み込んだcode
とstate
を受け取る。
const CONF = {
// 事前準備でScript Propertiesに登録した変数を取得
ID: PropertiesService.getScriptProperties().getProperty('LINE_LOGIN_CHANNEL_ID'),
SECRET: PropertiesService.getScriptProperties().getProperty('LINE_LOGIN_CHANNEL_SECRET'),
};
function doGet(e) {
const code = e.parameter.code;
const state = e.parameter.state; // Difyのsys.user_id
if (!code || !state) return HtmlService.createHtmlOutput('invalid request');
const token = exchangeToken_(code); // code→access_token
const prof = fetchProfile_(token.access_token); // userId取得(後述)
return HtmlService.createHtmlOutput('連携が完了しました。チャットに戻ってください。');
}
function exchangeToken_(code) {
const res = UrlFetchApp.fetch('https://api.line.me/oauth2/v2.1/token', {
method: 'post',
payload: {
grant_type: 'authorization_code',
code,
redirect_uri: ScriptApp.getService().getUrl(), // 事前登録と一致させる
client_id: CONF.ID,
client_secret: CONF.SECRET,
},
muteHttpExceptions: true
});
const json = JSON.parse(res.getContentText());
if (json.error) throw new Error(json.error_description || 'token error');
return json;
}
-
redirect_uri
はLINE DevelopersでコールバックURLに登録したURLと完全一致させる -
SpreadsheetApp.flush()
で書き込みの遅延反映を抑止 - 検証用にログ(
console.log
)でstate
とuserId
を記録しておくと便利
ステップ3:プロフィールAPIからLINE IDを取得・保存
fetchProfile_()
を定義。
でプロフィールAPIを呼び出し、userId
を取得する(以後の通知やアカウント連携に利用可能)。
function fetchProfile_(accessToken) {
const res = UrlFetchApp.fetch('https://api.line.me/v2/profile', {
headers: { Authorization: 'Bearer ' + accessToken },
muteHttpExceptions: true
});
const json = JSON.parse(res.getContentText());
if (json.error) throw new Error(json.error_description || 'profile error');
return json; // { userId, displayName, ... }
}
scope=profile openid
を要求しておけば、ログイン同意後のアクセスで一意なuserIdを取得できる。
動作確認
- Dify側:コードノードの出力URLが正しく生成され、stateに想定のIDが入っているか
- LINE側:コールバックURLの完全一致とclient_idの値が正しいか
-
GAS側:実行ログでcode/state受領→トークン交換→プロフィール取得の一連が成功しているか
※GASの実行ログにstate
とuserId
の記録が出ているかチェック