본문으로 건너뛰기
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.json의 socialLogin 섹션에서 버튼 노출 여부를 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.json → kakao.nativeAppKey 입력
  6. build.gradle.kts → manifestPlaceholders["kakaoScheme"] = "kakao{Native앱키}" 수정

네이버

  1. 네이버 개발자 센터 → Application → 애플리케이션 등록
  2. 사용 API → 네아로(네이버 아이디로 로그인) 선택
  3. Android → 패키지명 등록
  4. Client ID / Client Secret 복사
  5. social_login_config.json → naver.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.json → line.channelId 입력

Meta (Facebook)

  1. Meta for Developers → My Apps → Create App
  2. 제품 추가 → Facebook Login 선택
  3. 설정 → 기본 설정 → 앱 ID 및 클라이언트 토큰 복사
  4. Android → 패키지 이름 및 키 해시 등록
  5. social_login_config.json → meta.appId, meta.clientToken 입력
  6. build.gradle.kts → manifestPlaceholders["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