---
title: Yetkilendirme Adımları
description: ikas OAuth2 yetkilendirme akışının tüm adımları
---

ikas uygulamaları OAuth2 Authorization Code Flow kullanır. Mağaza sahibi uygulamayı iki farklı yoldan kurabilir; her iki senaryoda da Authorize ve Callback API adımları ortaktır. Farklılaşan tek nokta akışın **nasıl başladığıdır.**

## İki Başlangıç Senaryosu

### Senaryo A — ikas Dashboard'dan Kurulum

Mağaza sahibi, uygulamayı ikas Admin Panel’de App Store’dan veya “Uygulamalarım” sayfasından kurduğunda, ikas kullanıcıyı uygulamanın ikas Partner'de tanımlanan Kurulum Adresi’ne **storeName** parametresi ile yönlendirir.

<Callout type="info" title="ikas Partner">
Uygulamanızın ikas Partner konfigürasyonlarına https://partners.ikas.com/admin/application-details/{{client_id}}/configuration adresinden ulaşırsınız.
</Callout>

### Senaryo B — Uygulamadan Kurulum

Mağaza sahibi doğrudan uygulamanızı açtığında bir form aracılığıyla mağaza adını girer. Form `/api/oauth/authorize/ikas` endpoint'ine yönlendirir.

---

## Senaryo A İçin Ön Adım — ikas Partner Ayarları

OAuth akışı başlamadan önce ikas Partner Arayüzü'nde uygulamanızın kurulum adreslerini ve uygulama yetkilerini doğru şekilde tanımlamanız gerekir.

| Alan | Açıklama | Örnek |
|------|----------|-------|
| **Kurulum Adresi** | Mağaza sahibi App Store'dan kurulum başlattığında ikas'ın yönlendireceği URL | `https://your-app.com` |
| **Yönlendirme Adresi** | OAuth callback endpoint'i. Uygulamanın gönderdiği `redirect_uri` bu değerle tam eşleşmeli | `https://your-app.com/api/oauth/callback/ikas` |
| **Uygulama Yetkileri** | Mağaza sahibine onay ekranında gösterilecek scope'lar | `read_orders`, `write_orders` vb. |

<Callout type="warning" title="Yönlendirme Adresi Eşleşmesi">
Uygulamanın gönderdiği `redirect_uri` ile uygulamanın ikas Partner'deki yönlendirme adresi birebir aynı olmalıdır.
</Callout>

<Callout type="info" title="Scope Değişikliği">
Uygulama scope'larını değiştirirseniz (yeni yetki eklemek veya çıkarmak) mevcut tüm mağaza sahipleri uygulamayı yeniden yetkilendirmelidir. Hangi mağazaların yeniden yetkilendirme gerektirdiğini tespit etmek için `check-for-reauthorize` endpoint'i kullanılabilir.
</Callout>

---

## Senaryo B İçin Ön Adım — Mağaza Yetkilendirme Sayfası

Uygulama kurulumunda mağaza sahibinin mağaza adını gireceği form:

```typescript title="File: app/authorize-store/page.tsx"
'use client';

import React, { useEffect, useCallback, useState } from 'react';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import {
  Card, CardContent, CardDescription,
  CardFooter, CardHeader, CardTitle,
} from '@/components/ui/card';

const AuthorizeStorePage: React.FC = () => {
  const [storeName, setStoreName] = useState('');
  const [showError, setShowError] = useState(false);

  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    if (params.get('status') === 'fail') setShowError(true);
    const store = params.get('storeName');
    if (store) setStoreName(store);
  }, []);

  const handleInputChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      setStoreName(e.target.value);
      if (showError) setShowError(false);
    },
    [showError],
  );

  return (
    <main className="min-h-[100vh] flex flex-col items-center justify-center p-6">
      <Card className="w-full max-w-md">
        <CardHeader>
          <CardTitle>Connect your ikas store</CardTitle>
          <CardDescription>Enter your store name to authorize this app.</CardDescription>
        </CardHeader>
        {/* Form, GET isteği ile /api/oauth/authorize/ikas'a gönderir */}
        <form method="GET" action="/api/oauth/authorize/ikas" autoComplete="off">
          <CardContent className="space-y-2">
            <Label htmlFor="storeName">Store name</Label>
            <Input
              id="storeName"
              name="storeName"
              value={storeName}
              onChange={handleInputChange}
              required
              autoFocus
            />
            {showError && (
              <p className="text-sm text-destructive">An error occurred. Please try again.</p>
            )}
          </CardContent>
          <CardFooter>
            <Button type="submit" disabled={!storeName.trim()} className="w-full">
              Add to My Store
            </Button>
          </CardFooter>
        </form>
      </Card>
    </main>
  );
};

export default AuthorizeStorePage;
```

---

## 1. Ana Sayfa

Ana sayfa (`app/page.tsx`) her iki senaryoda da ortak giriş noktasıdır. `useBaseHomePage` hook'u başlangıç akışını yönetir:

```typescript title="File: app/page.tsx"
'use client';

import { useBaseHomePage } from './hooks/use-base-home-page';
import Loading from '@/components/Loading';

export default function Home() {
  useBaseHomePage();
  return <Loading />;
}
```

### `useBaseHomePage` Hook'u

```typescript title="File: app/hooks/use-base-home-page.ts"
'use client';

import { AppBridgeHelper } from '@ikas/app-helpers';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { TokenHelpers } from '@/helpers/token-helpers';

export function useBaseHomePage() {
  const [isLoading, setIsLoading] = useState(false);
  const router = useRouter();

  useEffect(() => {
    const initializeAuthFlow = async () => {
      try {
        // ikas platform yükleme göstergesini kapat
        AppBridgeHelper.closeLoader();

        // iframe içinde geçerli token var mı kontrol et
        const existingToken = await TokenHelpers.getTokenForIframeApp();

        if (existingToken) {
          // Token varsa direkt dashboard'a git
          router.push('/dashboard');
          return;
        }

        await handleAuthorizationFlow();
      } catch (error) {
        console.error('Auth flow error:', error);
        router.push('/authorize-store');
      } finally {
        setIsLoading(false);
      }
    };

    const handleAuthorizationFlow = async () => {
      // Uygulama iframe dışında mı açılmış? (doğrudan tarayıcı erişimi)
      if (window.self === window.top) {
        const urlParams = new URLSearchParams(window.location.search);
        const storeName = urlParams.get('storeName');

        if (storeName) {
          // Senaryo A: Dashboard kurulumu — storeName URL'de var, direkt OAuth başlat
          window.location.replace(`/api/oauth/authorize/ikas?storeName=${storeName}`);
          return;
        }
      }

      // Senaryo B: Uygulama kurulumu — kullanıcı mağaza adını form ile girecek
      router.push('/authorize-store');
    };

    if (isLoading) return;
    setIsLoading(true);
    initializeAuthFlow();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return { isLoading };
}
```

Hook'un karar ağacı şöyledir:

```
iframe'de geçerli token var mı?
├── Evet → /dashboard
└── Hayır
    ├── iframe dışı + URL'de storeName var mı?
    │   └── Evet → Senaryo A: /api/oauth/authorize/ikas?storeName=...
    └── Diğer durumlar → Senaryo B: /authorize-store
```

---

## 2. Authorize API (Ortak Adım)

Her iki senaryoda da `storeName` bu endpoint'e ulaştıktan sonra akış aynıdır.

```typescript title="File: app/api/oauth/authorize/ikas/route.ts"
import { config } from '@/globals/config';
import { getRedirectUri } from '@/helpers/api-helpers';
import { getSession, setSession } from '@/lib/session';
import { validateRequest } from '@/lib/validation';
import { OAuthAPI } from '@ikas/admin-api-client';
import { NextRequest, NextResponse } from 'next/server';
import z from 'zod';

const authorizeSchema = z.object({
  storeName: z.string().min(1, 'storeName is required'),
});

export async function GET(request: NextRequest) {
  const url = new URL(request.url, `http://${request.headers.get('host')}`);
  const validation = validateRequest(authorizeSchema, {
    storeName: url.searchParams.get('storeName'),
  });

  if (!validation.success) {
    return NextResponse.json({ error: validation.error }, { status: 400 });
  }

  const { storeName } = validation.data;

  // CSRF koruması için state üret ve session'a kaydet
  const state = Math.random().toFixed(16);
  const session = await getSession();
  session.state = state;
  session.storeName = storeName;
  await setSession(session);

  // ikas OAuth yetkilendirme URL'ini oluştur ve kullanıcıyı yönlendir
  const oauthBaseUrl = OAuthAPI.getOAuthUrl({ storeName });
  const authorizeUrl =
    `${oauthBaseUrl}/authorize` +
    `?client_id=${encodeURIComponent(config.oauth.clientId!)}` +
    `&redirect_uri=${encodeURIComponent(getRedirectUri(request.headers.get('host')!))}` +
    `&scope=${encodeURIComponent(config.oauth.scope)}` +
    `&state=${encodeURIComponent(state)}`;

  return NextResponse.redirect(authorizeUrl);
}
```

Kullanıcı ikas'ın onay ekranında "Uygulamayı Yükle" butonuna tıklar.

<Callout type="info" title="Geliştirme Ortamında redirect_uri">
`getRedirectUri()` fonksiyonu `redirect_uri`'yi `host` header'ından otomatik oluşturur. Bu sayede Cloudflare Tunnel gibi araçlarla çalışırken tunnel URL her yeniden başlatmada değişse bile `.env` dosyasını veya uygulamanın ikas Partner konfigürasyonunu güncellemeniz gerekmez.
</Callout>

---

## 3. Callback API (Ortak Adım)

Mağaza sahibi onay verdikten sonra ikas şu parametrelerle callback URL'inizi çağırır:

```
GET /api/oauth/callback/ikas?code={code}&storeName={storeName}&signature={signature}&state={state}
```

```typescript title="File: app/api/oauth/callback/ikas/route.ts"
export async function GET(request: NextRequest) {
  const url = new URL(request.url, `http://${request.headers.get('host')}`);
  const { searchParams } = url;

  const validation = validateRequest(callbackSchema, {
    code: searchParams.get('code'),
    state: searchParams.get('state') || undefined,
    signature: searchParams.get('signature') || undefined,
  });

  if (!validation.success) {
    return NextResponse.json({ error: validation.error }, { status: 400 });
  }

  const { code, state, signature } = validation.data;

  // 1. İmza doğrulama — HMAC-SHA256(code, clientSecret)
  if (signature && !TokenHelpers.validateCodeSignature(code, signature, config.oauth.clientSecret!)) {
    return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
  }

  // 2. State doğrulama (CSRF koruması)
  // State, Redis değil iron-session'da tutulur
  const session = await getSession();
  if (state && session.state && session.state !== state) {
    return NextResponse.json({ error: 'Invalid state parameter' }, { status: 400 });
  }

  // 3. Authorization code → access token dönüşümü
  const tokenResponse = await OAuthAPI.getTokenWithAuthorizationCode(
    {
      code,
      client_id: config.oauth.clientId!,
      client_secret: config.oauth.clientSecret!,
      redirect_uri: getRedirectUri(request.headers.get('host')!),
    },
    { storeName: session.storeName || 'api' },
  );

  // 4. Merchant ve authorized app bilgilerini çek
  const ikas = getIkas(tokenTemp as AuthToken);
  const [merchantResponse, authorizedAppResponse] = await Promise.all([
    ikas.queries.getMerchant(),
    ikas.queries.getAuthorizedApp(),
  ]);

  // 5. Token'ı veritabanına kaydet
  await AuthTokenManager.put(token);

  // 6. Kısa ömürlü JWT oluştur ve /callback sayfasına yönlendir
  const jwtToken = JwtHelpers.createToken(merchantId, authorizedAppId);
  const callbackUrl = new URLSearchParams();
  callbackUrl.set('token', jwtToken);
  callbackUrl.set('redirectUrl', redirectUrl); // ikas admin paneli URL'i
  callbackUrl.set('authorizedAppId', authorizedAppId);

  return NextResponse.redirect(
    new URL(`/callback?${callbackUrl.toString()}`, getRedirectUri(request.headers.get('host')!)),
  );
}
```

---

## 4. `/callback` Sayfası (Ortak Adım)

Sunucudan gelen JWT token'ı tarayıcıda saklar ve kullanıcıyı ikas Admin Panel'e geri yönlendirir:

```typescript title="File: app/callback/page.tsx"
'use client';

import { Suspense, useEffect } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import Loading from '@/components/Loading';
import { TokenHelpers } from '@/helpers/token-helpers';

function CallbackContent() {
  const router = useRouter();
  const searchParams = useSearchParams();

  useEffect(() => {
    (async () => {
      const params = new URLSearchParams(searchParams.toString());
      // Token'ı sessionStorage'a kaydet ve ikas admin'e yönlendir
      await TokenHelpers.setToken(router, params);
    })();
  }, [router, searchParams]);

  return <Loading />;
}

// Next.js 15: useSearchParams() Suspense boundary gerektirir
export default function CallbackPage() {
  return (
    <Suspense>
      <CallbackContent />
    </Suspense>
  );
}
```

`TokenHelpers.setToken` şunları yapar:

- `token` ve `authorizedAppId`'yi `sessionStorage`'a kaydeder
- `window.location.replace(redirectUrl)` ile kullanıcıyı ikas Admin Panel'e gönderir

<Callout type="success" title="Başarılı Yetkilendirme">
Tüm adımlar tamamlandığında kullanıcı ikas Admin Panel'deki uygulamanızın sayfasına yönlendirilir ve uygulama artık o mağaza adına token sahibidir.
</Callout>

---

## Tam Akış Özeti

| Adım | Senaryo A (Dashboard) | Senaryo B (Uygulama) |
|------|-----------------------|----------------------|
| Başlangıç | ikas `/?storeName=...`'e yönlendirir | Kullanıcı formu doldurur |
| `useBaseHomePage` | `storeName` URL'de → direkt authorize | `storeName` yok → `/authorize-store` |
| `/api/oauth/authorize/ikas` | Ortak | Ortak |
| ikas Onay Ekranı | Ortak | Ortak |
| `/api/oauth/callback/ikas` | Ortak | Ortak |
| `/callback` sayfası | Ortak | Ortak |
| Sonuç | ikas Admin Panel | ikas Admin Panel |

## Hata Durumları

Yetkilendirme sürecinde karşılaşabileceğiniz yaygın hatalar:

### Geçersiz Mağaza Adı
```typescript
// Callback API'de hata durumu - Next.js 15 best practices
const failUrl = new URL('/authorize-store', request.url);
failUrl.searchParams.set('storeName', storeName);
failUrl.searchParams.set('status', 'fail');
return NextResponse.redirect(failUrl);
```

### State Doğrulama Hatası
- `state` parametresi Redis'te bulunamazsa
- `state` değerleri eşleşmezse
- State'in süresi dolmuşsa (60 saniye TTL)

### Client Credentials Hatası
- Yanlış `client_id` veya `client_secret`
- Geçersiz `redirect_uri`
- Geçersiz `scope` tanımlaması

<Callout type="warning" title="Debug İpuçları">
Yetkilendirme sürecinde sorun yaşıyorsanız:
1. Environment variable'larınızı kontrol edin
2. Callback URL'inizin doğru tanımlandığından emin olun
3. State değişkeninin doğru kaydedilip kaydedilmediğini kontrol edin
4. Browser developer tools'da network sekmesini inceleyin
</Callout>