소셜 인증 어댑터는 ID 제공업체가 발급한 토큰을 검증해 사용자 레코드와 세션으로 바꾸는 백엔드 구성 요소입니다. 모든 소셜 로그인 설명 글에서 빠져 있는 중간 단계입니다. OAuth 2.0은 클라이언트가 제공업체 토큰을 얻는 방법을 설명하지만, 여전히 백엔드 쪽의 무언가가 그 토큰을 검증하고, 어느 계정의 것인지 결정하고, API가 신뢰하는 세션을 발급해야 합니다. 그 무언가가 바로 어댑터입니다.
핵심 요약
| 질문 | 답변 |
|---|---|
| 정의 | 검증된 제공업체 토큰을 앱의 사용자 + 세션으로 바꾸는 플랫폼 측 계층 |
| vs. 순수 OAuth | OAuth는 프로토콜이고, 어댑터는 백엔드에서 그 결과물을 소비하는 요소 |
| 신원 키 | 제공업체의 안정적인 주체 ID, 제공업체별로 authData에 저장 |
| 담당하는 흐름 | 로그인, 계정 연결(linkWith), 익명에서 식별된 사용자로의 업그레이드 |
| 절대 보관하지 않는 것 | 사용자의 제공업체 비밀번호: 토큰만 받아 서버 측에서 검증 |
동작하는 어댑터
제공업체 토큰이 들어가고 검증된 세션이 나옵니다. 게스트 업그레이드 흐름도 함께 보입니다:
// JavaScript / Node.js — Back4app JS SDK
// The adapter path: provider tokens in, verified session out.
// The platform verifies the token with the provider before any user exists.
const user = await Parse.User.logInWith('apple', {
authData: { id: providerUserId, token: identityToken },
});
// user.authData holds the provider block, keyed on the stable subject ID
// Anonymous → identified: upgrade the guest without losing its objects
const guest = await Parse.AnonymousUtils.logIn();
await guest.linkWith('apple', {
authData: { id: providerUserId, token: identityToken },
}); // Flutter / Dart — Back4app Flutter SDK
// The adapter path: provider tokens in, verified session out
final user = ParseUser.forQuery();
final response = await user.loginWith(
'apple',
apple(identityToken, providerUserId),
);
// First login creates the user; later logins match the same authData
// Anonymous → identified: upgrade the guest without losing its objects
final guest = ParseUser.forQuery();
await guest.loginAnonymous();
await guest.linkWith('apple', apple(identityToken, providerUserId)); // iOS / Swift — Back4app Swift SDK
// The adapter path: provider tokens in, verified session out
let user = try await User.apple.login(
user: providerUserId,
identityToken: tokenData
)
// First login creates the user; later logins match the same authData
// Anonymous → identified: upgrade the guest without losing its objects
let guest = try await User.anonymous.login()
let upgraded = try await guest.apple.link(
user: providerUserId,
identityToken: tokenData
) // Android / Kotlin — Back4app Android SDK
// The adapter path: provider tokens in, verified session out
val authData = mapOf("id" to providerUserId, "token" to identityToken)
ParseUser.logInWithInBackground("apple", authData).continueWith { task ->
val user = task.result // created on first login, matched afterwards
}
// Anonymous → identified: upgrade the guest without losing its objects
ParseAnonymousUtils.logIn { guest, e ->
if (e == null && guest != null) {
guest.linkWithInBackground("apple", authData)
}
} 어댑터가 실제로 하는 일
소셜 로그인에서 클라이언트의 역할은 주요 ID 제공업체가 토큰을 건네주는 순간 끝납니다. 어댑터의 역할은 거기서 시작되며, 모두 서버 측에서 일어납니다:
직접 구현할 때 건너뛰면 위험해지는 단계가 4단계입니다. 어댑터는 제공업체를 호출해 토큰이 진짜이고, 만료되지 않았으며, 이 앱을 위해 발급되었는지 확인합니다. 클라이언트 측 “검증”은 아무것도 증명하지 못합니다. 어떤 요청이든 어떤 신원이든 주장할 수 있기 때문입니다. 5단계는 고전적인 계정 탈취를 막는 계정 연결 규칙을 담고 있습니다. 매칭은 이메일 클레임이 아니라 제공업체의 안정적인 주체 ID를 키로 합니다. 사용자 테이블에 남는 결과는 authData 맵입니다. 연결된 제공업체마다 검증된 블록이 하나씩 있고, 모두 한 명의 사용자를 가리키며, 그 사용자의 객체, ACL, 세션은 비밀번호가 있는 계정과 정확히 똑같이 동작합니다.
인증 어댑터 vs. 직접 구현한 OAuth
| 관심사 | 어댑터 사용 | 직접 구현 |
|---|---|---|
| 토큰 검증 | 플랫폼이 서버 측에서 제공업체를 호출 | 제공업체마다 직접 구현하고 최신으로 유지 |
| 계정 매칭 | 구조적으로 안정적인 주체 ID를 키로 사용 | 직접 만든 스키마, 직접 만든 버그: 이메일을 키로 쓰는 것이 고전적 사례 |
| 계정 연결 | linkWith 호출 한 번 | 커스텀 테이블과 병합 로직 |
| 게스트 업그레이드 | 익명 사용자에 대한 linkWith, 그 자리에서 전환 | 계정 간 수동 데이터 마이그레이션 |
| 세션 발급 | 제공업체와 무관하게 통일된 플랫폼 세션 | JWT 위에 직접 구축 |
| 새 제공업체 | 설정만 하면 됨(또는 커스텀 어댑터 추가) | 또 하나의 OAuth 클라이언트 구현 |
꼭 새겨 둘 점이 있습니다. 어댑터는 OAuth를 대체하지 않습니다. 클라이언트는 여전히 제공업체의 흐름을 실행하고, PKCE도 여전히 중요합니다. 어댑터가 대체하는 것은 토큰이 도착한 이후에 직접 만들어야 했을 모든 것이며, 소셜 로그인 취약점의 대부분은 실제로 그곳에 있습니다.
계정 연결, 그리고 온보딩을 구하는 업그레이드
어댑터 기반 인증을 단순한 “토큰 검증” 엔드포인트와 구분 짓는 두 가지 흐름이 있습니다. 계정 연결: 같은 사람이 서로 다른 버튼으로 들어옵니다. 연결 호출은 두 번째 제공업체의 검증된 블록을 로그인한 사용자에게 붙이므로, 어느 제공업체로든 하나의 계정에 도달하고 제공업체 계정이 잠겨도 고객을 잃지 않습니다. 익명 사용자 업그레이드: 게스트가 곧바로 앱을 쓸 수 있게 하는 앱은 게스트를 익명 사용자로 처리합니다. 사용자가 마침내 로그인하면 연결 호출이 그 익명 사용자를 그 자리에서 식별된 계정으로 전환합니다. 객체 ID가 같으므로 장바구니, 진행 상황, 객체별 권한이 모두 유지됩니다. 이렇게 가입을 미루는 팀은 마지막에 기다리는 마이그레이션 스크립트 없이 가장 마찰이 큰 화면을 없앱니다. 같은 메커니즘이 반대 방향으로는 연결 해제로 동작하며, 사용자는 이를 통해 계정을 잃지 않고 제공업체를 정리합니다. 싱글 사인온 구성에서는 임직원용 제공업체 아래로 신원을 통합할 때 이를 사용합니다.
일반적인 사용 사례
- 주요 ID 제공업체를 통한 소비자 로그인: 표준 버튼들에 대해 검증, 매칭, 세션을 한 번에 통일된 방식으로 처리합니다.
- 게스트 우선 온보딩: 사용자가 가입을 결정하는 순간 익명 사용자를 그 자리에서 업그레이드하며, 데이터 손실이 없습니다.
- 다중 제공업체 계정: 사용자 한 명에 여러 로그인 방법을 연결하고, 사용자별로 연결을 해제합니다. 제공업체 계정 잠김에 대한 해답입니다.
- 엔터프라이즈 신원 연계: 소셜 제공업체 대신 임직원용 IAM 시스템을 가리키는 커스텀 어댑터입니다.
- 디바이스 간 연속성: 플랫폼 세션은 어느 제공업체로 시작했든 REST, GraphQL, Live Query 전반에서 동작합니다.
인증 어댑터를 쓸까, 흐름을 직접 구축할까? 결정 매트릭스
| 상황 | 권장 방향 |
|---|---|
| 표준 제공업체, 표준 흐름 | 어댑터: 누구나 쓰는 표준 경로 |
| 흐름 중간에 커스텀 클레임 처리가 필요함 | 직접 구현, 또는 플랫폼이 허용한다면 커스텀 어댑터 |
| 작은 팀이고 인증이 제품의 핵심이 아님 | 어댑터: 토큰 검증 버그는 곧 유출 사고의 재료 |
| 이미 신원 서비스를 운영 중임 | 중복 구축하지 말고 커스텀 어댑터로 연계 |
| 컴플라이언스상 인증의 모든 바이트를 소유해야 함 | 감사할 수 있는 오픈소스 구성 요소로 직접 구현 |
| 게스트가 데이터 손실 없이 전환되어야 함 | 어댑터: 그 자리에서의 연결이 기능의 전부 |
한계와 트레이드오프
- 플랫폼의 제공업체 목록을 물려받습니다. 주류 제공업체는 지원되지만, 틈새 제공업체라면 커스텀 어댑터를 작성하거나 로드맵을 기다려야 합니다.
- 클라이언트 측 흐름은 여전히 개발자의 몫입니다. 어댑터는 토큰 제출부터 시작합니다. 네이티브 로그인 UX, 리디렉션, PKCE는 여전히 클라이언트 작업이며, 앱 내 웹뷰 제약도 여전히 걸림돌입니다.
- 제공업체의 정책이 그대로 전달됩니다. 동의 화면, 토큰 형식, 릴레이 이메일 주소, 지원 중단은 제공업체의 일정에 따라 바뀝니다. 어댑터는 메커니즘을 흡수할 뿐 정책까지 흡수하지는 않습니다.
- 디버깅이 세 당사자에 걸쳐 있습니다. 로그인 실패는 클라이언트 흐름, 어댑터의 검증 호출, 제공업체 중 어디에서든 시작될 수 있습니다. 어댑터 경계에 좋은 로그를 남기는 일은 첫날부터 해 둘 가치가 있습니다.
- 통일성은 양날의 검입니다. 어댑터는 모든 제공업체를 주체 ID와 프로필로 정규화합니다. 앱에 제공업체별 상세 데이터가 깊이 필요하다면 어차피 그 제공업체의 API를 따로 호출하게 됩니다.
Back4app에서의 소셜 인증 어댑터
Back4app은 관리형 데이터베이스, 자동 생성되는 REST 및 GraphQL API, 인증, 파일 스토리지, Cloud Code 서버리스 함수를 결합한 오픈소스 Backend as a Service(BaaS) 플랫폼입니다. Back4app의 인증 어댑터는 이 글의 모든 내용을 플랫폼 동작으로 구현합니다. logInWith는 제공업체 토큰을 서버 측에서 검증하고 안정적인 주체 ID로 사용자를 생성하거나 매칭하며, linkWith는 계정 연결과 익명 사용자 업그레이드를 모두 그 자리에서 처리하고, 그 결과로 얻은 세션은 CLP와 ACL이 접근 범위를 결정하는 가운데 모든 API에서 동작합니다. 제공업체는 대시보드에서 설정하며 어댑터 인터페이스는 열려 있습니다. 커스텀 제공업체를 추가할 때도 인증 스택을 포크할 필요 없이 작은 검증 모듈만 작성하면 됩니다.
자주 묻는 질문
소셜 인증 어댑터란 무엇입니까?
ID 제공업체와 사용자 테이블 사이에서 플랫폼 측에 놓인 변환 계층입니다. 클라이언트가 제공업체에서 토큰을 받아 오면, 어댑터는 그 토큰을 해당 제공업체를 통해 서버 측에서 검증하고, 안정적인 주체(subject) ID를 추출한 뒤, 사용자 레코드를 생성하거나 기존 레코드와 매칭하고 앱 자체의 세션을 발급합니다. 제공업체마다 어댑터가 하나씩 있고, 앱 쪽에는 하나의 통일된 사용자 및 세션 모델이 남습니다.
인증 어댑터는 OAuth 자체와 어떻게 다릅니까?
OAuth 2.0과 OpenID Connect는 흐름, 토큰, 클레임 같은 통신 프로토콜을 정의합니다. 어댑터는 그 프로토콜의 결과물을 소비하는 구현 구성 요소입니다. 제공업체의 토큰을 검증하고, 로컬 계정에 매핑하며, 계정 연결과 업그레이드를 처리합니다. OAuth를 직접 구현하면 리디렉션, 토큰 교환, 검증을 모두 스스로 책임져야 하고, 어댑터를 쓰면 토큰이 도착한 이후의 모든 것을 플랫폼이 책임집니다.
authData란 무엇입니까?
사용자 레코드에 저장되는 제공업체별 신원 블록입니다. 연결된 제공업체마다 안정적인 주체 ID와 어댑터가 검증한 자격 증명이 들어 있습니다. 두 제공업체로 로그인한 사용자에게는 하나의 계정을 가리키는 authData 항목이 두 개 있습니다. 매칭이 이메일이 아니라 제공업체의 주체 ID를 기준으로 하므로, 재활용된 이메일을 통한 고전적인 계정 탈취 유형은 설계 단계에서 배제됩니다.
BaaS에서 계정 연결은 어떻게 동작합니까?
연결 호출은 새 계정을 만드는 대신, 추가 제공업체의 검증된 토큰을 현재 로그인한 사용자에게 붙입니다. 어댑터는 새 제공업체의 토큰을 로그인 때와 똑같이 검증한 뒤 두 번째 authData 블록을 기록합니다. 이후에는 어느 제공업체로든 같은 계정에 로그인할 수 있습니다. 서로 다른 디바이스에서 서로 다른 버튼으로 들어오는 사용자에 대한 표준적인 해답입니다.
익명 사용자를 소셜 로그인으로 업그레이드할 수 있습니까?
가능합니다. 이 패턴의 가장 뛰어난 기법 중 하나입니다. 익명 사용자에 기반한 게스트 세션은 장바구니, 환경 설정, 게임 진행 상황 같은 실제 객체를 쌓아 갑니다. 그 사용자에 제공업체를 연결하면 사용자가 그 자리에서 식별된 계정으로 전환되며, 사용자 ID가 바뀌지 않으므로 모든 객체, ACL, 관계가 그대로 유지됩니다. 마이그레이션 스크립트도, 데이터 복사도 필요 없습니다.
백엔드가 사용자의 제공업체 비밀번호를 보게 됩니까?
아니요. 사용자는 제공업체 자체의 화면(앱이나 웹 페이지)에서 인증하고, 클라이언트는 토큰만 받습니다. 어댑터는 그 토큰을 받아 제공업체를 통해 검증한 뒤 주체 ID를 저장합니다. OAuth의 핵심 약속이 끝까지 유지되는 것입니다. 백엔드는 제3자 비밀번호를 보관하지 않으며, 사용자 테이블이 유출되어도 제공업체 자격 증명은 노출되지 않습니다.
제공업체 토큰이 만료되면 어떻게 됩니까?
보통은 눈에 띄는 일이 일어나지 않습니다. 제공업체 토큰은 로그인과 계정 연결 시점에 필요합니다. 어댑터가 이를 검증하고 플랫폼의 세션을 발급하고 나면, 그 세션은 제공업체 토큰의 수명이 아니라 앱의 규칙에 따라 유지됩니다. 사용자는 앱의 세션이 끝나거나 폐기될 때만 제공업체에서 다시 인증하며, 그때 어댑터가 새 토큰을 검증하고 주기가 반복됩니다.
플랫폼이 지원하지 않는 제공업체를 추가할 수 있습니까?
일반적으로 가능합니다. 어댑터 시스템은 대개 플러그인 방식입니다. 커스텀 어댑터는 작은 검증 인터페이스를 구현합니다. 클라이언트가 제출한 authData를 받아 발급 서비스에 확인하고 성공 또는 실패를 반환하는 것입니다. 덕분에 이 패턴은 플랫폼의 세션 메커니즘을 건드리지 않고도 틈새 ID 제공업체, 엔터프라이즈 SSO 연계, 신원을 증명할 수 있는 모든 서비스로 확장됩니다.