---
title: "Інтеграція входу через Apple"
description: "Повний посібник з реалізації Sign in with Apple у платформі Ring"
locale: "uk"
---
# Посібник з інтеграції входу через Apple

Цей посібник надає повний розбір інтеграції **Sign in with Apple** у вашу програму Ring Platform.

## Список необхідних умов

- ✅ Обліковий запис розробника Apple ($99/рік)
- ✅ App ID зареєстрований із можливістю Sign in with Apple
- ✅ Service ID налаштований для веб-аутентифікації
- ✅ Приватний ключ (.p8 файл) завантажений і захищений
- ✅ Team ID з облікового запису розробника Apple

## Швидке налаштування (5 хвилин)

Якщо у вас уже є облікові дані Apple, ось найшвидший спосіб запустити вхід через Apple:

### 1. Змінні середовища

Додайте до вашого `.env.local`:

{`AUTH_APPLE_SECRET=<ваш-jwt-токен-тут>`}

### 2. Генерація JWT токена

Запустіть наш допоміжний скрипт:

{`cd scripts
node generate-apple-jwt.js`}

Або згенеруйте вручну за допомогою Node.js:

{`import fs from 'fs';

const token = jwt.sign(
  {
    iss: 'X9EQDPCJU6', // Ваш Team ID
    iat: Math.floor(Date.now() / 1000),
    exp: Math.floor(Date.now() / 1000) + 15777000,
    aud: 'https://appleid.apple.com',
    sub: 'com.sonoratek.ring-auth',
  },
  fs.readFileSync('AuthKey_YD444LWM9J.p8'),
  { algorithm: 'ES256', keyid: 'YD444LWM9J' }
);`}

### 3. Додайте до вашого компонента входу

{`import { signIn } from 'next-auth/react'
import { AppleIcon } from '@/components/icons'

export function LoginForm() {
  return (
    
       signIn('apple')}
        className="w-full flex items-center justify-center gap-2 bg-black text-white py-3 px-4 rounded-lg hover:bg-gray-800 transition-colors"
      >
        
        Продовжити з Apple
      
    
  )
}`}

Ось і все! Вхід через Apple тепер активний у вашій програмі.

## Повний приклад реалізації

Ось повний приклад інтеграції входу через Apple у компонент входу:

// components/auth/AppleSignInButton.tsx

{`'use client'

import { signIn, getSession } from 'next-auth/react'
import { useState } from 'react'
import { AppleIcon } from '@/components/icons'

interface AppleSignInButtonProps {
  className?: string
  size?: 'sm' | 'md' | 'lg'
  variant?: 'primary' | 'secondary'
}

export function AppleSignInButton({
  className = '',
  size = 'md',
  variant = 'primary'
}: AppleSignInButtonProps) {
  const [isLoading, setIsLoading] = useState(false)

  const handleAppleSignIn = async () => {
    try {
      setIsLoading(true)

      const result = await signIn('apple', {
        callbackUrl: '/dashboard',
        redirect: false
      })

      if (result?.error) {
        console.error('Помилка входу через Apple:', result.error)
        // Обробка помилки (показати сповіщення тощо)
      } else if (result?.url) {
        window.location.href = result.url
      }
    } catch (error) {
      console.error('Вхід через Apple не вдався:', error)
    } finally {
      setIsLoading(false)
    }
  }

  const sizeClasses = {
    sm: 'py-2 px-3 text-sm',
    md: 'py-3 px-4 text-base',
    lg: 'py-4 px-6 text-lg'
  }

  const variantClasses = {
    primary: 'bg-black text-white hover:bg-gray-800 border-black',
    secondary: 'bg-white text-black border-gray-300 hover:bg-gray-50'
  }

  return (
    
      {isLoading ? (
        
      ) : (
        
      )}
      {isLoading ? 'Виконується вхід...' : 'Продовжити з Apple'}
    
  )
}`}

## Інтеграція з існуючим потоком входу

Ось як інтегрувати вхід через Apple з вашим існуючим UI аутентифікації:

// components/auth/SocialLoginSection.tsx

{`'use client'

import { signIn } from 'next-auth/react'
import { AppleIcon, GoogleIcon } from '@/components/icons'

export function SocialLoginSection() {
  const handleProviderSignIn = async (provider: 'google' | 'apple') => {
    try {
      await signIn(provider, {
        callbackUrl: '/onboarding',
        redirect: true
      })
    } catch (error) {
      console.error(`Помилка входу через ${provider}:`, error)
    }
  }

  return (
    
      
        
          
        
        
          Або продовжити з
        
      

      
         handleProviderSignIn('google')}
          className="flex items-center justify-center gap-2 py-2.5 px-4 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
        >
          
          Google
        

         handleProviderSignIn('apple')}
          className="flex items-center justify-center gap-2 py-2.5 px-4 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
        >
          
          Apple
        
      
    
  )
}`}

## Обробка зворотних викликів входу через Apple

// app/auth/callback/apple/page.tsx

{`'use client'

import { useEffect, useState } from 'react'
import { useSession } from 'next-auth/react'
import { useRouter } from 'next/navigation'

export default function AppleCallback() {
  const { data: session, status } = useSession()
  const router = useRouter()
  const [error, setError] = useState(null)

  useEffect(() => {
    if (status === 'loading') return

    if (session?.user) {
      // Успішний вхід
      router.push('/dashboard')
    } else {
      // Перевірка помилки в параметрах URL
      const urlParams = new URLSearchParams(window.location.search)
      const errorParam = urlParams.get('error')

      if (errorParam) {
        setError(`Аутентифікація не вдалася: ${errorParam}`)
      }
    }
  }, [session, status, router])

  if (status === 'loading') {
    return (
      
        
          
          Завершення входу...
        
      
    )
  }

  if (error) {
    return (
      
        
          ⚠️ {error}
           router.push('/login')}
            className="px-4 py-2 bg-black text-white rounded-lg hover:bg-gray-800"
          >
            Спробувати знову
          
        
      
    )
  }

  return null
}`}

## Кастомізація досвіду входу через Apple

### Опції стилізації

// Кастомна стилізована кнопка Apple

{`export function CustomAppleButton() {
  return (
     signIn('apple')}
      className="group relative w-full flex items-center justify-center py-3 px-4 border border-gray-300 rounded-lg hover:border-gray-400 transition-colors"
    >
      
        
          
        
      
      Увійти з Apple
    
  )
}`}

## Тестування входу через Apple

### Тестування у розробці

1. **Використовуйте TestFlight**: Apple надає TestFlight для тестування Sign in with Apple
2. **Пісочниця**: Apple надає тестові облікові записи для тестування
3. **Сертифікати розробки**: Використовуйте сертифікати розробки для тестування

### Тестування у продакшені

1. **Перевірка App Store**: Apple перевіряє реалізацію Sign in with Apple
2. **Політика приватності**: Переконайтеся, що ваша політика приватності згадує Sign in with Apple
3. **Згода користувача**: Перевірте, що користувачі розуміють, які дані Apple передає

## Усунення поширених проблем

### Помилка "Invalid Client"

// Перевірте ваші змінні середовища

{`console.log('APPLE_ID:', process.env.AUTH_APPLE_ID)
console.log('APPLE_SECRET довжина:', process.env.AUTH_APPLE_SECRET?.length)

// Перевірте відповідність Service ID
const expectedServiceId = 'com.sonoratek.ring-auth'
const actualServiceId = process.env.AUTH_APPLE_ID

if (actualServiceId !== expectedServiceId) {
  console.error('Невідповідність Service ID!')
}`}

### Проблеми з JWT токеном

// Валідація структури JWT

{`import jwt from 'jsonwebtoken'

function validateAppleJWT(token: string) {
  try {
    const decoded = jwt.decode(token, { complete: true })
    console.log('JWT Header:', decoded?.header)
    console.log('JWT Payload:', decoded?.payload)

    // Перевірка закінчення терміну
    const exp = decoded?.payload?.exp
    if (exp && exp < Date.now() / 1000) {
      console.error('JWT токен закінчився!')
    }
  } catch (error) {
    console.error('Неправильний JWT:', error)
  }
}`}

### Верифікація домену

Переконайтеся, що ваш домен правильно налаштований у Apple Developer Portal:

1. Перейдіть до конфігурації вашого Service ID
2. Додайте ваш домен до "Domains and Subdomains"
3. Додайте return URLs для вашої програми
4. Перевірте володіння доменом із файлом верифікації Apple

## Оптимізація продуктивності

### Стратегії кешування

{`// Кешування JWT токенів (регенерація кожні 5 місяців замість 6)
const JWT_CACHE_DURATION = 5 * 30 * 24 * 60 * 60 * 1000 // 5 місяців

export function getCachedAppleJWT() {
  const cached = localStorage.getItem('apple-jwt')
  const cacheTime = localStorage.getItem('apple-jwt-time')

  if (cached && cacheTime) {
    const age = Date.now() - parseInt(cacheTime)
    if (age < JWT_CACHE_DURATION) {
      return cached
    }
  }

  // Генерація нового токена
  const newToken = generateAppleJWT()
  localStorage.setItem('apple-jwt', newToken)
  localStorage.setItem('apple-jwt-time', Date.now().toString())

  return newToken
}`}

## Найкращі практики безпеки

1. **Ротація ключів**: Регулярно оновлюйте приватні ключі
2. **Ізоляція середовищ**: Використовуйте різні ключі для dev/staging/production
3. **Аудит логування**: Логуйте всі спроби аутентифікації
4. **Обмеження частоти**: Реалізуйте обмеження частоти запитів на кінцеві точки входу
5. **Валідація токенів**: Завжди валідуйте токени на стороні сервера

## Міграція з інших провайдерів

При міграції з аутентифікації тільки через Google:

{`// До (тільки Google)
 signIn('google')}>
  Увійти через Google

// Після (множинні провайдери)

   signIn('google')}>
    Увійти через Google
  
   signIn('apple')}>
    Увійти через Apple
  
`}

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