본문으로 건너뛰기
Unveilydocs

소셜 로그인 설정

Google, Apple 소셜 로그인을 연결합니다.

한눈에 보기

Unveily SDK는 플랜별로 소셜 로그인 제공 범위가 다릅니다.

소셜BasicStandardPro
Google
Apple (iOS)
Apple (Android)
카카오
네이버
LINE
Meta

Google과 Apple 모두 Firebase Auth를 통해 처리됩니다 — 별도의 백엔드가 필요 없습니다.

두 제공사 모두 Firebase Console에서 설정하며, 동일한 google-services.json 파일이 두 로그인에 모두 적용됩니다. SDK는 Google 및 Apple 로그인에 대해 Firebase ID Token (idToken)을 반환하며, 서버에서 Firebase Admin SDK를 사용해 검증할 수 있습니다.

iOS 프로바이더 지원 현황

현재 iOS에서는 Apple과 Google 로그인만 동작합니다. iOS에서 카카오·네이버·LINE·Meta를 호출하면 향후 iOS SDK 업데이트 전까지 SDK_NOT_READY 에러가 반환될 수 있습니다. Android는 모든 프로바이더를 지원합니다. Apple/Google은 두 플랫폼 모두에서 동작합니다.

또한 iOS의 Apple 로그인은 idToken과 함께 authorizationCode (Apple 전용)를 반환합니다. 위의 "Google + Apple은 idToken 반환" 설명은 Firebase 경로(Android 및 iOS의 Firebase 처리분)에 해당합니다.


1. 키 설정 파일

assets/config/social_login_config.json에 각 소셜의 SDK 키를 입력합니다.

{
  "kakao": {
    "nativeAppKey": "여기에_카카오_네이티브_앱_키"
  },
  "naver": {
    "clientId": "여기에_네이버_클라이언트_ID",
    "clientSecret": "여기에_네이버_클라이언트_시크릿",
    "appName": "앱 이름"
  },
  "line": {
    "channelId": "여기에_LINE_채널_ID"
  },
  "meta": {
    "appId": "여기에_메타_앱_ID",
    "clientToken": "여기에_메타_클라이언트_토큰"
  }
}
  • 키가 비어 있으면 해당 소셜은 자동 비활성화됩니다.
  • Google 및 Apple: google-services.json + Firebase Console로 설정합니다. 이 파일에 별도 항목이 필요 없습니다.

2. build.gradle.kts Placeholder 수정

카카오와 Meta는 딥링크 콜백을 위해 build.gradle.kts의 manifest placeholder를 업데이트해야 합니다.

// app/build.gradle.kts — defaultConfig 내부
defaultConfig {
    // 카카오: "kakao" + nativeAppKey
    manifestPlaceholders["kakaoScheme"] = "kakao여기에_카카오_네이티브_앱_키"
    // Meta: "fb" + appId
    manifestPlaceholders["facebookLoginProtocolScheme"] = "fb여기에_메타_앱_ID"
}

3. 소셜 버튼 노출 제어

assets/config.jsonsocialLogin 섹션에서 버튼 노출 여부를 on/off 합니다.

{
  "socialLogin": {
    "google": true,
    "apple": true,
    "kakao": true,
    "naver": true,
    "line": false,
    "meta": false
  }
}
  • true: 버튼 노출 허용 (단, 플랜에서도 허용돼야 실제 동작)
  • false: 버튼 미노출

4. 개발자 콘솔 등록 절차

Google

  1. Firebase Console → 프로젝트 생성
  2. Authentication → 로그인 방법 → Google 사용 설정
  3. google-services.json 다운로드 → app/ 폴더에 교체

Apple (iOS 및 Android)

iOS와 Android 모두 Apple 로그인은 Firebase Auth를 통해 처리됩니다 — 동일한 Firebase 프로젝트가 두 플랫폼을 모두 지원합니다.

  1. Apple Developer → Certificates, Identifiers & Profiles:
    • Services ID 생성 (역도메인 형식, 예: com.yourcompany.app)
    • Sign In with Apple이 활성화된 Key 생성 → Team ID, Key ID 메모 후 .p8 키 다운로드
  2. Firebase Console → Authentication → 로그인 방법 → Apple 사용 설정
    • Team ID, Services ID, Key ID 입력 후 .p8 개인 키 내용 붙여넣기
  3. 동일한 google-services.json이 Google과 Apple 모두에 적용됩니다 — 추가 파일 불필요.

Firebase가 Apple의 전체 OAuth 플로우(Android의 Chrome Custom Tab 포함)를 내부적으로 처리합니다. 서버 측 콜백 엔드포인트나 커스텀 URL 스킴이 필요 없습니다.

카카오

  1. 카카오 개발자 콘솔 → 내 애플리케이션 → 애플리케이션 추가
  2. 앱 키 → Native 앱 키 복사
  3. 플랫폼 → Android → 패키지명 및 마켓 URL 등록
  4. 카카오 로그인 → 활성화 → 동의 항목 설정 (닉네임, 이메일 등)
  5. social_login_config.jsonkakao.nativeAppKey 입력
  6. build.gradle.ktsmanifestPlaceholders["kakaoScheme"] = "kakao{Native앱키}" 수정

네이버

  1. 네이버 개발자 센터 → Application → 애플리케이션 등록
  2. 사용 API → 네아로(네이버 아이디로 로그인) 선택
  3. Android → 패키지명 등록
  4. Client ID / Client Secret 복사
  5. social_login_config.jsonnaver.clientId, naver.clientSecret, naver.appName 입력

LINE

  1. LINE Developers → Providers → Create → Channel 생성
  2. Channel 종류 → LINE Login 선택
  3. Android Package Name 등록
  4. Channel ID 복사
  5. social_login_config.jsonline.channelId 입력

Meta (Facebook)

  1. Meta for Developers → My Apps → Create App
  2. 제품 추가 → Facebook Login 선택
  3. 설정 → 기본 설정 → 앱 ID클라이언트 토큰 복사
  4. Android → 패키지 이름 및 키 해시 등록
  5. social_login_config.jsonmeta.appId, meta.clientToken 입력
  6. build.gradle.ktsmanifestPlaceholders["facebookLoginProtocolScheme"] = "fb{앱ID}" 수정

5. JS Bridge API

로그인

window.unveilyBridge.auth.socialLogin({
  provider: 'google' // 'apple' | 'kakao' | 'naver' | 'line' | 'meta'
});

로그인 콜백

페이지에 다음 함수를 선언해두면 결과를 수신합니다.

function onSocialLoginResult(result) {
  if (result.success) {
    console.log(result.provider);     // 'google' | 'apple' | 'kakao' | ...
    console.log(result.uid);          // Firebase UID (Google/Apple) 또는 소셜 고유 ID
    console.log(result.displayName);  // 이름 (null 가능)
    console.log(result.email);        // 이메일 (null 가능)
    console.log(result.profileImage); // 프로필 이미지 URL (null 가능)
    console.log(result.idToken);      // Firebase ID Token (Google + Apple만 해당; 카카오/네이버/LINE/Meta는 null)
    // idToken으로 서버 측 Firebase Admin SDK를 통해 로그인 검증
  } else {
    console.error(result.error);
    // 주요 에러 코드:
    // FEATURE_NOT_ALLOWED  → 티어 미충족 (result.requiredTier 포함)
    // PROVIDER_DISABLED    → config.json에서 false로 설정됨
    // SDK_NOT_CONFIGURED   → social_login_config.json 키 미입력
    // SDK_NOT_READY        → iOS에서 아직 미지원 프로바이더 (카카오/네이버/LINE/Meta)
    // LOGIN_CANCELLED      → 사용자가 로그인 화면을 닫음
  }
}

로그아웃

window.unveilyBridge.auth.logout({ provider: 'google' });

연동 해제 (소셜 제공사 연결 해제)

해당 소셜 제공사의 연결만 해제합니다. 계정 자체는 삭제되지 않습니다.

window.unveilyBridge.auth.revoke({ provider: 'kakao' });

Google / Apple 연동 해제

revoke는 Firebase user.unlink()를 호출합니다 — 해당 제공사 연결만 해제됩니다. Firebase 계정은 그대로 유지됩니다. 계정 탈퇴(삭제)는 서버에서 user.delete()를 호출하는 별도 처리가 필요합니다.

로그아웃/연동해제 콜백

function onSocialAuthResult(result) {
  // result.success  — true/false
  // result.action   — 'logout' | 'revoke'
  // result.provider — 'google' | 'apple' | 'kakao' | ...
  // result.error    — 실패 시 에러 코드
}

6. 에러 코드 참조

에러 코드설명
FEATURE_NOT_ALLOWED현재 플랜에서 허용되지 않는 소셜 (+ requiredTier 포함)
PROVIDER_DISABLEDconfig.json에서 해당 소셜이 false
SDK_NOT_CONFIGUREDsocial_login_config.json 키가 비어 있음
SDK_NOT_READYiOS에서 아직 지원되지 않는 프로바이더 (카카오/네이버/LINE/Meta — 향후 iOS SDK 업데이트 예정)
LOGIN_CANCELLED사용자가 로그인 화면을 닫음
UNKNOWN_PROVIDER지원하지 않는 provider 값
GOOGLE_ID_TOKEN_NULLGoogle ID 토큰 발급 실패
FIREBASE_AUTH_FAILEDFirebase 로그인 자격증명 거부됨
APPLE_NO_USERApple 로그인 성공했으나 Firebase가 사용자를 반환하지 않음
APPLE_ERRORApple 로그인 실패 (일반)
GOOGLE_REVOKE_FAILEDGoogle 제공사 연결 해제 실패
APPLE_REVOKE_FAILEDApple 제공사 연결 해제 실패
NAVER_NO_TOKEN네이버 액세스 토큰 없음
LINE_INIT_FAILEDLINE SDK 초기화 실패

7. profileImage 처리 규칙

소셜제공 여부
Google거의 항상 제공 (없으면 null)
Apple항상 null (원천 미제공)
카카오동의 항목 설정 시 제공
네이버동의 항목 설정 시 제공
LINE사용자 프로필 공개 설정 시 제공
Meta공개 프로필 설정 시 제공

null인 경우 고객사에서 기본 이미지를 표시하도록 처리하세요.

function onSocialLoginResult(result) {
  const avatar = result.profileImage
    ?? '/images/default-avatar.png';
  document.getElementById('avatar').src = avatar;
}

8. Apple displayName 캐시

Apple은 최초 로그인 시에만 사용자 이름을 제공합니다. SDK는 이를 로컬에 캐시합니다.

동작처리
첫 로그인 (displayName 있음)캐시 저장 후 전달
재로그인 (displayName null)캐시된 값으로 대체 전달
로그아웃캐시 유지 (재로그인 대비)
연동 해제 (revoke)캐시 삭제 (재연동 시 Apple이 새로 제공)
플랫폼캐시 저장소키 형식
iOSUserDefaultsapple_name_{uid}
AndroidSharedPreferences("unveily_apple_name_cache")apple_name_{uid}

9. 완성 예제

// 로그인 버튼 클릭
document.getElementById('btn-google').addEventListener('click', () => {
  window.unveilyBridge.auth.socialLogin({ provider: 'google' });
});

// 결과 처리
function onSocialLoginResult(result) {
  if (!result.success) {
    if (result.error === 'FEATURE_NOT_ALLOWED') {
      alert(`${result.requiredTier} 플랜 이상에서 사용 가능합니다.`);
    } else if (result.error !== 'LOGIN_CANCELLED') {
      alert('로그인에 실패했습니다.');
    }
    return;
  }

  // 서버에 전송 — Google/Apple은 idToken으로 서버 측 검증
  sendToServer(result);
}

async function sendToServer(result) {
  await fetch('/api/auth/social', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      provider: result.provider,
      uid:      result.uid,
      idToken:  result.idToken,   // Firebase ID Token (Google/Apple) — 서버 측 검증용
      email:    result.email,
      displayName: result.displayName
    })
  });
}

서버 측 Firebase ID Token 검증

Google 및 Apple 로그인의 경우, Firebase Admin SDK를 사용해 서버에서 idToken을 검증합니다.

// npm install firebase-admin
import admin from 'firebase-admin';
admin.initializeApp({ credential: admin.credential.applicationDefault() });

app.post('/api/auth/social', async (req, res) => {
  const { provider, uid, idToken, email, displayName } = req.body;

  // Google 및 Apple의 Firebase ID Token 검증
  if (idToken) {
    const decoded = await admin.auth().verifyIdToken(idToken);
    if (decoded.uid !== uid) return res.status(401).json({ error: 'UID mismatch' });
  }

  // uid로 사용자 조회 또는 생성
  let user = await db.users.findOne({ socialProvider: provider, socialId: uid });
  if (!user) {
    user = await db.users.create({ socialProvider: provider, socialId: uid, email, name: displayName });
  }

  req.session.userId = user.id;
  res.json({ ok: true, user: { id: user.id, name: user.name } });
});
// NuGet: FirebaseAdmin
using FirebaseAdmin;
using FirebaseAdmin.Auth;

[HttpPost("auth/social")]
public async Task<IActionResult> SocialLogin([FromBody] SocialLoginRequest request)
{
    // Google 및 Apple의 Firebase ID Token 검증
    if (!string.IsNullOrEmpty(request.IdToken))
    {
        var decoded = await FirebaseAuth.DefaultInstance.VerifyIdTokenAsync(request.IdToken);
        if (decoded.Uid != request.Uid)
            return Unauthorized(new { error = "UID mismatch" });
    }

    var user = await _userService.FindOrCreateBySocialAsync(
        request.Provider, request.Uid, request.Email, request.DisplayName);

    HttpContext.Session.SetInt32("UserId", user.Id);
    return Ok(new { ok = true, user = new { user.Id, user.Name } });
}
// Maven: com.google.firebase:firebase-admin
@PostMapping("/api/auth/social")
public ResponseEntity<?> socialLogin(@RequestBody SocialLoginRequest request,
                                      HttpSession session) throws FirebaseAuthException {
    // Google 및 Apple의 Firebase ID Token 검증
    if (request.getIdToken() != null) {
        FirebaseToken decoded = FirebaseAuth.getInstance().verifyIdToken(request.getIdToken());
        if (!decoded.getUid().equals(request.getUid()))
            return ResponseEntity.status(401).body(Map.of("error", "UID mismatch"));
    }

    User user = userService.findOrCreateBySocial(
        request.getProvider(), request.getUid(), request.getEmail(), request.getDisplayName());
    session.setAttribute("userId", user.getId());
    return ResponseEntity.ok(Map.of("ok", true, "userId", user.getId()));
}
<?php
// composer require kreait/firebase-php
use Kreait\Firebase\Factory;

$data = json_decode(file_get_contents('php://input'), true);
$provider    = $data['provider'];
$uid         = $data['uid'];
$idToken     = $data['idToken'] ?? null;
$email       = $data['email'] ?? null;
$displayName = $data['displayName'] ?? null;

// Google 및 Apple의 Firebase ID Token 검증
if ($idToken) {
    $firebase = (new Factory)->withServiceAccount('/path/to/serviceAccount.json');
    $auth = $firebase->createAuth();
    $decoded = $auth->verifyIdToken($idToken);
    if ($decoded->claims()->get('sub') !== $uid) {
        http_response_code(401);
        echo json_encode(['error' => 'UID mismatch']);
        exit;
    }
}

$stmt = $pdo->prepare('SELECT * FROM users WHERE social_provider = ? AND social_id = ?');
$stmt->execute([$provider, $uid]);
$user = $stmt->fetch();

if (!$user) {
    $pdo->prepare('INSERT INTO users (social_provider, social_id, email, name) VALUES (?, ?, ?, ?)')
        ->execute([$provider, $uid, $email, $displayName]);
    $userId = $pdo->lastInsertId();
} else {
    $userId = $user['id'];
}

$_SESSION['user_id'] = $userId;
echo json_encode(['ok' => true, 'userId' => $userId]);

On this page