One Server

문서

로그인 붙이기

이메일·비밀번호와 소셜 로그인. 토큰이 어디로 돌아오는지.

로그인 붙이기

이메일과 비밀번호

POST /auth/v1/signup          {"email":"...","password":"..."}
POST /auth/v1/token?grant_type=password   {"email":"...","password":"..."}
GET  /auth/v1/user            Authorization: Bearer <access_token>
POST /auth/v1/logout

비밀번호는 10자 이상이어야 하고, 유출된 적이 있는 비밀번호는 거부돼요.

소셜 로그인

열한 곳을 지원해요: 구글, 카카오, 네이버, 애플, GitHub, GitLab, Microsoft, Facebook, LINE, Discord, X. list_auth_providers가 설정한 것과 아직 안 한 것을 모두 돌려줘요.

설정은 도구로 끝나요. 콘솔에 들어가지 않아도 돼요.

list_auth_providers   지금 상태와 콜백 주소
set_auth_provider     클라이언트 ID·시크릿 저장하고 켜기
set_auth_provider_enabled   자격증명은 두고 껐다 켜기
remove_auth_provider  자격증명째로 지우기
list_redirect_urls    로그인 후 돌아갈 수 있는 주소
add_redirect_url      돌아갈 주소 허용
remove_redirect_url   돌아갈 주소 제거

순서가 중요해요. 제공자 콘솔에 콜백 주소를 먼저 등록하세요. list_auth_providers의 callback_url이 그 주소예요. 한 글자만 달라도 제공자가 로그인을 거부하고, 그 오류에는 무엇이 틀렸는지 나오지 않아요.

그리고 돌아갈 주소를 등록하지 않으면 로그인은 되는데 아무 데도 가지 않아요. add_redirect_url로 넣어요. 스킴·호스트·포트가 모두 같아야 하고 경로는 보지 않아요. https://내앱/done을 넣으면 https://내앱이 저장되고 그 아래 어느 경로로든 돌아올 수 있어요.

이런 증상이면 list_redirect_urls부터 보세요. 대개 여기 없는 주소예요. 개발용 http://localhost 같은 것은 배포 전에 remove_redirect_url로 빼세요.

GET /auth/v1/authorize?provider=kakao&redirect_to=https://내앱/done

끝나면 redirect_to로 돌아오고, 토큰은 주소의 # 뒤에 실려 와요(질의 문자열이 아니에요).

const token = new URLSearchParams(location.hash.slice(1)).get('access_token')

직접 등록하는 제공자 (custom:)

위 열한 곳 말고도 OAuth 2.0 / OpenID Connect 제공자를 custom:<이름>으로 직접 등록할 수 있어요(Supabase와 같은 이름). 기본 카카오 칸을 다른 앱이 쓰고 있으면 custom:kakao-xts처럼 두 번째 카카오 앱도 둘 수 있어요.

set_auth_provider  provider: "custom:kakao-xts", client_id, client_secret,
                   authorize_url, token_url, userinfo_url (또는 issuer만),
                   scopes, claim_map: {"id":"id","email":"kakao_account.email",
                   "email_verified":"kakao_account.is_email_verified","phone":"kakao_account.phone_number"}
  • 주소는 공개된 https만 돼요. claim_map(처음 이름 claims도 받아요)을 비우면 OpenID의 표준 클레임(sub·email·email_verified·name·picture·phone_number)을 읽어요.
  • provider_type: "oidc"면 id_token을 issuer가 공개한 키(jwks_uri, 비우면 issuer의 설정 문서에서 찾아요)로 서명·issuer·대상(client_id)·만료·nonce까지 확인한 뒤에 읽어요. 앱이 직접 받은 id_token으로 signInWithIdToken({ provider: 'custom:<이름>', token })도 돼요. 빼면 oauth2(기본)로, claim_map으로 읽어요.
  • token_auth는 시크릿을 본문(body, 기본)으로 보낼지 HTTP Basic(basic)으로 보낼지, pkce는 기본 켜짐이에요. email_optional: true면 이메일 없는 사용자도 받아요.
  • 콘솔의 로그인 / 제공자 화면의 사용자 정의 제공자, list_custom_providers·create_custom_provider(Supabase 모양), set_auth_provider, CLI는 모두 같은 제공자를 보고 바꿔요. 요금제마다 둘 수 있는 수가 정해져 있어요(Free 3, Pro 10).
  • 앱에서는 그대로 supabase.auth.signInWithOAuth({ provider: 'custom:kakao-xts', options: { scopes: 'phone_number' } })이에요. scopes는 등록한 scope에 더해져요.
  • 세션에 제공자의 토큰이 provider_token(·provider_refresh_token)으로 한 번 실려 와요. 그것으로 제공자 API(예: 카카오 전화번호)를 불러요. 이 플랫폼은 PKCE 교환이 끝날 때까지만(최대 10분) 암호화해 두었다가 지워요.
  • 기존 계정에는 제공자가 이메일을 확인했다고 할 때만 이어 붙여요(email_verified 매핑).
  • /auth/v1/settings에는 Supabase처럼 나오지 않아요. CLI: oneend auth providers set custom:<이름> ....

이메일이 없는 계정

카카오, 네이버, GitHub, GitLab, Facebook, LINE, X는 이메일 없이 돌아올 수 있어요. 이메일을 꼭 요구하는 코드는 한국에서 대부분의 로그인을 막아요.

기존 계정에 이어 붙이는 것도 제공자가 이메일을 확인했다고 할 때만 해요. 확인 안 된 이메일로 이어 붙이면 남의 이메일을 적은 사람이 그 계정을 가져가요.

익명 로그인

set_anonymous_sign_in으로 켜면 이메일 없이 바로 세션을 받을 수 있어요. 지금 상태는 list_auth_providers의 anonymous_sign_in이에요. 익명 사용자도 <ref>_authenticated 역할이라 접근 규칙에서 따로 구분하려면 토큰의 is_anonymous를 보세요.

문자 인증번호 로그인

전화번호로 가입하고 로그인하려면 문자 계정이 있어야 해요. get_sms_settings로 지금 설정을 보고 set_sms_settings로 정해요. 직접 계약한 Solapi(CoolSMS), NHN Cloud, Aligo, Twilio 계정을 쓰거나, 유료 요금제라면 플랫폼 계정(platform, 문자 1건 ₩100, 국내만)을 골라요. 지우려면 remove_sms_settings이에요.

await supabase.auth.signInWithOtp({ phone: '010-1234-5678' })
await supabase.auth.verifyOtp({ phone: '010-1234-5678', token: '123456', type: 'sms' })

번호는 GoTrue처럼 국가 번호부터 숫자만(821012345678) 저장하고, 010으로 시작하면 한국 번호로 읽어요. 한 번호에는 60초에 한 번, 1시간에 5번, 한 IP 주소에서는 1시간에 20번까지 보내요. 틀린 인증번호를 5번 넣으면 그 인증번호는 못 써요. 너무 자주 요청하면 over_sms_send_rate_limit(429)로 거부돼요. allowed_countries는 기본으로 82만이에요. 해외 유료 번호로 문자를 보내게 하는 사기가 흔하니 쓰지 않는 나라는 넣지 마세요.

인증 메일: 보내는 주소(SMTP)와 템플릿

가입 확인·비밀번호 재설정·로그인 링크·이메일 변경·초대 메일은 기본으로 플랫폼 발신 주소에서, 요청 언어(한국어·영어·일본어)의 기본 문구로 나가요. 내 메일 주소로 보내려면 set_smtp_settings(Supabase의 SMTP Settings와 같은 항목: sender_email, sender_name, host, port, username, password, minimum_interval_seconds)로 정하고 test_smtp_settings로 시험 메일을 보내 봐요. get_smtp_settings는 비밀번호의 뒤 네 자리만 보여 줘요. 그 서버가 거부하면 플랫폼 주소로 대신 보내고 last_error에 남겨요. 지우려면 remove_smtp_settings이에요. 한 시간 한도(rate_limit_per_hour)나 같은 주소 간격을 넘으면 over_email_send_rate_limit(429)로 거부돼요.

문구는 get_email_templates로 보고 set_email_template로 종류마다(confirmation, invite, magic_link, recovery, email_change, reauthentication) 제목과 HTML 본문을 정해요. Supabase에서 쓰던 템플릿을 그대로 붙여 넣을 수 있어요: {{ .ConfirmationURL }}, {{ .Token }}, {{ .TokenHash }}, {{ .SiteURL }}, {{ .Email }}, {{ .NewEmail }}, {{ .RedirectTo }}, {{ .Data.<key> }}. 저장 전에 preview_email_template로 확인하고, 기본 문구로 돌리려면 remove_email_template이에요. {{ .SiteURL }}은 첫 번째 리디렉트 주소의 출처예요. reauthentication은 아직 보내지 않아요.

await supabase.auth.signUp({ email: '[email protected]', password: '…' })
await supabaseAdmin.auth.admin.inviteUserByEmail('[email protected]', { redirectTo: 'https://shop.example/welcome' })

SAML SSO

회사의 IdP(Okta, Microsoft Entra ID, Google Workspace 등)로 로그인하게 해요. list_sso_providers가 IdP에 넣을 acs_url과 entity_id를 알려 줘요. add_sso_provider에 IdP의 메타데이터(주소나 XML)와 이메일 도메인을 주면 등록되고, update_sso_provider로 바꾸고 remove_sso_provider로 지워요. 새 IdP는 엔터프라이즈 요금제에서만 추가할 수 있어요(list_sso_providers의 new_providers_allowed). 이미 등록한 IdP는 요금제와 상관없이 그대로 동작하고 바꿀 수 있어요.

const { data } = await supabase.auth.signInWithSSO({ domain: 'example.com' })
location.href = data.url

응답이나 어설션 중 하나 이상이 IdP 인증서로 서명돼 있어야 하고, 이 프로젝트가 보낸 요청에 대한 응답만 받아요. IdP에서 먼저 시작하는 로그인은 받지 않아요. SSO로 처음 들어온 사람은 새 계정이 되며, 같은 이메일의 기존 계정과 합쳐지지 않아요.

로그인 설정 한 번에 (로그인 / 제공자)

콘솔의 로그인 / 제공자 화면과 같은 설정을 Supabase의 auth config 키로 다뤄요. get_auth_config가 전체를 소문자 키로 돌려주고(external_google_client_id, sms_provider, saml_enabled 등), update_auth_config가 준 키만 바꿔요. 켠 제공자에 필요한 값이 빠졌거나 형식이 틀리면 아무것도 저장하지 않고 키마다 이유를 돌려줘요. 비밀 값은 ••••abcd처럼 뒤 네 자리만 보이며, 그대로 돌려주면 바뀌지 않아요. 직접 정의한 OAuth·OIDC 제공자는 list_custom_providers, create_custom_provider, update_custom_provider, set_custom_provider_enabled, remove_custom_provider로, Firebase·Clerk·WorkOS·Auth0·Cognito 토큰은 list_third_party_auth, add_third_party_auth, remove_third_party_auth로 다뤄요.

{ "external_kakao_enabled": true, "external_kakao_client_id": "REST_API_KEY", "external_kakao_secret": "CLIENT_SECRET",
  "external_kakao_scopes": "profile_nickname,account_email,gender", "external_kakao_email_optional": true }

카카오 동의 항목(external_kakao_scopes)은 그대로 scope로 보내고, 받은 값은 사용자의 user_metadata에 저장해요. 네이버는 받을 정보를 네이버 개발자센터에서 정하니 external_naver_scopes는 저장할 항목을 골라요.

가입자 지우기

remove_user { ref, id, confirm }가 가입자 한 명을 영구히 지워요. id는 list_users가 돌려준 UUID이고, confirm에는 그 가입자의 이메일(또는 id)을 그대로 넣어요. 되돌릴 수 없으니 사용자가 분명히 요청했을 때만 쓰세요. 지운 기록은 감사 기록(access)에 남아요.

가입자 한꺼번에 가져오기

import_users { ref, users: [{email, id?, encrypted_password?, email_confirmed?, user_metadata?, app_metadata?}], dry_run? }가 다른 서비스(Supabase auth.users 내보내기 등)의 가입자를 한 번에 500명까지 가져와요. id와 bcrypt 해시($2a$·$2b$)를 그대로 지켜서 비밀번호를 다시 정하지 않아도 로그인돼요. 평문 비밀번호는 받지 않아요. 목록 전체를 먼저 검사해 틀린 줄 번호를 알려 주고 하나도 가져오지 않아요(dry_run: true는 검사만). 이미 있는 계정(같은 id·이메일)은 건너뛰고 skipped로 세니 같은 호출을 다시 해도 안전해요. REST는 POST /v1/projects/<ref>/users/import {users, dry_run}이에요.

가입자 바꾸기와 로그인 링크

update_user { ref, id, ... }가 가입자 한 명의 이메일, 비밀번호(password), 이메일 확인(email_confirm), 정지(ban_duration, 예: 24h, 풀 때는 none), 메타데이터를 바꿔요. 비밀번호를 바꾸거나 정지하면 그 사람의 로그인이 모두 끊겨요. 비밀번호 규칙은 가입할 때와 같아요. generate_link는 초대(invite), 비밀번호 없는 로그인(magiclink), 비밀번호 재설정(recovery), 가입 확인(signup) 링크를 메일 없이 만들어 action_link로 돌려줘요. 그 링크를 연 사람은 그 가입자로 로그인되니 요청한 사람에게만 전하세요. redirect_to는 add_redirect_url로 등록한 주소만 돼요. REST로는 PATCH /v1/projects/<ref>/users/<id>와 POST /v1/projects/<ref>/users/<이메일>/links {type, password, data, redirect_to}이에요.

표에서 지금 로그인한 사람

auth_<ref>.uid()가 사용자 ID를 돌려줘요. 접근 규칙 안에서 써요.

패스키, 2단계 인증, OAuth 앱

get_passkey_settings     가입자의 패스키 로그인 설정
set_passkey_settings     켜기·끄기, RP 이름, RP ID, 허용 출처
list_user_factors        가입자 한 명의 2단계 인증 수단과 패스키
remove_user_factors      2단계 인증 모두 지우기 (confirm에 이메일)
remove_user_passkey      패스키 하나 지우기
list_oauth_apps          이 프로젝트로 로그인하는 다른 앱과 주소
create_oauth_app         앱 등록 (client_secret은 한 번만 보임)
reset_oauth_app_secret   시크릿 새로 만들기
remove_oauth_app         앱 등록 지우기 (confirm에 앱 이름)

OAuth 서버를 켜고 끄는 것, 가입 허용, 정지는 update_auth_config(OAUTH_SERVER_ENABLED, DISABLE_SIGNUP)와 update_user(ban_duration)로 해요. 비밀번호 재설정 메일 대신 generate_link의 recovery 링크를 직접 전해요.