0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Chrome と Brave で動作する OAuth 認証の設計と実装解説

Last updated at Posted at 2025-04-06

全体設計の方針

OAuth 認証を Chrome と Brave の両方で機能させるために、以下の設計方針を採用しました:

  1. ブラウザ互換性の優先: 異なるブラウザでも一貫して動作する認証フローの設計
  2. フォールバックメカニズム: 複数の認証方法を順次試行する仕組み
  3. 適切な OAuth クライアントタイプの選択: ブラウザ拡張機能での互換性を考慮
  4. 詳細なエラーハンドリングとデバッグ: 問題の早期発見と解決
  5. ユーザー体験の向上: 認証プロセスの透明性と使いやすさ

主要な技術用語と機能解説

OAuth 2.0

  • 機能: ユーザーがサードパーティアプリケーションに対して、自分のデータへのアクセス権を付与するための標準プロトコル。
  • 役割: Google Drive APIなどのサービスに安全にアクセスするための認証・認可の仕組みを提供します。

chrome.identity API

  • 機能: Chrome拡張機能がOAuth認証を行うためのChrome専用API。
  • 主要メソッド:
    • chrome.identity.getAuthToken: Chrome拡張機能向けの簡易OAuth認証。内部でトークン管理も行う。
    • chrome.identity.launchWebAuthFlow: より汎用的なOAuth認証フロー。リダイレクトURIを使った標準的なOAuthフローを実行。
    • chrome.identity.getRedirectURL(): 拡張機能のリダイレクトURIを取得。

OAuth クライアントタイプ

Chrome拡張機能タイプ:

  • Chrome専用に最適化されたOAuthクライアント
  • Braveなど他のChromiumベースブラウザでは互換性の問題が発生することがある

ウェブアプリケーションタイプ:

  • より汎用的なOAuthクライアント
  • 標準的なOAuthフローを使用するため、ブラウザ間の互換性が高い
  • 今回の解決策で採用したタイプ

リダイレクトURI

機能: OAuth認証完了後、認証サーバーがユーザーをリダイレクトするURI。

  • 形式:
    • Chrome拡張機能: https://<拡張機能ID>.chromiumapp.org/
    • この形式はChrome拡張機能専用のリダイレクトURIスキーム
  • 重要性:
    • Google Cloud Consoleで設定したリダイレクトURIと実際に使用するURIが完全に一致する必要がある
    • 不一致があると「redirect_uri_mismatch」エラーが発生

アクセストークン

  • 機能: APIリクエストの認証に使用される一時的な認証情報。
  • 取得方法:
    1. chrome.identity.getAuthToken: トークンを直接返す
    2. launchWebAuthFlow: リダイレクトURLのハッシュフラグメントからトークンを抽出する必要がある
  • 使用方法: APIリクエストのAuthorizationヘッダーにBearer <token>形式で設定

実装の詳細解説

フォールバック認証メカニズム

chromeとbraveの拡張機能では扱えるOAuthの認証に使えるものが異なる.認証方式としては以下の二通り

  • chrome.identity.getAuthToken : Chromeで最も効率的.Braveでは問題が発生する可能性がある.
  • chrome.identity.launchWebAuthFlow: より標準的なOAuthフロー.互換性が高い.

それぞれに応じて用いる認証方法を変更する.
そこで,Chromeの認証(chrome.identity.getAuthToken)から試し,失敗したらより標準的な方法(chrome.identity.launchWebAuthFlow)にフォールバックする.

oauth.js
export function getGoogleAuthToken() {
  // 方法1: chrome.identity.getAuthToken を試す
  // 失敗したら方法2: authenticateWithWebFlow にフォールバック

  return new Promise((resolve, reject) => {
    // 方法1: chrome.identity.getAuthToken を試す
    chrome.identity.getAuthToken({ interactive: true }, function(token) {
      if (chrome.runtime.lastError) { // 失敗したとき
        // 方法2: launchWebAuthFlow にフォールバック
        authenticateWithWebFlow() // launchWebAuthFlowを含む自作関数へ
          .then(resolve)
          .catch(reject);
        return;
      }
      resolve(token);
    });
  });
}

WebFlow認証の実装

oauth.js
function authenticateWithWebFlow() {
  return new Promise((resolve, reject) => {
    const clientId = '「ウェブアプリケーション」タイプのOAuthのクライアントID'; // 必要に応じて更新
    const redirectUri = chrome.identity.getRedirectURL();
    
    const scope = [
      'https://www.googleapis.com/auth/drive.file',
      'https://www.googleapis.com/auth/drive.appdata',
      'https://www.googleapis.com/auth/drive.appfolder'
    ].join(' ');

    const authUrl = `https://accounts.google.com/o/oauth2/auth` +
      `?client_id=${clientId}` +
      `&response_type=token` +
      `&redirect_uri=${encodeURIComponent(redirectUri)}` +
      `&scope=${encodeURIComponent(scope)}`;
    
    chrome.identity.launchWebAuthFlow(
      {
        url: authUrl,
        interactive: true
      },
      function(redirectUrl) {
        if (chrome.runtime.lastError) {
          reject(chrome.runtime.lastError);
          return;
        }
        
        try {
          const params = new URLSearchParams(new URL(redirectUrl).hash.substring(1));
          const accessToken = params.get('access_token');
          if (accessToken) {
            resolve(accessToken);
          } else {
            reject(new Error('Access token not found in redirect URL'));
          }
        } catch (e) {
          reject(e);
        }
      }
    );
  });
}

パラメータ

  • client_id: Google Cloud Consoleで作成したOAuthクライアントID
  • redirect_uri: 認証後のリダイレクト先URI(chrome.identity.getRedirectURL()で取得)
  • response_type: token(暗黙的フロー用)
  • scope: アクセス権限の範囲(例:https://www.googleapis.com/auth/drive.file)

認証フロー

  1. ユーザーがGoogleの認証画面にリダイレクトされる

    // 認証URLの構築
    const authUrl = `https://accounts.google.com/o/oauth2/auth` +
      `?client_id=${clientId}` +
      `&response_type=token` +
      `&redirect_uri=${encodeURIComponent(redirectUri)}` +
      `&scope=${encodeURIComponent(scope)}`;
    
    // 認証画面を表示(ユーザーをリダイレクト)
    chrome.identity.launchWebAuthFlow(
      {
        url: authUrl,    // ← この認証URLにユーザーがリダイレクトされる
        interactive: true // ← ユーザー操作を必要とする対話型認証
      },
      function(redirectUrl) { ... }
    );
    
    • authUrlの構築で、Google認証サーバーのURLとパラメータを設定
    • chrome.identity.launchWebAuthFlowが実際にユーザーをこのURLにリダイレクト
    • interactive: trueは、ユーザーに認証画面を表示することを指定
  2. ユーザーが権限を承認
    この部分はGoogleの認証画面でユーザーが行う操作なので、コード上には直接表示されません。ユーザーがGoogleの画面で「許可」ボタンをクリックすることに対応します。

  3. Googleが指定されたリダイレクトURIにユーザーを戻す

    // リダイレクトURIの設定
    const redirectUri = chrome.identity.getRedirectURL();  // ← Googleがユーザーを戻す先のURI
    
    // 認証URLにリダイレクトURIを含める
    const authUrl = `https://accounts.google.com/o/oauth2/auth` +
      // ...
      `&redirect_uri=${encodeURIComponent(redirectUri)}` +  // ← ここでリダイレクト先を指定
      // ...
    
    • chrome.identity.getRedirectURL()で拡張機能専用のリダイレクトURIを取得
    • このURIは通常 https://<拡張機能ID>.chromiumapp.org/ の形式
    • Google認証サーバーは認証完了後、このURIにアクセストークンを付加してリダイレクト
  4. 拡張機能がリダイレクトURLからトークンを抽出

    chrome.identity.launchWebAuthFlow(
      { /* ... */ },
      function(redirectUrl) {  // ← Googleからリダイレクトされたときに呼ばれるコールバック
        if (chrome.runtime.lastError) {
          reject(chrome.runtime.lastError);
          return;
        }
        
        try {
          // リダイレクトURLからトークンを抽出
          const params = new URLSearchParams(new URL(redirectUrl).hash.substring(1));  // ← URLのハッシュフラグメントを解析
          const accessToken = params.get('access_token');  // ← アクセストークンを取得
          if (accessToken) {
            resolve(accessToken);  // ← 成功時にトークンを返す
          } else {
            reject(new Error('Access token not found in redirect URL'));
          }
        } catch (e) {
          reject(e);
        }
      }
    );
    
    • redirectUrlパラメータにGoogleからリダイレクトされたURLが含まれる
    • new URL(redirectUrl).hashでURLのハッシュフラグメント部分(#以降)を取得
    • .substring(1)で先頭の#記号を除去
    • new URLSearchParams(...)でクエリパラメータとして解析
    • params.get('access_token')でアクセストークンの値を取得

OAuth設定の重要性

manifest.json
"oauth2": {
  "client_id": "ウェブアプリケーション用のクライアントID", //  authenticateWithWebFlow関数内のclientIdと一致させる
  "scopes": ["https://www.googleapis.com/auth/drive.file", ...] //  authenticateWithWebFlow関数内のscopeと一致させる
}

設定の意味:

  • client_id: OAuth認証に使用するクライアントID
  • scopes: 拡張機能が要求するアクセス権限の範囲

Google Cloud Consoleでの設定:

  1. OAuthクライアントIDの作成時に「ウェブアプリケーション」タイプを選択
  2. 承認済みリダイレクトURIにhttps://<拡張機能ID>.chromiumapp.org/を追加
  3. クライアントIDとクライアントシークレットを取得

Chrome拡張機能IDの取得と重要性:

  • 開発者モードでchrome://extensionsから確認可能
  • リダイレクトURIの構築に使用され、正確に一致する必要がある

まとめ

視覚的な認証フロー図とコードの対応
+-------------+                  +------------------+                  +------------------+
|             | 1. 認証リクエスト |                  |  2. 認証画面表示  |                  |
| 拡張機能    | ---------------> | chrome.identity  | ---------------> | Google認証サーバー|
|             |                  |                  |                  |                  |
+-------------+                  +------------------+                  +------------------+
      ^                                                                        |
      |                                                                        |
      | 4. トークン抽出                                                         | 3. リダイレクト
      | (redirectUrlからアクセストークン取得)                                    | (トークン付きURL)
      |                                                                        v
+-------------+                  +-----------------+                  +----------------+
|             |  5. トークン返却  |                 |  リダイレクト処理  |                |
| Promise解決 | <--------------- | コールバック関数  | <--------------- | リダイレクトURI |
|             |                  |                 |                  |                |
+-------------+                  +-----------------+                  +----------------+

コードとの対応:

  1. 認証リクエスト: chrome.identity.launchWebAuthFlow({url: authUrl, ...})
  2. 認証画面表示: Googleの認証画面(コード上には表示されない)
  3. リダイレクト: redirect_uri=${encodeURIComponent(redirectUri)}で指定したURIへ
  4. トークン抽出: const params = new URLSearchParams(new URL(redirectUrl).hash.substring(1))
  5. トークン返却: resolve(accessToken)でPromiseを解決

この認証フローにより、拡張機能はユーザーの許可を得てGoogleのサービス(Drive API等)にアクセスするためのトークンを安全に取得できます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?