Реалізація агрегації Social Login (кілька провайдерів) на сайті

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

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

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

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

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація агрегації Social Login (кілька провайдерів) на сайті
Середня
~3-5 робочих днів
Часті питання

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

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

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

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

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

Підтримка кількох OAuth-провайдерів одночасно: користувач вибирає Google, GitHub, Apple або Microsoft — і потрапляє в один аккаунт. Ключове питання — зв'язування аккаунтів при однаковому email.

NextAuth.js / Auth.js: кілька провайдерів

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

Clerk: готове рішення

// Clerk налаштовується через dashboard.clerk.com
// Включаємо провайдери: Google, GitHub, Apple, Microsoft
// Зв'язування аккаунтів — автоматично

// Використання
import { SignIn } from '@clerk/nextjs';

export default function LoginPage() {
  return (
    <SignIn
      appearance={{
        elements: {
          socialButtonsBlockButton: 'border border-gray-200',
        }
      }}
    />
  );
}

Clerk автоматично обробляє conflict resolution при однаковому email від різних провайдерів, показуючи користувачу діалог підтвердження.

Управління пов'язаними аккаунтами

// Сторінка налаштувань: список пов'язаних провайдерів
import { auth } from '@/auth';
import { db } from '@/lib/db';

export default async function SecuritySettingsPage() {
  const session = await auth();
  const accounts = await db.account.findMany({
    where: { userId: session!.user.id }
  });

  return (
    <div>
      <h2>Пов'язані аккаунти</h2>
      {accounts.map(account => (
        <div key={account.id} className="flex justify-between">
          <span>{account.provider}</span>
          {accounts.length > 1 && (
            <button onClick={() => unlinkAccount(account.id)}>
              Відвязати
            </button>
          )}
        </div>
      ))}
    </div>
  );
}

Налаштування Auth.js з 4 провайдерами, зв'язуванням аккаунтів та Prisma адаптером — 2–3 робочі дні.