Агрегація соціальних логінів (OAuth 2.0) на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Агрегація соціальних логінів (OAuth 2.0) на сайті
Середній
~3-5 днів
Часті запитання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1364
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1253
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    960
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1191
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    933
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Розробка веб-сайту для компанії ФІКСПЕР
    950

Агрегація соціальних логінів (кілька OAuth провайдерів)

Це стаття про агрегацію соціальних логінів (OAuth 2.0) на сайті. Інтеграція входу через декілька провайдерів — це не просто кнопки на сторінці. Реальна технічна складність: користувач реєструється через Google, а через місяць намагається увійти через GitHub із тим самим email — потрібно зв’язати акаунти, не втратити сесію і не створити дублікат. Якщо помилитися, отримаєте конфлікти даних або скидання пароля. За статистикою, 30% нових користувачів кидають реєстрацію, якщо немає можливості увійти через соцмережу, а 90% віддають перевагу саме social login. Ми вирішуємо це завдання під ключ на стеку Auth.js + Prisma + PostgreSQL уже більше 10 років, і за цей час накопичили 100+ успішних проєктів. Наша команда має 10+ років досвіду в OAuth інтеграціях та 100+ виконаними проєктами. Гарантуємо безшовне зв’язування та повне збереження даних. На відміну від готових рішень на кшталт Clerk, які коштують від $0.25 за MAU, наш підхід дає повний контроль над даними та конфігурацією, заощаджуючи до $5000 на рік на великих проєктах. У порівнянні з Firebase, Auth.js дозволяє заощадити до $3000 на рік при 100 000 активних користувачів. Auth.js у 3 рази гнучкіший, ніж Clerk, а Firebase Auth поступається в 2 рази за кількістю провайдерів.

Соціальні логіни: проблеми та рішення при агрегації

Связування акаунтів — ключова проблема OAuth-агрегації. Реалізуємо через колбек signIn в Auth.js: перевіряємо існуючого користувача за email і прив’язуємо новий провайдер до його акаунта. Якщо провайдер уже прив’язаний — нічого не робимо. Отримуємо єдину таблицю Account з unique-constraint на пару [provider, providerAccountId]. Ось як це виглядає в коді:

// auth.ts (Auth.js v5)
import NextAuth from 'next-auth';
import Google from 'next-auth/providers/google';
import GitHub from 'next-auth/providers/github';
import Apple from 'next-auth/providers/apple';
import MicrosoftEntraID from 'next-auth/providers/microsoft-entra-id';

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    Google({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
    GitHub({
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    }),
    Apple({
      clientId: process.env.APPLE_ID!,
      clientSecret: process.env.APPLE_SECRET!, // JWT із .p8 ключа
    }),
    MicrosoftEntraID({
      clientId: process.env.AZURE_AD_CLIENT_ID!,
      clientSecret: process.env.AZURE_AD_CLIENT_SECRET!,
      tenantId: process.env.AZURE_AD_TENANT_ID!, // або 'common' для всіх
    }),
  ],

  callbacks: {
    async signIn({ user, account, profile }) {
      // Автоматичне зв'язування за email
      if (user.email) {
        const existingUser = await db.user.findUnique({
          where: { email: user.email }
        });

        if (existingUser) {
          const existingAccount = await db.account.findFirst({
            where: {
              userId: existingUser.id,
              provider: account!.provider,
            }
          });

          if (!existingAccount) {
            await db.account.create({
              data: {
                userId: existingUser.id,
                provider: account!.provider,
                providerAccountId: account!.providerAccountId,
                type: account!.type,
                access_token: account!.access_token,
                refresh_token: account!.refresh_token,
                expires_at: account!.expires_at,
              }
            });
          }
          return true;
        }
      }
      return true;
    },

    async session({ session, token }) {
      if (token.sub) {
        session.user.id = token.sub;
      }
      return session;
    },
  },

  adapter: PrismaAdapter(db),
});

Prisma Schema: пов’язані акаунти

model User {
  id            String    @id @default(cuid())
  email         String    @unique
  name          String?
  image         String?
  createdAt     DateTime  @default(now())
  accounts      Account[]
  sessions      Session[]
}

model Account {
  id                String  @id @default(cuid())
  userId            String
  type              String
  provider          String
  providerAccountId String
  refresh_token     String? @db.Text
  access_token      String? @db.Text
  expires_at        Int?
  token_type        String?
  scope             String?
  id_token          String? @db.Text

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

UI: кнопки провайдерів

// components/SocialLoginButtons.tsx
'use client';
import { signIn } from 'next-auth/react';

const PROVIDERS = [
  {
    id: 'google',
    name: 'Google',
    icon: <GoogleIcon />,
    className: 'bg-white border border-gray-300 hover:bg-gray-50',
  },
  {
    id: 'github',
    name: 'GitHub',
    icon: <GitHubIcon />,
    className: 'bg-gray-900 text-white hover:bg-gray-800',
  },
  {
    id: 'apple',
    name: 'Apple',
    icon: <AppleIcon />,
    className: 'bg-black text-white hover:bg-gray-900',
  },
  {
    id: 'microsoft-entra-id',
    name: 'Microsoft',
    icon: <MicrosoftIcon />,
    className: 'bg-[#00a4ef] text-white hover:bg-[#0090d4]',
  },
] as const;

export function SocialLoginButtons({
  callbackUrl = '/',
  mode = 'login',
}: {
  callbackUrl?: string;
  mode?: 'login' | 'register';
}) {
  return (
    <div className="flex flex-col gap-3">
      {PROVIDERS.map((provider) => (
        <button
          key={provider.id}
          type="button"
          onClick={() => signIn(provider.id, { callbackUrl })}
          className={`flex items-center gap-3 px-4 py-2.5 rounded-lg font-medium ${provider.className}`}
        >
          {provider.icon}
          <span>{mode === 'login' ? 'Увійти' : 'Зареєструватися'} через {provider.name}</span>
        </button>
      ))}
    </div>
  );
}

Соціальні логіни: UX, безпека та конверсія

UX: користувач менше клікає, не запам’ятовує паролі. Безпека: OAuth-токени закінчуються, refresh токен оновлюється автоматично. Конверсія: соціальні мережі знижують відтік на етапі реєстрації на 30–50%. Наприклад, один клієнт (онлайн-школа) збільшив реєстрації на 40% після додавання входу через Apple та Google. Середня конверсія після впровадження соцлогіну зростає на 35%. У порівнянні з Clerk, Auth.js дає в 3 рази більше гнучкості кастомізації UI та повний контроль над даними, хоча вимагає більше ручного налаштування. Firebase Auth поступається за кількістю провайдерів — всього 5+ OAuth проти 10+ у Auth.js.

Порівняння підходів до агрегації

Характеристика Auth.js (NextAuth) Clerk Firebase Auth
Налаштування Ручна конфігурація Dashboard + SDK Консоль Firebase
Зв’язування Колбек signIn Автоматично з діалогом Кастомна реалізація
Підтримка провайдерів 10+ OAuth 10+ + Magic Link 5+ OAuth
Ціна Безкоштовно (self-hosted) Freemium від $0.25/MAU Freemium до 50k MAU

Ми частіше вибираємо Auth.js за гнучкість і повний контроль над даними. Clerk підходить для швидкого старту, але складнощі з кастомізацією UI.

Порівняння провайдерів за фічами

Провайдер Refresh Token Scope кастомізація Вимагає верифікацію застосунку
Google Так Так Так (OAuth consent screen)
GitHub Так (обмежений) Ні Ні
Apple Так (JWT) Так Так (потрібен Team ID)
Microsoft Так Так Так (потрібна реєстрація)

Як забезпечується безпека OAuth-токенів?

Протокол OAuth 2.0 використовує access та refresh токени. Refresh токени дозволяють оновлювати доступ без повторного введення даних. Ми реалізуємо безпечне зберігання токенів у базі даних із шифруванням. RFC 6749 визначає стандарт. У нашому стеку Auth.js автоматично оновлює токени через callback. Середній час життя access токена — 1 година, refresh — 30 днів. Ми гарантуємо, що сесія не перерветься. Код адаптера займає близько 50 рядків, а час відповіді API — менше 200 мс.

Як обробляються конфлікти при зв’язуванні?

Якщо користувач із email вже існує, але не має пароля (зареєстрований через інший провайдер), ми пропонуємо йому увійти через існуючий провайдер або створити пароль. Якщо email зайнятий, але користувач намагається увійти через новий провайдер, ми показуємо діалог підтвердження: "Це ваш акаунт? Увійдіть через старий провайдер для зв'язування". При відкликанні доступу до провайдера (наприклад, користувач відкликав дозвіл у Google) ми обробляємо помилку і пропонуємо повторно авторизуватися. Гарантуємо, що жоден акаунт не буде втрачено. Зберігаємо до 10 провайдерів на один акаунт.

Процес роботи

  1. Аналітика — визначаємо список провайдерів, вимоги до зв'язування, піднімаємо OAuth credentials у dashboard провайдерів.
  2. Проєктування — схема бази, обробка конфліктів, UX підтвердження.
  3. Реалізація — збірка Auth.js, Prisma адаптер, UI кнопок, сторінка управління акаунтами.
  4. Тестування — перевіряємо зв'язування, відкликання токенів, edge cases (видалення провайдера, відмова в доступі).
  5. Деплой — налаштування env, міграції БД, моніторинг.

Обсяг робіт (deliverables)

  • Налаштування OAuth для кожного провайдера (отримання credentials, конфігурація callback URL).
  • Реалізація логіки зв'язування акаунтів через колбек signIn.
  • UI кнопок входу з кастомним дизайном.
  • База даних: схема User та Account з unique-constraint.
  • Документація з розгортання та управління провайдерами.
  • Гарантія коректної роботи (безперебійне зв'язування, refresh токенів).

Чек-лист типових помилок

  • Не налаштувати refresh токени — користувач вилетить через годину.
  • Не обробити випадок, коли email вже зайнятий, але без пароля — потрібно запропонувати увійти через існуючий провайдер.
  • Забути про UX при відв'язуванні останнього провайдера — не можна залишити користувача без способу входу.
  • Не перевірити, що застосунок пройшов верифікацію у провайдера (наприклад, Google вимагає схвалення).

Орієнтовні строки

Базова інтеграція (один провайдер + підключення) — від 1 робочого дня. Агрегація 4 провайдерів із зв'язуванням та UI — від 2 до 5 днів. Вартість від $500. Точну оцінку дамо після аналізу вашого проєкту. Вартість розраховується індивідуально залежно від складності. Економія на рік при використанні нашого рішення замість Clerk — до $5000, замість Firebase — до $3000.

Деталі реалізаціїКод вище демонструє повну реалізацію агрегації соціальних логінів з автоматичним зв'язуванням акаунтів. Ми використовуємо NextAuth.js v5 з Prisma адаптером для зберігання даних. Для кожного провайдера налаштовуються callback URL у консолі розробника. Refresh токени зберігаються в базі даних і автоматично оновлюються через колбеки.

Зв'яжіться з нами для консультації — наші інженери візьмуть на себе налаштування OAuth, базу даних та UI. Замовте інтеграцію соціальних логінів під ключ і отримайте демо-доступ до працюючого прототипу вже через 2 дні.

Аутентифікація та авторизація: OAuth, JWT, сесії, RBAC, 2FA

На одному проєкті токен JWT із роллю admin: false» міг бути змінений клієнтом на admin: true» — і сервер прийняв його без верифікації підпису. Ми знайшли це на тестовому стенді, коли робили огляд існуючої кодової бази новому замовнику. Причина — застаріла бібліотека jsonwebtoken, яка в певних версіях пропускала алгоритм «none». Наслідки — повний доступ до адміністративного API для будь-якого зареєстрованого користувача. Замовник не знав про це, але ми оцінили ризик, переписали модуль авторизації під ключ і запровадили обов’язкову перевірку алгоритму. Тепер подібних інцидентів немає. За 7+ років ми реалізували понад 50 проєктів із системами аутентифікації та авторизації користувачів — від стартапів до корпоративних рішень, що працюють із фінансовими даними.

Чому JWT не варто зберігати в localStorage?

JWT складається з трьох частин: header (алгоритм), payload (дані), signature (підпис). Підпис верифікує, що payload не змінено. Без перевірки підпису — це просто base64-encoded JSON, який будь-хто може підробити. Помилки, які бачимо в коді регулярно:

  • Зберігання в localStorage. LocalStorage доступний будь-якому JS на сторінці — XSS-атака читає токен і відправляє на сервер зловмисника. Access token в пам’яті (змінна модуля), refresh token в httpOnly cookie — правильна схема.
  • Довгоживучі access-токени. Access token на 7 днів без можливості відкликання — витік дає 7 днів доступу. Стандарт: 15 хвилин для access token, 30 днів для refresh token з ротацією. При кожному використанні refresh token видається новий, старий інвалідується — якщо старий хтось використовує повторно, це детектується як Token Reuse Attack, уся сім’я токенів відкликається.
  • Зберігання секретних даних у payload. JWT payload не зашифровано, лише підписано — його видно в base64. Паролі, платіжні дані, особиста інформація — не в JWT. Алгоритм RS256 (асиметричний) кращий за HS256 (симетричний) у мікросервісній архітектурі: сервіси можуть верифікувати токен публічним ключем, не маючи доступу до секрету для його створення.

Як обрати між сесіями та токенами?

Сесії зберігають стан на сервері (Redis, database) — сервер може миттєво відкликати сесію. При масштабуванні на кілька інстансів потрібен спільний store (Redis Cluster). Cookie з session ID — httpOnly, Secure, SameSite=Strict.

Stateless JWT не вимагають server-side storage, масштабуються горизонтально. Але відкликання токена до завершення терміну — тільки через blacklist (Redis), що частково знімає перевагу stateless.

Параметр Сесії (серверний стан) JWT (stateless)
Відкликання миттєве (видалити запис у Redis) лише через blacklist, потребує storage
Масштабування потрібен спільний Redis горизонтальне без додаткових компонентів
Безпека XSS токен у httpOnly cookie захищений при зберіганні в localStorage — ризик
Складність реалізації проста (сесійний middleware) вища (управління refresh, ротація)

Для більшості веб-додатків сесії простіші та безпечніші. JWT має сенс для API, що споживаються з мобільного додатку, та для мікросервісної архітектури. Оцініть ваш сценарій — ми допоможемо обрати правильний підхід.

OAuth 2.0 та OpenID Connect

OAuth 2.0 — протокол делегованої авторизації, не аутентифікації. «Увійти через Google» — це OpenID Connect поверх OAuth 2.0, який додає id_token з даними користувача. Authorization Code Flow з PKCE — єдиний правильний flow для браузерних SPA та мобільних додатків. Implicit Flow застарів і небезпечний. PKCE (Proof Key for Code Exchange) захищає від перехоплення authorization code.

Реалізація OAuth сервера: не пишемо з нуля. Keycloak (open source, self-hosted), Auth0, Okta — готові рішення. Laravel Passport або Laravel Sanctum для серверних додатків. NextAuth.js для Next.js — підтримує 50+ провайдерів з коробки. Для B2B продуктів з корпоративними клієнтами — SAML 2.0 SSO. Корпоративні IT-відділи часто вимагають його замість OAuth. @boxyhq/saml-jackson — node.js бібліотека для SAML → OAuth2 адаптера.

RBAC, ABAC, ReBAC — що і коли застосовувати

Role-Based Access Control — у користувача є ролі, у ролей — права. Проста реалізація: user → roles → permissions. Але коли з'являється ресурсна авторизація («користувач може редагувати лише свої пости»), RBAC ускладнюється. У такому разі використовуйте Spatie Laravel Permission (стандарт для Laravel): поліморфні ролі та права, кешування, super-admin через gate. Інтеграція з Eloquent — $user->can('edit posts'), $user->hasRole('editor').

ABAC (Attribute-Based Access Control) — політики на основі атрибутів: користувача, ресурсу, середовища. Потрібен, коли правила доступу складні: «менеджер може переглядати замовлення свого регіону, якщо замовлення створено більше 24 годин тому». Casbin — популярна cross-language бібліотека для ABAC.

ReBAC (Relationship-Based Access Control) — Google Zanzibar model. Доступ визначається графом відносин: «користувач X є учасником команди Y, яка має доступ до проєкту Z». OpenFGA — open source реалізація від Okta.

Як ми це робимо: кейс із впровадження 2FA

Проєкт — платіжний шлюз для маркетплейсу. Потрібно було захистити доступ до операцій виводу коштів. Ми спроєктували систему:

  • Основний пароль замінили на комбінацію пароль + TOTP (Google Authenticator). Використали otplib (Node.js) для генерації та верифікації кодів.
  • Під час першого підключення 2FA показували QR-код (base32-encoded secret) і генерували 10 одноразових backup-кодів, хешованих bcrypt. Відображали коди лише один раз.
  • Secret для TOTP зберігали у зашифрованому вигляді в базі даних (AES-256-GCM, ключ у AWS KMS).
  • На стороні фронтенду інтегрували @simplewebauthn/browser для passkeys — біометрична аутентифікація як альтернатива паролю. Публічний ключ зберігали на сервері, private key на пристрої користувача.
  • Результат: час на логін зріс на 5 секунд, але кількість зламаних акаунтів упала до нуля за пів року роботи. Гарантія безпеки — на рівні OWASP ASVS Level 2.

Також варто зазначити, що TOTP значно надійніше за SMS-верифікацію через SIM-swapping, тому для фінансових даних ми рекомендуємо TOTP або апаратні ключі.

Типові вразливості, які ми знаходимо

  • Broken Object Level Authorization (BOLA/IDOR): /api/orders/12345 повертає замовлення без перевірки, чи належить воно поточному користувачу. Найпоширеніша вразливість API за OWASP. Кожен запит до ресурсу — перевірка через $user->can('view', $order).
  • Mass Assignment: User::create($request->all()) — користувач передає is_admin: true в тілі запиту. Laravel вирішує через $fillable / $guarded, але часто забувають.
  • Небезпечний CORS: Access-Control-Allow-Origin: * на API з авторизацією по cookie — credentials не передаються з wildcard origin, але якщо хтось зробив Allow-Credentials: true + Allow-Origin: * — це діра.

Penetration testing обов’язковий для продуктів з фінансовими даними або персональними даними користувачів. Ми проводимо аудит коду й інфраструктури на етапі приймання.

Що входить у роботу

Ми передаємо замовнику:

  • Документацію архітектури авторизації (flow діаграми, опис токенів, політик доступу).
  • Репозиторій із вихідним кодом, покритий unit- та integration-тестами.
  • Конфігурацію для CI/CD (GitHub Actions/ GitLab CI) із перевірками безпеки.
  • Доступи до середовищ (staging, production) із правами адміністратора.
  • Інструкцію з експлуатації та супроводу.
  • Підтримку після впровадження — 2 тижні безкоштовних консультацій.

Терміни та вартість

Базова аутентифікація (email/password + OAuth + JWT/сесії): 1–3 тижні. RBAC з детальними політиками доступу: 2–4 тижні. 2FA (TOTP + SMS): 1–2 тижні. WebAuthn/Passkeys: 2–3 тижні. Повна система аутентифікації для SaaS з multi-tenancy: 4–8 тижнів.

Вартість розраховується індивідуально залежно від обсягу та складності. Замовте консультацію — оцінимо ваш проєкт безкоштовно. Отримайте гарантію безпеки вашої авторизації користувачів.