Hızlı Başlangıç Rehberi

Bu rehber, Atlantic ID'yi projenize entegre etmek için gerekli tüm adımları içerir. Yaklaşık 10 dakikada ilk test entegrasyonunuzu tamamlayabilirsiniz.


Başlamadan Önce

Gereksinimler

  • Atlantic ID Developer Console hesabı
  • HTTPS destekli bir web uygulaması (production için)
  • Temel OAuth 2.0 / OIDC bilgisi (önerilir)

Öğrenecekleriniz

Bu rehberde şunları öğreneceksiniz:

  1. Developer Console'da client oluşturma
  2. PKCE ile authorization flow başlatma
  3. Token exchange yapma
  4. Kullanıcı bilgilerini alma
  5. Token'ları doğrulama

Adım 1: Developer Console'da Client Oluşturma

1.1. Console'a Giriş Yapın

https://id.codeatlantis.com/dev adresine gidin ve Atlantic ID hesabınızla giriş yapın.

1.2. Yeni Client Oluşturun

"Create New Client" butonuna tıklayın ve formu doldurun:

Client Bilgileri

Client Name:

My Application (Development)

Kullanıcılara gösterilen uygulama adı

Client Type:

  • Public: Mobil uygulamalar, SPA (React, Vue, Angular)
  • Confidential: Backend uygulamalar (Node.js, PHP, Python, Java)

💡 İpucu: Client secret'ı güvenli saklayamıyorsanız (frontend apps) "Public" seçin.

Redirect URIs:

http://localhost:3000/auth/callback
https://yourapp.com/auth/callback

Her satıra bir URI. Geliştirme ve production URI'lerini şimden ekleyin.

Allowed Scopes:

openid profile email phone offline_access

Başlangıç için tüm scope'ları seçebilirsiniz.

1.3. Client Credentials'ı Kaydedin

Client oluşturduktan sonra aşağıdaki bilgileri not edin:

Client ID: cli_abc123xyz789
Client Secret: sec_def456uvw012 (sadece Confidential için)

⚠️ UYARI: client_secret değerini asla frontend kodunda, Git repository'sinde veya public yerlerde paylaşmayın!


Adım 2: PKCE Implementation

PKCE (Proof Key for Code Exchange), authorization code'un çalınmasını engelleyen bir güvenlik mekanizmasıdır. Public client'lar için zorunludur.

2.1. Code Verifier Oluşturma

JavaScript/TypeScript:

// 43-128 karakter arası rastgele string
function generateCodeVerifier() {
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);
  return base64UrlEncode(array);
}

function base64UrlEncode(buffer) {
  return btoa(String.fromCharCode(...buffer))
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=/g, '');
}

const codeVerifier = generateCodeVerifier();
// Örnek: "dBjftJeZ4CVP-mB92K27uhbUbP1E_4jY3F_EA2ZXCUE"

Python:

import secrets
import hashlib
import base64

def generate_code_verifier():
    code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).decode('utf-8')
    return code_verifier.rstrip('=')

code_verifier = generate_code_verifier()

PHP:

function generateCodeVerifier(): string {
    $randomBytes = random_bytes(32);
    return rtrim(strtr(base64_encode($randomBytes), '+/', '-_'), '=');
}

$codeVerifier = generateCodeVerifier();

2.2. Code Challenge Oluşturma

Code verifier'ın SHA256 hash'ini alın:

JavaScript/TypeScript:

async function generateCodeChallenge(verifier) {
  const encoder = new TextEncoder();
  const data = encoder.encode(verifier);
  const hash = await crypto.subtle.digest('SHA-256', data);
  return base64UrlEncode(new Uint8Array(hash));
}

const codeChallenge = await generateCodeChallenge(codeVerifier);
// Örnek: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"

Python:

def generate_code_challenge(verifier):
    digest = hashlib.sha256(verifier.encode('utf-8')).digest()
    challenge = base64.urlsafe_b64encode(digest).decode('utf-8')
    return challenge.rstrip('=')

code_challenge = generate_code_challenge(code_verifier)

PHP:

function generateCodeChallenge(string $verifier): string {
    $hash = hash('sha256', $verifier, true);
    return rtrim(strtr(base64_encode($hash), '+/', '-_'), '=');
}

$codeChallenge = generateCodeChallenge($codeVerifier);

2.3. Code Verifier'ı Saklayın

Code verifier'ı güvenli bir yerde saklayın (token exchange'de kullanacaksınız):

Browser (Frontend):

sessionStorage.setItem('pkce_verifier', codeVerifier);

Backend (Session):

// Express.js
req.session.pkceVerifier = codeVerifier;

// PHP
$_SESSION['pkce_verifier'] = $codeVerifier;

Adım 3: Authorization Flow Başlatma

3.1. State Parametresi Oluşturma (CSRF Koruması)

function generateState() {
  const array = new Uint8Array(16);
  crypto.getRandomValues(array);
  return base64UrlEncode(array);
}

const state = generateState();
sessionStorage.setItem('oauth_state', state);

3.2. Authorization URL Oluşturma

const authUrl = new URL('https://id.codeatlantis.com/oauth/authorize');

authUrl.searchParams.set('client_id', 'cli_abc123xyz789');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('scope', 'openid profile email');
authUrl.searchParams.set('redirect_uri', 'http://localhost:3000/auth/callback');
authUrl.searchParams.set('state', state);
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');

// Opsiyonel: Nonce ekleyebilirsiniz (ID token replay attack koruması)
const nonce = generateState(); // Aynı fonksiyonu kullanabilirsiniz
authUrl.searchParams.set('nonce', nonce);
sessionStorage.setItem('oauth_nonce', nonce);

3.3. Kullanıcıyı Yönlendirme

window.location.href = authUrl.toString();

Oluşan URL örneği:

https://id.codeatlantis.com/oauth/authorize?
  client_id=cli_abc123xyz789&
  response_type=code&
  scope=openid+profile+email&
  redirect_uri=http://localhost:3000/auth/callback&
  state=xyz789abc&
  code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
  code_challenge_method=S256&
  nonce=def456ghi

Adım 4: Callback Handling

Kullanıcı Atlantic ID'de login olduktan sonra, redirect_uri adresinize şu parametrelerle döner:

http://localhost:3000/auth/callback?code=AQCxxxxxxxxxxxxxx&state=xyz789abc

4.1. State Doğrulama

const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const returnedState = urlParams.get('state');
const storedState = sessionStorage.getItem('oauth_state');

if (returnedState !== storedState) {
  throw new Error('State mismatch - possible CSRF attack');
}

// Hata kontrolü
const error = urlParams.get('error');
if (error) {
  const errorDescription = urlParams.get('error_description');
  throw new Error(`OAuth error: ${error} - ${errorDescription}`);
}

4.2. Authorization Code'u Backend'e Gönderme

Frontend'den Backend'e:

// Code'u backend'e gönderin (cookie, header, vb. ile)
const response = await fetch('/api/auth/callback', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    code: code,
    code_verifier: sessionStorage.getItem('pkce_verifier')
  })
});

const tokens = await response.json();

Adım 5: Token Exchange

Backend'de authorization code'u token'larla değiştirin:

5.1. Token Endpoint'e İstek

Node.js (fetch):

const response = await fetch('https://id.codeatlantis.com/oauth/token', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: authCode,
    redirect_uri: 'http://localhost:3000/auth/callback',
    client_id: 'cli_abc123xyz789',
    code_verifier: codeVerifier,
    // Confidential client için:
    // client_secret: 'sec_def456uvw012'
  })
});

if (!response.ok) {
  const error = await response.json();
  throw new Error(`Token exchange failed: ${error.error}`);
}

const tokens = await response.json();

PHP:

$data = [
    'grant_type' => 'authorization_code',
    'code' => $authCode,
    'redirect_uri' => 'http://localhost:3000/auth/callback',
    'client_id' => 'cli_abc123xyz789',
    'code_verifier' => $codeVerifier,
    // Confidential için:
    // 'client_secret' => 'sec_def456uvw012'
];

$ch = curl_init('https://id.codeatlantis.com/oauth/token');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/x-www-form-urlencoded'
]);

$response = curl_exec($ch);
$tokens = json_decode($response, true);

5.2. Token Response

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImF0bGFudGljX2lkXzIwMjQifQ...",
  "token_type": "Bearer",
  "expires_in": 900,
  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImF0bGFudGljX2lkXzIwMjQifQ...",
  "refresh_token": "RT_xxxxxxxxxxxxxxxxxxxxxx",
  "scope": "openid profile email"
}

5.3. Token'ları Güvenli Saklama

Backend (Önerilen):

// Session'da sakla
req.session.accessToken = tokens.access_token;
req.session.refreshToken = tokens.refresh_token;

// Veya encrypted cookie
res.cookie('access_token', tokens.access_token, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax',
  maxAge: 900000 // 15 dakika
});

Adım 6: Kullanıcı Bilgilerini Alma

6.1. UserInfo Endpoint'e İstek

const userResponse = await fetch('https://id.codeatlantis.com/oauth/userinfo', {
  headers: {
    'Authorization': `Bearer ${tokens.access_token}`
  }
});

const user = await userResponse.json();

6.2. User Response

{
  "sub": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Görkem Yılmaz",
  "email": "gorkem@codeatlantis.com",
  "email_verified": true,
  "phone_number": "+905001234567",
  "picture": "https://id.codeatlantis.com/avatar/550e8400.jpg",
  "updated_at": 1735686000
}

6.3. Kullanıcıyı Veritabanınıza Kaydetme

// Örnek: User oluştur veya güncelle
const dbUser = await db.users.upsert({
  where: { atlanticId: user.sub },
  update: {
    name: user.name,
    email: user.email,
    picture: user.picture,
    updatedAt: new Date()
  },
  create: {
    atlanticId: user.sub,
    name: user.name,
    email: user.email,
    emailVerified: user.email_verified,
    picture: user.picture
  }
});

// Session'a user ID'yi kaydet
req.session.userId = dbUser.id;

Adım 7: ID Token Doğrulama

ID token'ı mutlaka backend'de doğrulayın:

7.1. JWKS Public Key ile Doğrulama

Node.js:

const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');

const client = jwksClient({
  jwksUri: 'https://id.codeatlantis.com/oauth/jwks',
  cache: true,
  cacheMaxAge: 86400000
});

function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    if (err) return callback(err);
    callback(null, key.getPublicKey());
  });
}

try {
  const decoded = await new Promise((resolve, reject) => {
    jwt.verify(tokens.id_token, getKey, {
      issuer: 'https://id.codeatlantis.com',
      audience: 'cli_abc123xyz789',
      algorithms: ['RS256']
    }, (err, decoded) => {
      if (err) reject(err);
      else resolve(decoded);
    });
  });

  console.log('Verified user:', decoded.sub, decoded.email);
} catch (error) {
  console.error('ID token verification failed:', error);
}

7.2. Nonce Kontrolü (Opsiyonel ama Önerilen)

const storedNonce = sessionStorage.getItem('oauth_nonce');
if (decoded.nonce !== storedNonce) {
  throw new Error('Nonce mismatch - possible replay attack');
}

Tam Örnek: React SPA

// src/hooks/useAuth.js
import { useState, useEffect } from 'react';

export function useAuth() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Callback handling
    const urlParams = new URLSearchParams(window.location.search);
    const code = urlParams.get('code');

    if (code) {
      handleCallback(code);
    } else {
      checkSession();
    }
  }, []);

  async function handleCallback(code) {
    try {
      const verifier = sessionStorage.getItem('pkce_verifier');
      const state = urlParams.get('state');
      const storedState = sessionStorage.getItem('oauth_state');

      if (state !== storedState) throw new Error('State mismatch');

      // Backend'e code gönder
      const response = await fetch('/api/auth/exchange', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ code, code_verifier: verifier })
      });

      const { user } = await response.json();
      setUser(user);

      // Cleanup
      sessionStorage.removeItem('pkce_verifier');
      sessionStorage.removeItem('oauth_state');
      window.history.replaceState({}, '', '/');
    } catch (error) {
      console.error('Auth failed:', error);
    } finally {
      setLoading(false);
    }
  }

  async function checkSession() {
    try {
      const response = await fetch('/api/auth/me');
      if (response.ok) {
        const { user } = await response.json();
        setUser(user);
      }
    } finally {
      setLoading(false);
    }
  }

  async function login() {
    const { verifier, challenge } = await generatePKCE();
    const state = generateRandomString();

    sessionStorage.setItem('pkce_verifier', verifier);
    sessionStorage.setItem('oauth_state', state);

    const authUrl = new URL('https://id.codeatlantis.com/oauth/authorize');
    authUrl.searchParams.set('client_id', process.env.REACT_APP_CLIENT_ID);
    authUrl.searchParams.set('response_type', 'code');
    authUrl.searchParams.set('scope', 'openid profile email');
    authUrl.searchParams.set('redirect_uri', window.location.origin + '/auth/callback');
    authUrl.searchParams.set('state', state);
    authUrl.searchParams.set('code_challenge', challenge);
    authUrl.searchParams.set('code_challenge_method', 'S256');

    window.location.href = authUrl.toString();
  }

  async function logout() {
    await fetch('/api/auth/logout', { method: 'POST' });
    setUser(null);
  }

  return { user, loading, login, logout };
}

Sık Karşılaşılan Hatalar

redirect_uri_mismatch

Neden: Redirect URI console'dakiyle eşleşmiyor
Çözüm: Console'dan URI'yi kontrol edin, protocol ve trailing slash dikkat

invalid_grant

Neden: Authorization code geçersiz/expired
Çözüm: Code'lar tek kullanımlık ve 5 dk geçerli

invalid_request (code_challenge required)

Neden: PKCE parametreleri eksik
Çözüm: code_challenge ve code_challenge_method gönderin


Sonraki Adımlar

Tebrikler! İlk entegrasyonunuzu tamamladınız.

Şimdi bunları inceleyin:


Yardım

Takıldığınız bir yer mi var?