OAuth Direct Setup - knpy/yuka-app GitHub Wiki
NextAuth.jsを使わずに、Google OAuthを直接実装する場合の設定手順。
- Google Cloud Consoleにアクセス
- 新しいプロジェクトを作成、または既存プロジェクトを選択
- 「APIs & Services」→「Library」
- 以下のAPIを有効化:
- Google+ API
- Google Calendar API
- People API
- 「APIs & Services」→「Credentials」
- 「+ CREATE CREDENTIALS」→「OAuth 2.0 Client IDs」
- Application type: Web application
- Authorized JavaScript origins:
http://localhost:3000 https://your-domain.com
- Authorized redirect URIs:
http://localhost:3000/auth/callback https://your-domain.com/auth/callback
- 「OAuth consent screen」タブ
- User Type: External
- App information:
- App name: yuka-app
- User support email: [email protected]
- Scopes:
../auth/userinfo.email ../auth/userinfo.profile ../auth/calendar.readonly
GOOGLE_CLIENT_ID=your_client_id
GOOGLE_REDIRECT_URI=http://localhost:3000/auth/callback
const initiateGoogleAuth = () => {
const clientId = process.env.GOOGLE_CLIENT_ID;
const redirectUri = process.env.GOOGLE_REDIRECT_URI;
const scope = [
'openid',
'email',
'profile',
'https://www.googleapis.com/auth/calendar.readonly'
].join(' ');
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?` +
`client_id=${clientId}&` +
`redirect_uri=${encodeURIComponent(redirectUri)}&` +
`response_type=code&` +
`scope=${encodeURIComponent(scope)}&` +
`access_type=offline&` +
`prompt=consent`;
window.location.href = authUrl;
};
// pages/auth/callback.tsx
import { useEffect } from 'react';
import { useRouter } from 'next/router';
export default function AuthCallback() {
const router = useRouter();
useEffect(() => {
const { code } = router.query;
if (code) {
exchangeCodeForTokens(code as string);
}
}, [router.query]);
const exchangeCodeForTokens = async (code: string) => {
try {
const response = await fetch('/api/auth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code })
});
const tokens = await response.json();
// トークンを安全に保存
localStorage.setItem('accessToken', tokens.access_token);
router.push('/dashboard');
} catch (error) {
console.error('Token exchange failed:', error);
}
};
return <div>認証中...</div>;
}
// pages/api/auth/token.ts
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
const { code } = req.body;
try {
const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: process.env.GOOGLE_CLIENT_ID,
client_secret: process.env.GOOGLE_CLIENT_SECRET,
code,
grant_type: 'authorization_code',
redirect_uri: process.env.GOOGLE_REDIRECT_URI
})
});
const tokens = await tokenResponse.json();
res.json(tokens);
} catch (error) {
res.status(500).json({ error: 'Token exchange failed' });
}
}
// pages/api/calendar/events.ts
export default async function handler(req, res) {
const { authorization } = req.headers;
if (!authorization) {
return res.status(401).json({ error: 'Authorization required' });
}
const accessToken = authorization.replace('Bearer ', '');
try {
const calendarResponse = await fetch(
'https://www.googleapis.com/calendar/v3/calendars/primary/events',
{
headers: { Authorization: `Bearer ${accessToken}` }
}
);
const events = await calendarResponse.json();
res.json(events);
} catch (error) {
res.status(500).json({ error: 'Calendar API call failed' });
}
}
- アクセストークンは適切に暗号化して保存
- リフレッシュトークンを使用して自動更新
- トークンの有効期限チェック
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/api/:path*',
headers: [
{ key: 'Access-Control-Allow-Origin', value: 'your-domain.com' },
{ key: 'Access-Control-Allow-Methods', value: 'GET,POST,OPTIONS' }
]
}
];
}
};
- クライアントサイドに秘密情報を露出しない
- HTTPS通信の強制
- CSRFトークンの実装
-
Invalid redirect URI
- Google Cloud Consoleで正確なURIを設定
-
Access denied
- OAuth consent screenの設定確認
- 必要なスコープの設定確認
-
Token expired
- リフレッシュトークンの実装
- トークン更新ロジックの追加