全体設計の方針
OAuth 認証を Chrome と Brave の両方で機能させるために、以下の設計方針を採用しました:
- ブラウザ互換性の優先: 異なるブラウザでも一貫して動作する認証フローの設計
- フォールバックメカニズム: 複数の認証方法を順次試行する仕組み
- 適切な OAuth クライアントタイプの選択: ブラウザ拡張機能での互換性を考慮
- 詳細なエラーハンドリングとデバッグ: 問題の早期発見と解決
- ユーザー体験の向上: 認証プロセスの透明性と使いやすさ
主要な技術用語と機能解説
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スキーム
- Chrome拡張機能:
-
重要性:
- Google Cloud Consoleで設定したリダイレクトURIと実際に使用するURIが完全に一致する必要がある
- 不一致があると「redirect_uri_mismatch」エラーが発生
アクセストークン
- 機能: APIリクエストの認証に使用される一時的な認証情報。
-
取得方法:
-
chrome.identity.getAuthToken
: トークンを直接返す -
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)にフォールバックする.
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認証の実装
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)
認証フロー
-
ユーザーが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は、ユーザーに認証画面を表示することを指定
-
ユーザーが権限を承認
この部分はGoogleの認証画面でユーザーが行う操作なので、コード上には直接表示されません。ユーザーがGoogleの画面で「許可」ボタンをクリックすることに対応します。 -
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にアクセストークンを付加してリダイレクト
-
拡張機能がリダイレクト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設定の重要性
"oauth2": {
"client_id": "ウェブアプリケーション用のクライアントID", // ← authenticateWithWebFlow関数内のclientIdと一致させる
"scopes": ["https://www.googleapis.com/auth/drive.file", ...] // ← authenticateWithWebFlow関数内のscopeと一致させる
}
設定の意味:
- client_id: OAuth認証に使用するクライアントID
- scopes: 拡張機能が要求するアクセス権限の範囲
Google Cloud Consoleでの設定:
- OAuthクライアントIDの作成時に「ウェブアプリケーション」タイプを選択
- 承認済みリダイレクトURIにhttps://<拡張機能ID>.chromiumapp.org/を追加
- クライアントIDとクライアントシークレットを取得
Chrome拡張機能IDの取得と重要性:
- 開発者モードでchrome://extensionsから確認可能
- リダイレクトURIの構築に使用され、正確に一致する必要がある
まとめ
+-------------+ +------------------+ +------------------+
| | 1. 認証リクエスト | | 2. 認証画面表示 | |
| 拡張機能 | ---------------> | chrome.identity | ---------------> | Google認証サーバー|
| | | | | |
+-------------+ +------------------+ +------------------+
^ |
| |
| 4. トークン抽出 | 3. リダイレクト
| (redirectUrlからアクセストークン取得) | (トークン付きURL)
| v
+-------------+ +-----------------+ +----------------+
| | 5. トークン返却 | | リダイレクト処理 | |
| Promise解決 | <--------------- | コールバック関数 | <--------------- | リダイレクトURI |
| | | | | |
+-------------+ +-----------------+ +----------------+
コードとの対応:
- 認証リクエスト: chrome.identity.launchWebAuthFlow({url: authUrl, ...})
- 認証画面表示: Googleの認証画面(コード上には表示されない)
- リダイレクト: redirect_uri=${encodeURIComponent(redirectUri)}で指定したURIへ
- トークン抽出: const params = new URLSearchParams(new URL(redirectUrl).hash.substring(1))
- トークン返却: resolve(accessToken)でPromiseを解決
この認証フローにより、拡張機能はユーザーの許可を得てGoogleのサービス(Drive API等)にアクセスするためのトークンを安全に取得できます。