LovableとSupabaseでLineWorksでログインを試行錯誤した話

# Supabase Edge Functions と Lovable 環境での LineWorks 認証実装 ― 分割構成への検討

この記事では、Lovable（フロントエンド）と Supabase（バックエンド）を組み合わせ、LineWorks の OAuth 認証を実装する際の各要素とその課題、そして解決策として Edge Function を分割して実装した試行錯誤について解説します。

*まだ完成しておらず、とりあえず一旦これで行ってみるという話です。*

---

## 1. 背景と目的

- **環境構成:**
  - フロントエンド：https://example-collection.lovable.app/auth
  - バックエンド：Supabase Edge Functions（例: https://samplefunction.functions.supabase.co/get-authcode）
  - 認証プロバイダー：LineWorks（OAuth 2.0 を利用）

- **目的:**
  - Lovable と Supabase を組み合わせた環境下で、LineWorks を用いたログイン認証(OAuth)を実現する。  
  - クライアント ID は公開しても問題ないが、Client Secret は秘匿が必要なため、Edge Functions 内で安全に取り扱う。  
  - Edge Function を利用することで、サーバーサイドで Secret を隠蔽しつつ、認可コード取得とアクセストークンの発行を行う。
  - Implicit Flowは嫌！

### 先に結論
- EdgeFunctionは機能ごとに小さく分割する
- LineWorksで認証するので、ANON KEYによる認証は切る

---

## 2. OAuth 認証フローにおけるセキュリティ上の注意点

- **Client ID と Client Secret:**
  - Client ID は公開情報として問題ないが、Client Secret はブラウザに露出してはいけない。  
  → Edge Functions で認証フローを処理することで、Secret をサーバーサイドに留める。

- **OAuth フロー:**
  - Implicit Flow はアクセストークンが直接ブラウザに返るためリスクがあるため、認可コードグラントフロー（可能なら PKCE）を利用する。  
  → 認可コードを受け取り、サーバー側でトークン交換を行う。

---

## 3. 初期実装：単一の Edge Function での認可コード取得とトークン発行

最初は 1 つの Edge Function にて以下の処理を実装しました。
以下の２つの大きな機能を1つのEdgeFunctionで実装していました。

1. **認可コード取得:**
   - フロントエンドからボタンを押すと、Edge Function が呼び出され、LineWorks の認可エンドポイント（[https://auth.worksmobile.com/oauth2/v2.0/authorize](https://auth.worksmobile.com/oauth2/v2.0/authorize)）へリダイレクトします。  
   - ここで、client_id、redirect_uri、scope、response_type=code、state といったパラメータが付与されます。

2. **アクセストークン発行:**
   - LineWorks 側でユーザー認証が成功すると、認可コードが Edge Function の redirect_uri に付与されて戻ります。  
   - Edge Function はそのコードを用いてアクセストークンを取得します。

---

## 4. 問題点：リダイレクト時の Authorization ヘッダーと CORS の問題

- **ブラウザのリダイレクトとヘッダーつけられない問題:**
  - LineWorksからのレスポンスはリダイレクトになります。通常のリダイレクト（HTTP 302）はブラウザ側で自動処理され、カスタムヘッダー（例えば Authorization ヘッダー）は付与されません。  
  → そのため、Edge Function が「Authorization ヘッダー必須」の状態になっている場合、認可コード取得後のリダイレクト時にエラーが発生しました。

- **CORS の問題:**
  - `fetch` を用いてリダイレクト処理を行おうとすると、LineWorks の認可エンドポイントが CORS 対応していないためエラーになるという課題もありました。

- **アクセストークンの返却先がフロントエンド問題**
    EdgeFunctionにリダイレクトすると、最後にアクセストークンを画面に返したいとなった場合に「リクエストに対してレスポンスを返す」ことによる受け渡しができない。(EdgeFunctionに画面を介さずリダイレクトしてしまったため)
    Cookieをつけて画面のURLに遷移させたり、Queryにつけて新たに画面にアクセスしないといけない
```mermaid
sequenceDiagram
    participant 画面
    participant EdgeFunction
    participant LineWorks

    LineWorks -->> EdgeFunction: リダイレクト
    EdgeFunction ->> LineWorks: アクセストークン取得リクエスト（認可コード利用）
    LineWorks -->> EdgeFunction: アクセストークン返却
    Note over EdgeFunction,画面: ここの受け渡しが美しくない
    EdgeFunction -->> 画面: アクセストークンを返却
```

---

## 5. 解決策：Edge Function の分割構成

これらの課題を解決するため、Edge Function を以下の 2 つに分割する構成を検討しました。
ユーザ情報を取得するものも含めると３つです。
(OAuth2.0じゃなくてOIDC使えばよかったな・・・・)

```mermaid
sequenceDiagram
    participant 画面
    participant EdgeFunction_認可コード取得 as Edge Function(認可コード取得)
    participant EdgeFunction_トークン取得 as Edge Function(トークン取得)
    participant EdgeFunction_ユーザ情報取得 as Edge Function(ユーザ情報取得)
    participant LineWorks

    画面->>EdgeFunction_認可コード取得: 実行
    EdgeFunction_認可コード取得->>LineWorks: 認可コード取得リクエスト
    LineWorks-->>画面: 認可コード返却

    画面->>EdgeFunction_トークン取得: トークン取得リクエスト（認可コード渡す）
    EdgeFunction_トークン取得->>LineWorks: トークン発行リクエスト
    LineWorks-->>EdgeFunction_トークン取得: アクセストークン返却
    EdgeFunction_トークン取得-->>画面: アクセストークンを返却

    画面->>EdgeFunction_ユーザ情報取得: ユーザ情報取得リクエスト（アクセストークン渡す）
    EdgeFunction_ユーザ情報取得->>LineWorks: ユーザ情報リクエスト
    LineWorks-->>EdgeFunction_ユーザ情報取得: ユーザ情報返却
    EdgeFunction_ユーザ情報取得-->>画面: ユーザ情報を返却
```

### (1) 認可コード取得用 Edge Function

- **役割:**
  - ユーザーが「LineWorksでログイン」ボタンを押した際に実行され、認可エンドポイントへアクセス、最終的にはLovableのフロントエンドにリダイレクトします。

- **特徴:**
  - 認可コードを取得し、画面にリダイレクトする  
  - EdgeFunctionではなくフロントエンドにリダイレクトすることで、リクエストの起点とレスポンスの終点を揃える。

### (2) アクセストークン発行用 Edge Function

- **役割:**
  - フロントエンド側で、URL に含まれる `code` と `state` を検出した場合に自動実行され、認可コードを受け取ってアクセストークンを発行します。

- **特徴:**
  - この関数では、認可コードからアクセストークンを取得するために、必要なリクエストを LineWorks のトークンエンドポイントに送信します。  
  - ここで、Edge Function 内でデフォルトの anon key を使用するか、JWT 検証を無効にして（`--no-verify-jwt` オプション）処理を進めることが検討されます。  
  - 結果として、アクセストークンがフロントエンドに返却され、フロントエンドはそのトークンを用いて LineWorks の API を呼び出し、認証を完了させます。
  ※ただし、このLineWorksのAPI実行もフロントエンドから直接はできないので、また別のEdgeFunctionを使うことになります。

---

## 6. フロントエンドと連携する流れ

1. **ログインボタン押下:**
   - フロントエンド（[https://example-collection.lovable.app/auth](https://example-collection.lovable.app/auth)）で「LineWorksでログイン」ボタンを押すと、ユーザーは認可コード取得用の Edge Function にGETリクエスト。

2. **認可コードの受け取り:**
   - ユーザーが LineWorks で認証・同意すると、LineWorks は認可コードを付与して、事前に設定したリダイレクトURI（画面）に戻ります。

3. **アクセストークン発行:**
   - 画面は URL のクエリから `code` と `state` を検出すると、自動的にアクセストークン発行用の Edge Function を呼び出す仕組みを実装します。  
     - Edge Function は認可コードからアクセストークンを取得し、EdgeFunctionのレスポンスとしてアクセストークンを画面に返します。

4. **認証完了:**
   - フロントエンドは受け取ったアクセストークンを利用して LineWorks の API を呼び出し、認証が成功した状態を確立します。

---

## 7. まとめ

- **目的:**
  - Lovable + Supabase 環境下で、LineWorks を利用した認証を実装する。

- **技術的なチャレンジ:**
  - Client Secret を秘匿するため、Supabase Edge Functions を活用。  
  - 単一の Edge Function で認可コード取得とアクセストークン発行を実装し、EdgeFunctionにリダイレクトすると Authorization ヘッダーが付かずエラーが発生。  

- **解決策:**
  - Edge Function を 2 つに分割し、1 つは認可コード取得専用、もう 1 つはアクセストークン発行専用とする。
  - 画面はcodeとstateがあるかどうかで処理を分ける。(URL に `code` と `state` が含まれる場合に自動的にアクセストークン発行用 Edge Function を呼び出す処理を実装)  

一旦これで進めてみて、何か不都合等があれば更新します！