OAuth Providers
Rules
- Use Authorization Code flow with PKCE for public clients (SPAs, mobile) — never Implicit flow
- Server-side code exchange for web apps — the client secret never leaves the server
- State parameter: generate random
state, store in session/cookie, verify on callback — prevents CSRF
- Google OAuth: scopes
openid email profile, use id_token for user info
- GitHub OAuth: scope
read:user user:email, exchange code at https://github.com/login/oauth/access_token
- Discord OAuth: scope
identify email, token endpoint at https://discord.com/api/oauth2/token
- Token refresh: store
refresh_token server-side, refresh before access_token expires
- Session management: create app session on successful OAuth callback, don't store OAuth tokens in cookies
- Link accounts: allow users to connect multiple providers to one account via
accounts table
Patterns
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("Invalid 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");
}
Avoid
- Storing client secrets in frontend code — always exchange tokens server-side
- Skipping the
state parameter — CSRF attacks are real
- Storing raw OAuth tokens in cookies — use server-side sessions
- Forgetting to handle the "user already exists with that email" case when linking providers