Skip to main content
Unveilydocs

Social Login Setup

Wire up Google and Apple social login in minutes.

At a Glance

The Unveily SDK supports different social login providers depending on your plan.

SocialBasicStandardPro
Google
Apple (iOS)
Apple (Android)
Kakao
Naver
LINE
Meta

Google and Apple both go through Firebase Auth — no custom backend required.

Both providers are configured in Firebase Console and the same google-services.json covers both. The SDK returns a Firebase ID Token (idToken) for Google and Apple logins, which your server can verify using the Firebase Admin SDK.

iOS provider availability

On iOS today, only Apple and Google login work. Calling Kakao, Naver, LINE, or Meta on iOS may return the SDK_NOT_READY error until a future iOS SDK update. Android supports all providers. Apple/Google work on both platforms.

Also, Apple login on iOS returns an authorizationCode (Apple-only) in addition to idToken. The "Google + Apple return idToken" statement above refers to the Firebase path (Android, and the Firebase-handled portion on iOS).


1. Key Configuration File

Enter the SDK keys for each social provider in assets/config/social_login_config.json.

{
  "kakao": {
    "nativeAppKey": "your-kakao-native-app-key-here"
  },
  "naver": {
    "clientId": "your-naver-client-id-here",
    "clientSecret": "your-naver-client-secret-here",
    "appName": "App Name"
  },
  "line": {
    "channelId": "your-line-channel-id-here"
  },
  "meta": {
    "appId": "your-meta-app-id-here",
    "clientToken": "your-meta-client-token-here"
  }
}
  • If a key is empty, that social provider is automatically disabled.
  • Google and Apple: Configured via google-services.json + Firebase Console. No entries needed in this file.

2. Update build.gradle.kts Placeholders

Kakao and Meta require updating the manifest placeholders in build.gradle.kts for deep link callbacks.

// app/build.gradle.kts — inside defaultConfig
defaultConfig {
    // Kakao: "kakao" + nativeAppKey
    manifestPlaceholders["kakaoScheme"] = "kakaoyour-kakao-native-app-key-here"
    // Meta: "fb" + appId
    manifestPlaceholders["facebookLoginProtocolScheme"] = "fbyour-meta-app-id-here"
}

3. Control Social Button Visibility

Use the socialLogin section in assets/config.json to turn each button on or off.

{
  "socialLogin": {
    "google": true,
    "apple": true,
    "kakao": true,
    "naver": true,
    "line": false,
    "meta": false
  }
}
  • true: Button is shown (also requires the plan to allow it for it to actually work)
  • false: Button is hidden

4. Developer Console Registration Steps

Google

  1. Firebase Console → Create a project
  2. Authentication → Sign-in method → Enable Google
  3. Download google-services.json → Place in the app/ folder

Apple (iOS and Android)

Both iOS and Android Apple Sign-In go through Firebase Auth — the same Firebase project covers both platforms.

  1. Apple Developer → Certificates, Identifiers & Profiles:
    • Create a Services ID (reverse-domain format, e.g. com.yourcompany.app)
    • Create a Key with Sign In with Apple enabled → note the Team ID, Key ID, and download the .p8 key
  2. Firebase Console → Authentication → Sign-in method → Enable Apple
    • Enter the Team ID, Services ID, Key ID, and paste the .p8 private key content
  3. The same google-services.json covers both Google and Apple — no extra file needed.

Firebase handles the entire OAuth flow with Apple internally (including the Chrome Custom Tab on Android). No server-side callback endpoint or custom URL scheme is required.

Kakao

  1. Kakao Developers Console → My Applications → Add Application
  2. App Keys → Copy the Native App Key
  3. Platform → Android → Register package name and market URL
  4. Kakao Login → Activate → Configure consent items (nickname, email, etc.)
  5. social_login_config.json → Enter kakao.nativeAppKey
  6. build.gradle.kts → Update manifestPlaceholders["kakaoScheme"] = "kakao{NativeAppKey}"
  1. Naver Developers Center → Application → Register Application
  2. Use API → Select Naver ID Login
  3. Android → Register package name
  4. Copy Client ID / Client Secret
  5. social_login_config.json → Enter naver.clientId, naver.clientSecret, naver.appName

LINE

  1. LINE Developers → Providers → Create → Create a Channel
  2. Channel type → Select LINE Login
  3. Register Android Package Name
  4. Copy Channel ID
  5. social_login_config.json → Enter line.channelId

Meta (Facebook)

  1. Meta for Developers → My Apps → Create App
  2. Add Product → Select Facebook Login
  3. Settings → Basic → Copy App ID and Client Token
  4. Android → Register package name and key hash
  5. social_login_config.json → Enter meta.appId, meta.clientToken
  6. build.gradle.kts → Update manifestPlaceholders["facebookLoginProtocolScheme"] = "fb{AppID}"

5. JS Bridge API

Login

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

Login Callback

Declare the following function on your page to receive the result.

function onSocialLoginResult(result) {
  if (result.success) {
    console.log(result.provider);     // 'google' | 'apple' | 'kakao' | ...
    console.log(result.uid);          // Firebase UID (Google/Apple) or social unique ID
    console.log(result.displayName);  // Name (can be null)
    console.log(result.email);        // Email (can be null)
    console.log(result.profileImage); // Profile image URL (can be null)
    console.log(result.idToken);      // Firebase ID Token (Google + Apple only; null for Kakao/Naver/LINE/Meta)
    // Use idToken to verify the login server-side via Firebase Admin SDK
  } else {
    console.error(result.error);
    // Key error codes:
    // FEATURE_NOT_ALLOWED  → Tier requirement not met (includes result.requiredTier)
    // PROVIDER_DISABLED    → Set to false in config.json
    // SDK_NOT_CONFIGURED   → Key not entered in social_login_config.json
    // SDK_NOT_READY        → Provider not yet available on iOS (Kakao/Naver/LINE/Meta)
    // LOGIN_CANCELLED      → User closed the login screen
  }
}

Logout

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

Disconnects the social provider from the user's account. The account itself is not deleted.

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

Google / Apple Revoke

revoke calls Firebase user.unlink() — it disconnects the provider only. The Firebase account remains intact. Account deletion (withdrawal) must be handled separately via user.delete() on your server.

Logout / Revoke Callback

function onSocialAuthResult(result) {
  // result.success  — true/false
  // result.action   — 'logout' | 'revoke'
  // result.provider — 'google' | 'apple' | 'kakao' | ...
  // result.error    — error code on failure
}

6. Error Code Reference

Error CodeDescription
FEATURE_NOT_ALLOWEDSocial provider not allowed on current plan (includes requiredTier)
PROVIDER_DISABLEDThat social provider is set to false in config.json
SDK_NOT_CONFIGUREDKey is empty in social_login_config.json
SDK_NOT_READYProvider not yet supported on iOS (Kakao/Naver/LINE/Meta — planned for a future iOS SDK update)
LOGIN_CANCELLEDUser closed the login screen
UNKNOWN_PROVIDERUnsupported provider value
GOOGLE_ID_TOKEN_NULLGoogle ID token issuance failed
FIREBASE_AUTH_FAILEDFirebase sign-in credential rejected
APPLE_NO_USERApple sign-in succeeded but Firebase returned no user
APPLE_ERRORApple sign-in failed (general)
GOOGLE_REVOKE_FAILEDGoogle provider unlink failed
APPLE_REVOKE_FAILEDApple provider unlink failed
NAVER_NO_TOKENNo Naver access token
LINE_INIT_FAILEDLINE SDK initialization failed

7. profileImage Handling Rules

SocialAvailability
GoogleAlmost always available (null if not)
AppleAlways null (not provided by Apple)
KakaoAvailable if consent item is configured
NaverAvailable if consent item is configured
LINEAvailable if user profile is public
MetaAvailable if public profile is set

If null, display a default image on your side.

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

8. Apple displayName Cache

Apple only provides the user's name on the first login. The SDK caches this locally.

ActionBehavior
First login (displayName available)Saved to cache and passed along
Re-login (displayName is null)Replaced with cached value
LogoutCache retained (for re-login)
RevokeCache deleted (Apple provides it again on re-link)
PlatformCache StorageKey Format
iOSUserDefaultsapple_name_{uid}
AndroidSharedPreferences("unveily_apple_name_cache")apple_name_{uid}

9. Complete Example

// Login button click
document.getElementById('btn-google').addEventListener('click', () => {
  window.unveilyBridge.auth.socialLogin({ provider: 'google' });
});

// Handle the result
function onSocialLoginResult(result) {
  if (!result.success) {
    if (result.error === 'FEATURE_NOT_ALLOWED') {
      alert(`Available on ${result.requiredTier} plan or higher.`);
    } else if (result.error !== 'LOGIN_CANCELLED') {
      alert('Login failed.');
    }
    return;
  }

  // Send to your server — use idToken for server-side verification (Google/Apple)
  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) — verify server-side
      email:    result.email,
      displayName: result.displayName
    })
  });
}

Server-side Firebase ID Token Verification

For Google and Apple logins, verify the idToken on your server using the Firebase Admin SDK.

// 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;

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

  // Find or create user by 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)
{
    // Verify Firebase ID Token for Google and Apple
    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 {
    // Verify Firebase ID Token for Google and Apple
    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;

// Verify Firebase ID Token for Google and Apple
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