OAuth 제공자
규칙
- 공개 클라이언트(SPA, 모바일)에서는 Authorization Code + PKCE 흐름 사용 — Implicit 흐름 사용 금지
- 웹 앱의 경우 서버 측에서 코드 교환 — 클라이언트 시크릿이 서버를 벗어나지 않음
- State 매개변수: 무작위
state를 생성하여 세션/쿠키에 저장하고, 콜백 시 확인 — CSRF 방지
- Google OAuth: 범위
openid email profile, 사용자 정보에 id_token 사용
- GitHub OAuth: 범위
read:user user:email, https://github.com/login/oauth/access_token에서 코드 교환
- Discord OAuth: 범위
identify email, 토큰 엔드포인트 https://discord.com/api/oauth2/token
- 토큰 갱신:
refresh_token을 서버 측에 저장하고, access_token 만료 전에 갱신
- 세션 관리: OAuth 콜백 성공 시 앱 세션을 생성하고, OAuth 토큰을 쿠키에 저장하지 않음
- 계정 연동:
accounts 테이블을 통해 사용자가 여러 제공자를 하나의 계정에 연결할 수 있도록 허용
패턴
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const code = searchParams.get("code");
const state = searchParams.get("state");
if (state !== cookies().get("oauth_state")?.value) {
return new Response("유효하지 않은 state", { status: 400 });
}
const tokens = await exchangeCodeForTokens(code);
const userInfo = await fetchUserInfo(tokens.access_token);
const user = await upsertUser(userInfo);
await createSession(user.id);
return redirect("/dashboard");
}
주의 사항
- 클라이언트 시크릿을 프론트엔드 코드에 저장하지 않음 — 항상 서버 측에서 토큰 교환
state 매개변수 생략 금지 — CSRF 공격은 실제 위협임
- 원시 OAuth 토큰을 쿠키에 저장하지 않음 — 서버 측 세션 사용
- 제공자 연결 시 "해당 이메일을 가진 사용자가 이미 존재함" 케이스를 처리하는 것 잊지 않기