link-based-auth について
link-based-auth は、ログイン画面なしで個別アクセスを提供するための認証パターンです。トークン付きリンク、エッジでの検証、セッション Cookie を使う構成とセキュリティ上の制約を説明します。
主な機能
- 01トークン付き個別リンク
- 02エッジでのトークン検証
- 03セッション Cookie 認証
- 04訪問者ごとの ID 対応
- 05ローカルでのモックデータ介入
活用シーン
- 01採用担当者別のポートフォリオ表示
- 02招待制デモアプリへのアクセス
- 03顧客別ドキュメントの表示
- 04個別ビューのローカルテスト
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ loxosceles-dev/ dev-skills --skill link-based-authインストール後、Claude Code で/link-based-authを入力して読み込まれたか確認します。
詳細
リンクベース認証
これは参考パターンです。 アプローチを参考にし、状況に合わせて応用してください。そのままコピーしないでください。
問題:ログイン画面のわずらわしさを伴わずに、パーソナライズされた体験を提供する必要があるアプリケーションがあります。ポートフォリオサイト、デモアプリ、招待制アクセスなどが該当します。
解決策:パーソナライズされたURLに認証トークンを埋め込みます。エッジ関数がトークンを検証し、セッション Cookie を設定することで、訪問者はシームレスに認証された状態で利用できます。
パターン
フロー:
1. Admin generates personalized link with embedded token
2. Visitor clicks link
3. CDN edge function intercepts request
4. Edge function validates token, sets auth cookie
5. Frontend loads with authentication already established
6. API requests use cookie/token for authorization
アーキテクチャ:
Personalized Link (contains token)
↓
CDN Edge Function
├── Valid token → Set cookie, redirect to app
└── No/invalid token → Serve public view
↓
Static Frontend (reads cookie for auth state)
↓
API (validates token from Authorization header)
主要コンポーネント:
- リンクジェネレーター — トークンを埋め込んだ URL を作成し、それぞれに訪問者のメタデータを関連付けます
- ID バックエンド — トークンを仮想ユーザー ID にマッピングします(例:パスワードなしで作成された Cognito ユーザー)
- エッジ認証関数 — CDN のエッジでトークンを検証し、セッション Cookie を設定します
- 認証コンテキスト — Cookie からトークンを抽出し、コンポーネントに認証状態を提供するフロントエンドのコンテキストです
このパターンを使う理由
メリット:
- 手間いらず:ログイン画面も、パスワードも、サインアップも不要です
- パーソナライズ:各リンクが特定の訪問者 ID に対応します
- セキュア:トークンはサーバー側で検証され、Cookie は HttpOnly/Secure です
- 改ざん防止:URL を操作しても効果はありません。認証はバックエンドの ID に紐付いています
ユースケース:
- 採用担当者向けの個別ビューを備えたポートフォリオサイト
- 招待制アクセスのデモアプリケーション
- パーソナライズされたコンテンツを制限付きで提供するマーケティングサイト
- 顧客ごとのセクションを備えたドキュメント
実装
エッジ認証
// Edge function (Lambda@Edge, CloudFlare Worker, etc.)
export async function handler(event) {
const request = event.Records[0].cf.request;
const params = new URLSearchParams(request.querystring);
const token = params.get('token');
if (token) {
const isValid = await validateToken(token);
if (isValid) {
return {
status: '302',
headers: {
'location': [{ value: '/' }],
'set-cookie': [{ value: `auth=${token}; Secure; HttpOnly; SameSite=Strict` }],
},
};
}
}
// Check existing session cookie
const cookies = request.headers.cookie?.[0]?.value || '';
if (cookies.includes('auth=')) {
return request; // Authenticated, proceed
}
return request; // Unauthenticated, serve public view
}
フロントエンド認証コンテキスト
// lib/auth/auth-context.tsx
export function useAuth() {
const tokens = extractTokensFromCookies();
const environment = detectEnvironment();
const getAuthHeaders = (routeType: 'public' | 'protected') => {
if (environment === 'local') {
return { 'x-api-key': process.env.NEXT_PUBLIC_API_KEY };
}
if (routeType === 'public') {
return { 'x-api-key': process.env.NEXT_PUBLIC_API_KEY };
}
if (tokens.accessToken) {
return { Authorization: `Bearer ${tokens.accessToken}` };
}
return {};
};
return {
isAuthenticated: !!tokens.accessToken,
environment,
getAuthHeaders,
};
}
フックベースのデータ取得
// lib/profile/use-profile.ts
export function useProfile() {
const { isAuthenticated, getAuthHeaders } = useAuth();
const { data, loading } = useQuery(GET_PROFILE, {
skip: !isAuthenticated,
context: { headers: getAuthHeaders('protected') },
});
return { profile: data?.profile, isLoading: loading };
}
セキュリティに関する考慮事項
- Cookie フラグ:
Secure、HttpOnly、SameSite=Strictにより、XSS と CSRF を防止します - トークンの有効期限:有効期間の短いトークンで、漏えいの可能性がある時間を制限します
- コンテンツの分離:各トークンは特定の ID に対応するため、訪問者には自分のコンテンツだけが表示されます
- クライアント側にシークレットを置かない:トークンはサーバー側で検証され、フロントエンドは検証結果のみを読み取ります
- URL への直接アクセス:有効なトークン/Cookie がなければ、設計上、公開コンテンツのみが表示されます
ローカル開発
実際のトークンを使わずにローカル開発を行う場合は、環境に応じて動作するインターセプターを使用してください。
export function useLocalInterceptor() {
const { environment } = useAuth();
const visitorId = useSearchParams()?.get('visitor');
const shouldIntercept = environment === 'local' && !!visitorId;
return {
shouldIntercept,
getMockData: () => ({
name: `Test Visitor ${visitorId}`,
message: 'Mock data for local development',
}),
};
}
コンポーネントはまずインターセプターを確認し、次に実際のデータにフォールバックします。
function PersonalizedContent() {
const interceptor = useLocalInterceptor();
const { data } = useRealData();
const content = interceptor.shouldIntercept
? interceptor.getMockData()
: data;
return <div>{content.message}</div>;
}
使用しない場合
- 機密データ:このパターンはパーソナライズ用であり、特に機密性の高い情報を保護するためのものではありません
- 長期間有効なセッション:トークンベースのリンクは短時間のやり取りに適しており、継続的に利用するアカウントには向いていません
- 複雑な認証フロー:MFA、パスワードリセット、ロール管理が必要な場合は、フル機能の認証プロバイダーを使用してください
関連パターン
- 静的フロントエンドのホスティング — この認証パターンが動作するホスティングインフラストラクチャ
- 環境デプロイ戦略 — 環境が認証設定に与える影響
継続的な改善
開発者が、このスキルで防ぐべきだった動作を修正した場合は、今後同じ修正が発生しないよう、このスキルに加える具体的な変更を提案してください。
コメント
Markdown 対応