TL;DR
Supabase는 네이버 로그인을 공식 지원하지 않는다. Custom OIDC Provider 기능을 써도 네이버의 비표준 userinfo 응답 포맷 때문에 Error getting user email from external provider 에러가 발생한다. Edge Function을 userinfo 프록시로 두어 응답을 표준 OIDC 포맷으로 변환하면 해결된다.
오픈소스로 공개했다: supabase-naver-oidc-proxy
배경
Translator Pro는 PDF 번역 서비스인데, 인증을 Supabase Auth로 처리하고 있다. 기존에는 Google OAuth만 지원하다가 한국 사용자를 위해 네이버 로그인을 추가하기로 했다.
카카오는 Supabase가 built-in으로 지원하지만, 네이버는 안 한다. GitHub에 feature request도 올라와 있지만 아직 미해결 상태다.
시도 1: Custom OIDC Provider (Auto-discovery)
네이버가 OIDC를 지원한다는 걸 알게 됐다. Discovery endpoint도 있다:
https://nid.naver.com/.well-known/openid-configurationSupabase에 최근 추가된 Custom Provider 기능으로 간단히 될 줄 알았다.
설정
Supabase 대시보드 → Authentication → Custom Providers:
- Configuration Method: Auto-discovery
- Issuer URL:
https://nid.naver.com - Scopes:
openid
결과: 실패
error: server_error
error_code: unexpected_failure
error_description: Error getting user email from external provider왜 안 될까?
원인을 찾는 데 시간이 좀 걸렸다. 개발자 도구 Network 탭에서 Supabase 콜백 요청의 Payload를 확인해서 에러 메시지 전문을 볼 수 있었다.
핵심은 네이버의 userinfo 응답이 OIDC 표준과 다르다는 것이다.
Supabase가 기대하는 형식 (OIDC 표준):
{
"sub": "abc123",
"email": "user@example.com",
"email_verified": true,
"name": "홍길동"
}네이버가 실제로 반환하는 형식:
{
"resultcode": "00",
"message": "success",
"response": {
"id": "abc123",
"email": "user@example.com",
"name": "홍길동"
}
}사용자 정보가 response 객체 안에 감싸져 있어서 Supabase가 email을 찾지 못한다.
시도 2: Edge Function으로 userinfo 프록시
Supabase Auth가 userinfo를 호출할 때 네이버 API를 직접 호출하는 대신, Edge Function을 중간에 두어 응답 포맷을 변환하기로 했다.
Supabase Auth
├→ Authorization URL → 네이버 (직접)
├→ Token URL → 네이버 (직접)
└→ Userinfo URL → Edge Function (프록시)
└→ 네이버 /v1/nid/me 호출
└→ response 객체 언래핑
└→ 표준 OIDC 포맷으로 반환Edge Function 코드
전체 코드는 60줄도 안 된다:
const NAVER_USERINFO_URL = "https://openapi.naver.com/v1/nid/me";
Deno.serve(async (req: Request) => {
const authorization = req.headers.get("Authorization");
if (!authorization) {
return Response.json({ error: "Missing Authorization header" }, { status: 401 });
}
const naverResponse = await fetch(NAVER_USERINFO_URL, {
headers: { Authorization: authorization },
});
if (!naverResponse.ok) {
return Response.json({ error: "Failed to fetch user info from Naver" }, { status: naverResponse.status });
}
const data = await naverResponse.json();
const profile = data.response;
return Response.json({
sub: profile.id,
email: profile.email,
email_verified: true,
name: profile.name,
nickname: profile.nickname,
picture: profile.profile_image,
});
});배포:
supabase functions deploy naver-userinfo --no-verify-jwt--no-verify-jwt가 필요한 이유: 이 함수는 Supabase JWT가 아니라 네이버 access_token을 Authorization 헤더로 받는다. Supabase의 JWT 검증을 끄지 않으면 네이버 토큰이 거부된다.
Supabase 설정 (Manual configuration)
Auto-discovery 대신 Manual configuration으로 전환하고 userinfo URL만 Edge Function으로 지정한다:
| Field | Value |
|---|---|
| Configuration Method | Manual configuration |
| Issuer URL | https://nid.naver.com |
| Authorization URL | https://nid.naver.com/oauth2/authorize |
| Token URL | https://nid.naver.com/oauth2/token |
| Userinfo URL | https://<project>.supabase.co/functions/v1/naver-userinfo |
| JWKS URI | https://nid.naver.com/oauth2/jwks |
| Scopes | profile |
삽질 포인트 3가지
실제로 동작시키기까지 몇 가지 함정이 더 있었다.
1. Scopes는 반드시 profile만
처음에 openid를 포함했더니 Supabase가 id_token에서 사용자 정보를 추출하려고 시도하고, userinfo 엔드포인트를 아예 호출하지 않았다. Edge Function Invocations에 호출 기록이 0건이었다.
openid을 빼고 profile만 남기면 Supabase가 id_token 대신 userinfo 엔드포인트를 사용한다.
2. 네이버 개발자센터에서 이메일 "필수 동의" 설정
네이버 개발자센터 → API 권한관리에서 이메일을 "필수 동의"로 설정하지 않으면, 네이버 API가 이메일을 반환하지 않는다. Edge Function이 정상 동작해도 email 필드가 비어 있어서 Supabase가 동일한 에러를 내뱉는다.
3. --no-verify-jwt 필수
Edge Function 배포 시 이 플래그를 빼먹으면, Supabase Edge Function 런타임이 네이버 access_token을 Supabase JWT로 검증하려다 401을 반환한다.
클라이언트 코드
로그인 버튼에서 호출하는 코드는 간단하다:
const { error } = await supabase.auth.signInWithOAuth({
// Supabase SDK 타입이 custom provider를 아직 지원하지 않아 타입 단언 필요
provider: "custom:naver" as "google",
options: {
redirectTo: `${window.location.origin}/auth/callback`,
},
});네이버 BI 가이드라인에 따라 버튼은 녹색 배경(#03A94D)에 흰색 N 로고를 사용해야 한다.
오픈소스
같은 문제를 겪을 한국 개발자를 위해 오픈소스로 공개했다. Edge Function 코드와 상세 설정 가이드가 포함되어 있다.
GitHub: supabase-naver-oidc-proxy
마무리
Supabase Custom Provider는 OIDC 표준을 따르는 provider에는 잘 작동하지만, 네이버처럼 비표준 응답을 반환하는 경우 Edge Function 프록시가 필요하다. 60줄짜리 함수 하나로 해결할 수 있지만, "어디서 실패하는지" 찾는 게 가장 오래 걸렸다.
카카오는 Supabase built-in 지원이니, 한국 서비스라면 카카오 + 네이버(이 방법) 조합으로 대부분의 사용자를 커버할 수 있다.