Reference / Auth Projects

Auth Projects

Firebase Auth alternatifi — kendi uygulamanızın end-user havuzunu Sentroy üzerinde host edin. Email/password, 6 social provider federation (Google, GitHub, Facebook, Microsoft, X, Apple), passkey (WebAuthn), TOTP MFA, magic link, invitation flow, webhook delivery, self-service hesap yönetimi ve Sentroy-hosted UI dahil. Per-project RS256 JWT + JWKS publish + RFC 9700 refresh token rotation.

"Sign in with Sentroy" vs Auth Project#

İki ürün farklı problemleri çözer — başlamadan önce hangisi sizin için doğru olduğuna karar verin.

Sign in with SentroyAuth Project
Kullanıcı tabanıSentroy hesabı zaten varKendi kullanıcı havuzunuz (Sentroy bilmez)
PatternOAuth 2.0 / OIDC federationDirekt signup/login API + SDK
AkışRedirect → consent → callbackForm submit → token döner
JWTSentroy global key (HS/RS256)Per-project RS256 + JWKS
BrandingSentroy consent screenTam sizin (logo, renk, copy)
Benzer ürünSign in with Google/AppleFirebase Auth, Auth0, Clerk
SDKHerhangi OAuth lib (NextAuth, Authlib)@sentroy-co/client-sdk/auth

Hızlı karar: Kullanıcılarınızın zaten Sentroy hesabıyla giriş yapmasını istiyorsanız (örn. iç araç) → Sign in with Sentroy. Kendi kullanıcı havuzunuzu sıfırdan host etmek istiyorsanız (consumer ürün, multi-tenant SaaS) → Auth Project, bu sayfa.

Setup#

Sentroy dashboard'unda bir Auth Project oluşturun — sonra SDK'yı backend'inize bağlayın.

1. auth.sentroy.com'a login olup Auth Projects sidebar entry'sinden yeni proje oluşturun. Wizard sizi şu adımlardan geçirir:

  • Name + Slug — slug public URL'lerde geçer (auth.sentroy.com/p/<slug>), sonradan değişmez.
  • Branding — primary color + display name + logo URL. Mail template'i, verify-email / reset-password / login / signup landing page'lerinde marka kimliğiniz olur.
  • Email verification — açıkken signup sonrası kullanıcı mail link tıklamadan login yapamaz.
  • Magic link — passwordless login akışı.
  • Allowed origins (CORS) — browser'dan public auth API'yi çağıracak origin'leriniz. Boş bırakırsanız yalnız server-to-server kullanım (browser CORS reddedilir).
  • Social providers — Google / GitHub / Facebook / Microsoft / X / Apple credentials (her biri opsiyonel). Tetiklendiğinde Sentroy-hosted authorize URL üretilir.

Create response'unda plaintext API key (aps_...) bir kez gösterilir — RP backend'inizin env'ine kopyalayın (örn. SENTROY_AUTH_API_KEY). DB'de hash'i kalır, plaintext'i geri alamazsınız; sızdırılırsa dashboard'dan rotate edin.

Quickstart#

SDK'yı kurun, projeyi initialize edin, ilk signup'ı yapın.

npm: npm install @sentroy-co/client-sdk (v2.13.9+ — auth modülü dahil)

Passkey desteği isterseniz opsiyonel peer dep: npm install @simplewebauthn/browser

// app/lib/sentroy-auth.ts
import { SentroyAuth } from "@sentroy-co/client-sdk/auth"

export const auth = new SentroyAuth({
  projectSlug: "acme-app",
  apiKey: process.env.NEXT_PUBLIC_SENTROY_AUTH_API_KEY!,
  storage: "localStorage", // "memory" | "localStorage" | custom adapter
})

// Firebase tarzı subscription
auth.onAuthStateChanged((user) => {
  console.log(user ? "signed in: " + user.email : "signed out")
})

// Signup — emailVerification açıksa tokens dönmez, mail gönderilir
await auth.signUp({ email: "alice@example.com", password: "hunter2-strong" })

// Login — MFA-aware discriminated union
const out = await auth.signIn({
  email: "alice@example.com",
  password: "hunter2-strong",
  rememberMe: true, // refresh token TTL'i uzar (30g → 90g)
})

if (out.kind === "mfa") {
  // Kullanıcıdan TOTP code veya recovery code iste
  const code = prompt("MFA code")
  await auth.verifyMfa({ mfaToken: out.data.mfaToken, code })
} else {
  // out.data.user, out.data.accessToken hazır
  console.log("logged in:", out.data.user)
}

await auth.signOut()

React SDK#

@sentroy-co/client-sdk/auth/react — Provider + 5 reactive hook.

SentroyAuthProvider tek bir SDK instance tutar; tüm hook'lar onun üzerinden çalışır. Provider mount'ta storage'dan restore + (varsa) social-login fragment'ı consume eder.

HookDönerKullanım
useAuth(){ auth, user, loading, signIn, signUp, signOut, sendPasswordReset, verifyEmail, verifyMfa, sendMagicLink, consumeMagicLink, acceptInvitation, socialAuthorizeUrl, consumeRedirectFragment }Tüm auth aksiyonları + reactive user state
useUser()SentroyAuthUser | nullSadece current user istenirse
useSessions(){ sessions, loading, error, refresh, revoke }Active session list — security/devices sayfası
useActivity(){ activity, loading, error, refresh }Audit log — login/password-change/MFA/etc
useMfa(){ status, loading, error, refresh, enrollTotp, verifyTotpEnrollment, disableTotp }TOTP enrollment + status
usePasskeys(){ passkeys, loading, error, refresh, register, remove }Passkey list/register/delete (WebAuthn)
"use client"
import { useAuth, useSessions, useMfa, usePasskeys } from "@sentroy-co/client-sdk/auth/react"

export function SecurityPage() {
  const { user } = useAuth()
  const { sessions, revoke } = useSessions()
  const { status, enrollTotp, verifyTotpEnrollment, disableTotp } = useMfa()
  const { passkeys, register, remove } = usePasskeys()

  return (
    <div>
      <h2>Active sessions</h2>
      {sessions?.map((s) => (
        <div key={s.id}>
          {s.userAgent} · {s.ip}
          <button onClick={() => revoke(s.id)}>Revoke</button>
        </div>
      ))}

      <h2>Two-factor auth</h2>
      {status?.enrolled ? (
        <button onClick={() => disableTotp(currentPassword)}>Disable TOTP</button>
      ) : (
        <button onClick={async () => {
          const { secret, otpauthUri } = await enrollTotp()
          // QR code'u otpauthUri'den render et, kullanıcı 6-digit girip:
          await verifyTotpEnrollment(code)
        }}>Enroll TOTP</button>
      )}

      <h2>Passkeys</h2>
      <button onClick={() => register("MacBook Touch ID")}>Add passkey</button>
      {passkeys?.map((p) => (
        <div key={p.id}>
          {p.deviceName} <button onClick={() => remove(p.id)}>Remove</button>
        </div>
      ))}
    </div>
  )
}

React Native / Expo#

@sentroy-co/client-sdk/auth Expo'da çalışır — sadece storage adapter'ı geçmen yeterli. WebAuthn passkey'leri web-only.

SDK platform-agnostic core ile build edilir; React Native'delocalStorage yoktur, bu yüzden storage adapter verilmelidir. İki yaygın seçenek: AsyncStorage (hızlı, encrypted değil) veya SecureStore (iOS Keychain / Android Keystore — refresh token gibi long-lived sırlar için önerilir). Sosyal login için expo-web-browser kullanılır (in-app browser session).

Install: expo install @react-native-async-storage/async-storage expo-secure-store expo-web-browser

// app/lib/sentroy-auth.ts
import AsyncStorage from "@react-native-async-storage/async-storage"
import { SentroyAuth, type SentroyAuthStorage } from "@sentroy-co/client-sdk/auth"

function createAsyncStorageAdapter(): SentroyAuthStorage {
  return {
    getItem: (key) => AsyncStorage.getItem(key),
    setItem: (key, value) => AsyncStorage.setItem(key, value),
    removeItem: (key) => AsyncStorage.removeItem(key),
  }
}

export const auth = new SentroyAuth({
  projectSlug: "acme-app",
  apiKey: process.env.EXPO_PUBLIC_SENTROY_AUTH_API_KEY!,
  storage: createAsyncStorageAdapter(),
})

// Aynı SDK API — tüm signIn/signUp/onAuthStateChanged Web'le birebir:
auth.onAuthStateChanged((user) => {
  console.log(user ? "signed in " + user.email : "signed out")
})

Hydration race — loading guard#

SDK ilk mount'ta storage'dan token rehydrate eder — bu async. Splash/spinner göstermezsen kullanıcı kısa süreliğine "signed-out" görüp anında "signed-in"e fliplar. useAuth().loading tam bunun için var:

// app/App.tsx
import { SentroyAuthProvider, useAuth } from "@sentroy-co/client-sdk/auth/react"
import { ActivityIndicator, View } from "react-native"
import { auth } from "./lib/sentroy-auth"

function Root() {
  const { user, loading } = useAuth()

  if (loading) {
    // Storage henüz rehydrate olmadı — splash göster
    return (
      <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
        <ActivityIndicator />
      </View>
    )
  }

  return user ? <HomeStack /> : <AuthStack />
}

export default function App() {
  return (
    <SentroyAuthProvider client={auth}>
      <Root />
    </SentroyAuthProvider>
  )
}

React Native gotchas#

Framework setup recipes#

Provider/init pattern'ı her popüler framework için — kopyala-yapıştır başlangıç noktası.

SDK kullanım API'si tüm framework'lerde aynı; değişen tek şey provider'ı app shell'ine nasıl wrap ettiğin. Aşağıdaki recipe'ler en hızlı yolu gösterir.

// app/providers.tsx — Provider client component
"use client"
import { SentroyAuthProvider } from "@sentroy-co/client-sdk/auth/react"

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <SentroyAuthProvider
      projectSlug="acme-app"
      apiKey={process.env.NEXT_PUBLIC_SENTROY_AUTH_API_KEY!}
      autoConsumeFragment
    >
      {children}
    </SentroyAuthProvider>
  )
}

// app/layout.tsx — Server component wraps Providers
import { Providers } from "./providers"
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}

// app/account/page.tsx — useAuth in any client component
"use client"
import { useAuth } from "@sentroy-co/client-sdk/auth/react"
export default function Account() {
  const { user, loading, signOut } = useAuth()
  if (loading) return <p>Loading…</p>
  if (!user) return <a href="/login">Sign in</a>
  return <button onClick={() => signOut()}>Sign out {user.email}</button>
}

Social federation#

6 provider — Google, GitHub, Facebook, Microsoft, X (Twitter), Apple. Per-project credentials, Sentroy-hosted callback.

Her provider için RP kendi OAuth client'ını dashboard'da tanımlar (clientId + secret; Apple için teamId/keyId/p8 privateKey). Sentroy callback URL'i her provider için sabit: https://auth.sentroy.com/api/v1/auth/<slug>/social/<provider>/callback

Provider notları

  • Google / GitHub / Facebook — standart OAuth 2.0, authorization code + email scope.
  • Microsoft — Microsoft Graph; tenant defaultcommon, B2B Entra için tenant UUID.
  • X (Twitter) — OAuth 2.0 + PKCE (S256). API tier'ı email döndürmez; Sentroy <username>@x.local placeholder üretir (user sonradan email güncelleyebilir).
  • Apple Sign In — ECDSA P-256 client_secret JWT (Sentroy her authorize'da runtime'da imzalar), Apple'ınresponse_mode=form_post akışı.

Authorize akışı (browser)

// SDK helper: redirect URL üret
import { useAuth } from "@sentroy-co/client-sdk/auth/react"

function SocialButtons() {
  const { socialAuthorizeUrl } = useAuth()
  const go = (provider: "google" | "github" | "apple") => {
    window.location.href = socialAuthorizeUrl(provider, {
      redirectUri: window.location.origin + "/auth/callback",
      rememberMe: true,
    })
  }
  return (
    <>
      <button onClick={() => go("google")}>Continue with Google</button>
      <button onClick={() => go("github")}>Continue with GitHub</button>
      <button onClick={() => go("apple")}>Continue with Apple</button>
    </>
  )
}

// Callback page — Sentroy fragment'ta access_token+refresh_token döndürür
// SentroyAuthProvider autoConsumeFragment=true ile otomatik consume eder.
// Manual: await auth.consumeRedirectFragment()

Authorize endpoint (manuel)

# Browser bu URL'e GET ile yönlendirilir (apiKey gerektirmez)
GET https://auth.sentroy.com/api/v1/auth/acme-app/social/google/authorize?
  redirectUri=https://app.example.com/auth/callback&
  rememberMe=1

# Sentroy provider'a redirect → callback'i kendisi handle eder →
# user'ı redirectUri'ye gönderir, fragment:
#   #access_token=eyJ...&refresh_token=apt_...&token_type=Bearer&expires_in=3600

MFA (TOTP)#

RFC 6238 time-based OTP — Google Authenticator, 1Password, Authy uyumlu.

Akış: enrollTotp() verifyTotpEnrollment(code) → kullanıcının next login'inde signIn kind: "mfa" döner → verifyMfa({ mfaToken, code }).

// 1. Enrollment — kullanıcı Authenticator app'ine QR'ı ekler
const { secret, otpauthUri } = await auth.mfa.enrollTotp()
// otpauthUri = "otpauth://totp/Acme:alice@example.com?secret=...&issuer=Acme"
// Bunu QR code component'ine ver, kullanıcı taradıktan sonra:

// 2. Enrollment confirm — 6-digit code'la
const { recoveryCodes } = await auth.mfa.verifyTotpEnrollment("123456")
// recoveryCodes: 10 tane one-time-use code — kullanıcıya GÖSTER ve indir/sakla de
// (forgot-totp akışında bu kodlardan biri ile recovery sign-in yapılır)

// 3. Sonraki login akışı — discriminated union
const out = await auth.signIn({ email, password })
if (out.kind === "mfa") {
  const code = prompt("6-digit code")
  await auth.verifyMfa({ mfaToken: out.data.mfaToken, code })
  // veya: await auth.verifyMfa({ mfaToken, recoveryCode: "..." })
}

// Disable — current password ile re-auth
await auth.mfa.disableTotp("currentPassword")

Status check: await auth.mfa.getStatus(){ enrolled, factorType, verifiedAt, recoveryCodesRemaining }.

Passkey / WebAuthn#

Şifresiz, phishing-resistant authentication — Touch ID, Face ID, hardware key.

İki akış: register (giriş yapmış kullanıcıya yeni passkey ekleme) ve authenticate (passkey ile sign-in).

// Kayıtlı user yeni passkey ekler
await auth.passkey.register("MacBook Touch ID")
// → browser WebAuthn prompt'u, başarılıysa passkey listesine eklenir

// List + delete
const keys = await auth.passkey.list()
// [{ id, credentialIdPrefix, deviceName, transports, lastUsedAt, createdAt }]
await auth.passkey.delete(keys[0].id)

// Passwordless sign-in
const { user } = await auth.passkey.authenticate({
  email: "alice@example.com", // opsiyonel — varsa o user'ın passkey'leri allow-listed
  rememberMe: true,
})
// → session kuruldu

React: usePasskeys() hook'u list + register + remove'u reactive sağlar (mutation sonrası otomatik refresh).

Invitation flow#

Admin başka birini kullanıcı havuzunuza davet eder — mail link ile aktivasyon + password set.

Dashboard'da Users sayfasından Invite user ile davet gönderilir. Davet alan kullanıcıya mail gider: https://auth.sentroy.com/p/<slug>/invitation?token=...(Sentroy-hosted) ya da RP kendi sayfasında consume edebilir.

// RP'nin /invitation/accept sayfası — token URL'den gelir
const token = new URLSearchParams(location.search).get("token")!

const { user } = await auth.acceptInvitation({
  token,
  password: "newPasswordChosen",
  displayName: "Alice",
})
// → hesap create + session kuruldu, redirect home'a

Server-side admin SDK ile davet üretmek için dashboard endpoint'i kullanılır (POST /api/companies/{slug}/auth-projects/{id}/invitations — cookie auth). v2'de public API katmanında aps_ ile invite create endpoint'i planlandı.

Self-service /me endpoints#

Kullanıcının kendi hesabını yönetmesi — şifre/email değiştir, hesap sil, session/activity gör.

Tüm /me/* endpoint'leri kullanıcının access token'ı (user JWT) ile auth'lanır. SDK üzerinden çağrı:

// Profil + üyelik
const me = await auth.getCurrentUser()                     // canlı DB read
const sessions = await auth.listSessions()                  // active sessions
await auth.revokeSession(sessionId)                         // belirli session'ı kapat

// Şifre değiştir — tüm sessions revoke + local session clear
await auth.changePassword({ currentPassword, newPassword })

// Email değiştir — confirmation mail yeni adrese gider
await auth.requestEmailChange({ newEmail, currentPassword })
// User mail'i tıklayınca:
await auth.confirmEmailChange(tokenFromMailLink)
// → email update + tüm sessions revoke + local clear

// Hesap sil — iki adımlı (confirmation mail)
await auth.requestAccountDeletion(currentPassword)
// Mail link:
await auth.confirmAccountDeletion(tokenFromMailLink)
// → hesap hard-delete, local session clear

// Activity log — login/password-change/email-change/MFA/passkey/social events
const activity = await auth.getActivity()
// [{ id, action, ipAddress, createdAt, details }]

React: useSessions() ve useActivity() hook'larını kullanmak en temizi — mutation sonrası otomatik refresh.

User data management#

Sentroy auth essentials'i tutar; uygulama-spesifik data sizin DB'nizde — strateji ve sync pattern.

Sentroy yalnızca auth için gerekli alanları saklar: id, email, emailVerified, displayName, image, locale, metadata (~16KB cap), createdAt, lastLoginAt, lastLoginIp. Aboneliğiniz, siparişleriniz, post'larınız, kullanıcı tercih JSON'unuz — bunlar sizin DB'nizde kalır ve Sentroy sub claim'i (= user.id) ile foreign-key'lenir.

Sentroy vs you — kim neyi tutar#

Sentroy ownsYou own (mirror in your DB)
id — JWT sub claimusers.sentroy_user_id (FK, indexed, unique)
email, emailVerifiedSubscription, billing, plan, role, permissions
displayName, imageUser-generated content (posts, comments, files)
locale (UI hint)App preferences, notification settings, theme
metadata — JSON ≤16KB cap, small flagsProfile detail, address, phone, social handles (bulk)
lastLoginAt, lastLoginIpActivity log, audit trail (app-specific events)
Sessions, MFA factors, passkeys, recovery codesAnything >16KB or relational (orders, projects, teams)

Kural: JWT'de claim olarak fayda görmüyorsa veya kimlik doğrulama UX'ine doğrudan değmiyorsa — sizin DB'nizde tut.

Source-of-truth + webhook mirror#

Standart pattern: user.signup webhook'unda kendi users tablonuza row create edin, sentroy_user_id ile join key olarak. user.account-deleted webhook'unda cascade delete (veya soft-delete, GDPR'ye göre):

// app/api/webhooks/sentroy-auth/route.ts
import { createHmac, timingSafeEqual } from "node:crypto"
import { db } from "@/lib/db"

export async function POST(req: Request) {
  const sig = req.headers.get("X-Sentroy-Signature") ?? ""
  const body = await req.text()
  const expected =
    "sha256=" +
    createHmac("sha256", process.env.SENTROY_WEBHOOK_SECRET!)
      .update(body)
      .digest("hex")
  if (sig.length !== expected.length || !timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
    return new Response("bad signature", { status: 401 })
  }

  const event = JSON.parse(body) as
    | { event: "user.signup"; data: { userId: string; email: string } }
    | { event: "user.account-deleted"; data: { userId: string } }
    | { event: "user.email-changed"; data: { userId: string; email: string } }

  switch (event.event) {
    case "user.signup":
      await db.user.create({
        data: {
          sentroyUserId: event.data.userId,
          email: event.data.email,
          createdAt: new Date(),
        },
      })
      break
    case "user.email-changed":
      await db.user.update({
        where: { sentroyUserId: event.data.userId },
        data: { email: event.data.email },
      })
      break
    case "user.account-deleted":
      // Cascade delete — orders, posts, comments tüm FK'lar
      await db.user.delete({ where: { sentroyUserId: event.data.userId } })
      break
  }

  return new Response(null, { status: 200 })
}

İlk istek (lazy provisioning): Webhook geç gelir veya kaçırılırsa, ilk authenticated API request'inde row eksikse upsert yap. JWT sub + email zaten elinizde.

user.metadata — ne zaman kullanmalı?#

metadata küçük, JWT'ye veya signup sırasında auth UX'ine değen flag'ler için: onboarding state, plan tier (custom claim hedefi), invitation source, marketing opt-in.

// İyi kullanım
await auth.signUp({
  email: "alice@example.com",
  password: "...",
  metadata: {
    onboarded: false,           // app routes onboarding wizard'a
    plan: "trial",              // custom claim → JWT'ye kopyalanır
    invitedBy: "alice@x.com",   // attribution
    marketingOptIn: true,
  },
})

// KÖTÜ kullanım — bulk data Sentroy'a göndermeyin
await auth.updateProfile({
  metadata: {
    addressBook: [...],         // 50KB JSON — 16KB cap aşar
    sessionHistory: [...],       // append-only log Sentroy işi değil
    creditCardLast4: "4242",    // PCI scope hedefi, kendi DB'nizde tutun
  },
})

GDPR — right to erasure + portability#

Sentroy tarafı için kullanıcı self-service account deletion'a sahip: POST /api/v1/auth/<slug>/me/account/delete-request → confirmation mail → delete-confirm hard-delete. Webhook tetiklenir; sizin DB cleanup'ı yukarıdaki sync handler halleder.

Full data export (GDPR Article 20): Sentroy tarafı için GET /me JSON döner; sizin tarafınızda sentroy_user_id ile join edip kullanıcının tüm row'larını JSON'a serialize edin — mail'leyin veya signed URL ile indir butonu sunun.

// app/api/me/export/route.ts
import { db } from "@/lib/db"
import { admin } from "@/lib/sentroy-admin"

export async function GET(req: Request) {
  const token = req.headers.get("authorization")?.replace(/^Bearer\s+/, "")
  const claims = await admin.verifyIdToken(token!)
  const sentroyMe = await admin.users.get(claims.sub) // canlı profil
  const ourRows = await db.user.findUnique({
    where: { sentroyUserId: claims.sub },
    include: { orders: true, posts: true, preferences: true },
  })

  return Response.json({
    sentroy: sentroyMe,
    application: ourRows,
    exportedAt: new Date().toISOString(),
  })
}

Inactive user cleanup#

Sentroy şu an inactive user'ı otomatik prune etmez. Auto-cleanup istiyorsanız: dashboard Users sayfasından CSV export → lastLoginAt < 18 ay önce filter → server-to-server admin SDK ile admin.users.delete(id) batch. v2'de "auto-delete after N days inactive" policy planlandı.

Soft inactivity kullanılan pattern: Mail uyarısı ("90 days inactive — log in to keep account") gönder, 30 gün daha bekle, sonra delete. Mail'i sizin uygulamanız tetikler (Sentroy'un activity webhook'u + cron job).

Sentroy-hosted UI#

Form yazmaktan kaçınmak isteyenler için — branded login/signup/verify/reset sayfaları.

Her project için Sentroy şu sayfaları otomatik host eder (branding'iniz uygulanır):

PathGörev
/p/<slug>/loginEmail+password + social + magic link + MFA tek form
/p/<slug>/signupYeni hesap form'u
/p/<slug>/verify-emailMail link landing (token consume)
/p/<slug>/reset-passwordPassword reset form
/p/<slug>/magicMagic link consume
/p/<slug>/invitationDavet kabul + password set
/p/<slug>/accountSelf-service hesap (sessions, MFA, passkey, email/password change, hesap sil)

RP kendi sayfasına ?redirectUri=... param ekleyerek geri yönlendirme yapar. Auth tokens fragment'ta döner (RP'nin SDK'sı consumeRedirectFragment() ile alır).

Webhooks#

Auth event'leri (signup, login, password-changed, vb.) sizin endpoint'inize HTTP POST.

Dashboard'da Webhooks tab'ından create. Her webhook için HMAC-SHA256 secret üretilir (plaintext bir kez gösterilir). Topic'lere abone olunur veya boş bırakılırsa hepsi gönderilir.

Topic'ler

  • user.signup — yeni kayıt
  • user.login — başarılı login (her seferinde)
  • user.password-changed — self-service change veya reset
  • user.email-changed — confirmed email change
  • user.account-locked — 5 failed login → 15dk lock
  • user.account-deleted — self-service veya admin

Payload formatı

POST https://yourapp.com/webhooks/sentroy-auth
Content-Type: application/json
User-Agent: sentroy-auth-webhook/1.0
X-Sentroy-Event: user.signup
X-Sentroy-Signature: sha256=<hex-hmac>
X-Sentroy-Delivery-Id: dlv_<random>

{
  "event": "user.signup",
  "timestamp": "2026-05-18T12:34:56.789Z",
  "data": {
    "userId": "...",
    "email": "alice@example.com",
    "emailVerified": false,
    "provider": "email" // veya "google" | "github" | ...
  }
}

Signature verify (Node)

import { createHmac, timingSafeEqual } from "node:crypto"

export async function POST(req: Request) {
  const sig = req.headers.get("X-Sentroy-Signature") // "sha256=..."
  const body = await req.text()
  const expected = "sha256=" + createHmac("sha256", process.env.SENTROY_WEBHOOK_SECRET!)
    .update(body)
    .digest("hex")

  const a = Buffer.from(sig ?? "")
  const b = Buffer.from(expected)
  if (a.length !== b.length || !timingSafeEqual(a, b)) {
    return new Response("bad signature", { status: 401 })
  }

  const event = JSON.parse(body)
  // ... process event.data
  return new Response(null, { status: 200 })
}

Retry: 3 attempt (0s / 2s / 10s backoff). 4xx (429 hariç) → deterministik fail, retry yok. 5xx ve network error → retry. Tüm attempt'ler auth_project_webhook_deliveries koleksiyonuna 30 gün TTL ile loglanır; dashboard'da Webhook deliveries tab'ında görülür.

REST endpoints#

SDK kullanmıyorsanız direkt HTTP — tüm endpoint'ler /api/v1/auth/[slug]/... altında.

Auth modes: aps_ = project API key (server-only master), user = end-user access token, none = single-use token (zaten secret).

MethodPathAuthUse
POST/signupaps_Yeni kullanıcı
POST/loginaps_Tokens veya MFA challenge
POST/login/mfa/verifyaps_TOTP code veya recovery code
POST/refreshaps_Token rotation (RFC 9700)
POST/logoutaps_Refresh token revoke
POST/verify-emailnoneEmail verify token consume
POST/password-reset/requestaps_Reset mail gönder
POST/password-reset/confirmnoneToken + yeni şifre
POST/magic-link/requestaps_Magic mail gönder
POST/magic-link/consumeaps_Magic token → login
POST/invitation/acceptaps_Davet token + password
GET/social/{provider}/authorizenoneOAuth redirect URL
GET/POST/social/{provider}/callbacknoneProvider callback (Apple POST)
POST/passkey/authenticate/beginaps_WebAuthn challenge
POST/passkey/authenticate/completeaps_Assertion → login
GET/meuserProfil (live DB)
GET/me/sessionsuserActive sessions
DELETE/me/sessions/{id}userSession revoke
POST/me/passworduserŞifre değiştir
POST/me/email/change-requestuserEmail change mail
POST/me/email/change-confirmuserToken ile email change
POST/me/account/delete-requestuserHesap sil mail
POST/me/account/delete-confirmnoneToken ile hard-delete
GET/me/activityuserAudit log
GET/me/mfauserMFA status
POST/me/mfa/totp/enrolluserTOTP secret + URI
POST/me/mfa/totp/verify-enrollmentuserCode + recovery codes
POST/me/mfa/totp/disableuserRe-auth + TOTP off
GET/me/passkeyuserPasskey list
DELETE/me/passkey/{id}userPasskey delete
POST/me/passkey/register/beginuserWebAuthn create challenge
POST/me/passkey/register/completeuserAttestation kaydet
GET/userinfouserOIDC userinfo (claims)
GET/jwks.jsonnoneProject public key set

Aşağıda 8 en sık kullanılan endpoint için TypeScript SDK / cURL / Python örnekleri. aps_ = project master API key (server-only), <access-token> = end-user JWT. Tüm path'ler https://auth.sentroy.com/api/v1/auth/<your-project-slug>/... altındadır.

POST /signup — yeni kullanıcı#

POST/api/v1/auth/{slug}/signup

Body: email (required, string), password (required, ≥8 chars, HIBP-checked), displayName (optional), locale (optional, "tr"/"en"), metadata (optional, JSON ≤16KB). Email verification açıksa response token döndürmez — mail link beklenir.

import { SentroyAuth } from "@sentroy-co/client-sdk/auth"

const auth = new SentroyAuth({
  projectSlug: "acme-app",
  apiKey: process.env.SENTROY_AUTH_API_KEY!,
})

const out = await auth.signUp({
  email: "alice@example.com",
  password: "hunter2-strong",
  displayName: "Alice",
  metadata: { plan: "trial" },
})
// emailVerification açıksa: out.kind === "verification-required"
// kapalıysa: out.kind === "tokens" → out.data.accessToken hazır

POST /login — tokens veya MFA challenge#

POST/api/v1/auth/{slug}/login

Body: email, password (required), rememberMe (optional bool; refresh TTL 30g → 90g). Response discriminated union: MFA enroll edilmişse kind: "mfa" + mfaToken döner, ardından /login/mfa/verify çağırılır.

const out = await auth.signIn({
  email: "alice@example.com",
  password: "hunter2-strong",
  rememberMe: true,
})

if (out.kind === "mfa") {
  const code = prompt("6-digit code from authenticator app")!
  await auth.verifyMfa({ mfaToken: out.data.mfaToken, code })
} else {
  // out.data.user, out.data.accessToken, out.data.refreshToken
  console.log("Signed in:", out.data.user.email)
}

POST /refresh — rotation#

POST/api/v1/auth/{slug}/refresh

RFC 9700 family-based rotation. Eski refresh token bir kez kullanılır; ikinci kullanım reuse detection tetikler ve tüm family revoke edilir. Yeni access + refresh ikilisi döner.

// SDK otomatik refresh yapar — manuel çağrı gerekmez:
const me = await auth.getCurrentUser() // 401 olursa SDK içeride refresh + retry

// Manuel (custom backend için):
const { accessToken, refreshToken } = await auth.refresh(currentRefreshToken)

POST /logout — refresh revoke#

POST/api/v1/auth/{slug}/logout

Refresh token'ı (ve family root'unu) revoke eder. Access token hâlâ TTL bitene kadar (1 saat) valid kalır — kritik logout'larda access token'ı da blacklist'lemen gerekirse POST /me/sessions/<id> DELETE kullan.

await auth.signOut()
// → POST /logout + local storage clear + onAuthStateChanged(null) fire

GET /userinfo — OIDC claims#

GET/api/v1/auth/{slug}/userinfo

OIDC standard userinfo endpoint. Auth: end-user access token (Bearer JWT). aps_ burada kabul edilmez. GET /me'den farkı: /userinfo OIDC claim isimleri döner (sub, email_verified), /me SDK profil shape'i döner.

// SDK kullanıyorsan auth.getCurrentUser() yeterli (SDK shape).
// OIDC claim shape istiyorsan raw fetch:
const accessToken = auth.getAccessToken()
const resp = await fetch(
  "https://auth.sentroy.com/api/v1/auth/acme-app/userinfo",
  { headers: { Authorization: `Bearer ${accessToken}` } }
)
const claims = await resp.json()
// { sub, email, email_verified, name, picture, locale, ... }

POST /verify-email — token consume#

POST/api/v1/auth/{slug}/verify-email

Auth: none — token zaten secret (single-use, short-lived). Body: token (required, mail link'ten). Başarıda emailVerified: true + auto-login tokens döner.

// /verify-email landing page'inde:
const token = new URLSearchParams(location.search).get("token")!
const { user, accessToken } = await auth.verifyEmail(token)
// → emailVerified=true + signed in

POST /password-reset/{request,confirm}#

POST/api/v1/auth/{slug}/password-reset/request
POST/api/v1/auth/{slug}/password-reset/confirm

İki adım: request (mail gönder — email enumeration koruması, her zaman 200) → kullanıcı mail link'i tıklar → confirm (token + yeni şifre, yeni şifre HIBP-checked). Confirm başarısında tüm session'lar revoke + auto-login.

// 1. Request reset
await auth.sendPasswordReset({
  email: "alice@example.com",
  redirectUri: "https://app.example.com/reset",
})
// → her zaman success (silent no-op if no account)

// 2. /reset page'inde, token URL param'dan:
const token = new URLSearchParams(location.search).get("token")!
const { user, accessToken } = await auth.confirmPasswordReset({
  token,
  newPassword: "newSecurePass123",
})

GET /jwks.json — server-side verify için#

GET/api/v1/auth/{slug}/jwks.json

Public RSA key set (per-project). Auth: none — public key. RP backend'iniz JWT'yi verify ederken bu endpoint'i cache'leyip (default TTL 1h, rotation grace'e uygun) kid ile eşleştirir. SDK'nın SentroyAuthAdmin.verifyIdToken metodu bunu otomatik halleder.

import { SentroyAuthAdmin } from "@sentroy-co/client-sdk/auth/admin"

const admin = new SentroyAuthAdmin({
  projectSlug: "acme-app",
  apiKey: process.env.SENTROY_AUTH_API_KEY!,
  jwksCacheTtl: 3600, // saniye — default 1h
})

// JWKS otomatik fetch + cache + kid match + signature verify
const claims = await admin.verifyIdToken(accessToken)
// claims.sub, claims.email, claims.iss, claims.aud, claims.exp

// Manuel JWKS pull (debugging / custom verify):
const jwks = await fetch(
  "https://auth.sentroy.com/api/v1/auth/acme-app/jwks.json"
).then((r) => r.json())
// { keys: [{ kty: "RSA", kid: "...", n: "...", e: "AQAB", alg: "RS256" }, ...] }

ID token claims#

Access token RS256 imzalı JWT — per-project key ile.

SDK'nın verifyIdToken() metodu JWKS'i fetch edip kid ile eşleştirir, signature + iss + aud + exp kontrol eder. JWKS cache TTL default 1 saat (rotation grace period'una uyumlu); manuel invalidation: admin.invalidateJwksCache().

{
  "sub": "auth-project-user-id",      // user id
  "email": "alice@example.com",
  "email_verified": true,
  "name": "Alice",                    // displayName
  "picture": "https://...",           // image URL
  "iss": "https://auth.sentroy.com/p/acme-app",
  "aud": "aps_a1b2c3d4e5f6",          // project API key prefix
  "iat": 1733000000,
  "exp": 1733003600,                  // 1 hour TTL

  // Eğer customClaims set'liyse:
  "plan": "pro",                      // staticClaims
  "orgId": "org_xyz"                  // fromMetadata
}

Custom JWT claims#

User metadata'sından veya statik değerlerden access token'a alan ekleyin — RP backend'i ek DB call yapmadan kullanır.

Dashboard Settings → Custom claims'ten config. İki tip:

  • From metadata — whitelist top-level key'ler; user.metadata içindeki o key JWT'ye kopyalanır. Örnek: user.metadata.orgId = "org_xyz" + whitelist ["orgId"] → orgId claim'i set.
  • Static claims — her user'a sabit eklenir (örn. project version tag, deployment env). aud/iss/sub override edilemez.

Update metadata: dashboard'dan user detail'inde edit, veya PATCH /api/companies/{slug}/auth-projects/{id}/users/{userId}.

Email templates#

Verify/reset/magic/invitation/new-device-alert/account-locked mail'leri project branding'iniz ile gönderilir.

Mail'ler Sentroy'un sistem mail platform'undan noreply@auth.sentroy.com üzerinden gider (v2'de custom from-domain). Default template'ler tr + en locale'lerinde. Dashboard → Emails tab'ından her template için override yazılabilir (LocalizedField — TR/EN sekme, aynı widget):

  • verify-email
  • password-reset
  • magic-link
  • invitation
  • new-device-alert — lastLoginIp değişince
  • account-locked — 5 failed login lockout
  • email-change-confirm
  • account-delete-confirm

Override yoksa Sentroy default template (project branding placeholder'ları ile render edilir). Placeholder'lar:

  • {projectName} — branding.displayName
  • {primaryColor} — CTA buton rengi
  • {logoUrl} — logo (yoksa text fallback)
  • {userEmail} — recipient address
  • {verifyUrl} / {resetUrl} / {magicUrl} / {invitationUrl} — action URL'leri

Migration from other auth providers#

Mevcut user pool'unuzu Auth0/Firebase/Cognito'dan Sentroy'a taşımak için CSV import.

Dashboard Users → Import butonuyla CSV upload edilir. Format:

# users.csv
email,passwordHash,passwordAlgo,emailVerified,displayName,metadata
alice@example.com,scrypt$N$r$p$salt$hash,scrypt,true,Alice,"{""plan"":""pro""}"
bob@example.com,$argon2id$v=19$...,argon2id,true,Bob,"{}"
carol@example.com,,,false,Carol,"{}"

# Hash yoksa: user "password reset gerekli" state'inde import edilir;
# ilk login denemesinde reset mail gider.

Desteklenen hash formatları: scrypt (native), argon2id, bcrypt (transparent migration — ilk login'de scrypt'e re-hash). Auth0/Firebase'in farklı hash formatları için adapter script'i SDK örneklerinde mevcut.

Import sırasında user.signup webhook'u tetiklenmez (bulk migration için). Tetiklenmesini istiyorsanız Trigger webhooks checkbox'ını işaretleyin.

User pool yönetimi (dashboard)#

auth.sentroy.com → company → Auth Projects → [proje] → Users.

Dashboard tab'ları: Overview (MAU + recent signups grafik), Users (paginated list, search, email-verified filter, per-user revoke/delete), Activity (audit log), Webhooks, Emails (template overrides), Settings (branding, password policy, allowed origins, custom claims, JWT key rotation, social providers, magic link toggle, email verification toggle, plan/quota),API keys.

Dashboard endpoint'leri cookie auth ile çalışır (cross-subdomain .sentroy.com). RP'nin server-to-server user yönetimi için aps_ public API kullanılır — admin endpoint'lerinin tam public API katmanı v2'de planlandı.

Security#

v1.62.106+ — production-ready posture.

  • Password hash: scrypt N=2^16 (OWASP minimum, pure Node — native binding gerekmez). Format self-describing: scrypt$N$r$p$salt$hash. Argon2id + bcrypt import için transparent migration.
  • HaveIBeenPwned check: signup ve password-reset confirm'de yeni şifre HIBP k-anonymity API'sine query atılır (ilk 5 char hash). Breached parolalar reddedilir.
  • Failed login lockout: 5 başarısız deneme sonrası account 15 dakika kilitli + account-locked webhook + mail. Lockout window kullanıcı başına; session/IP değil.
  • Email enumeration: password reset, magic link request her zaman 200 döner — hesap yoksa silent no-op. Signup explicit 409 döner (DX trade-off); v2'de tightening.
  • Rate limit: per-IP signup 5/dk, login 20/dk, password-reset 3/dk. Aşımda 429 + Retry-After header.
  • Refresh token rotation: RFC 9700 family-based. Reuse detection → tüm family revoke + audit log (auth-project.refresh.reuse-detected). Remember-me TTL: 30g default, 90g remember-me.
  • CORS: Project'in allowedOrigins listesi authoritative. Boş = browser çağrıları reddedilir (yalnız server-to-server).
  • JWT signing: Per-project RSA 2048-bit keypair. Public key JWKS'te publish; private key DB'de AES-GCM şifrelenmiş, sadece sign sırasında decrypt. 2-slot rotation destekli: yeni key issue'a kullanılır, eski key grace period'da (verify-only) kalır → JWKS her ikisini publish eder.
  • Social provider secret'ları: ClientSecret + Apple p8 privateKey AES-256-GCM şifreli DB'de;ENV_VAULT_MASTER_KEY ile decrypt.
  • Webhook secret: Plaintext DB'de (HMAC verify için lazım) — DB compromise threat model'inde zaten her şey kompromize; ayrıca encrypt savunma derinliği vermez.
  • Quota: Free tier 5K MAU + 100 signup/saat (atomic counter, signup endpoint'inde enforce). Aşımda 429. Paid tier unlimited.

v2 epic'leri (henüz yapılmadı)#

Şu an v1.62.106 — production-ready. Bilinen eksikler:

  • Custom domain (auth.customer.com CNAME)
  • Public admin API katmanı (aps_ ile users.list/get/update/delete — şu an dashboard cookie-auth only)
  • RBAC per-project (custom roles + permission system)
  • Anonymous users (Firebase pattern — guest → upgrade)
  • SMS MFA (TOTP zaten var, SMS factor eklenir)
  • Browser-safe public key tier (admin key separation)
  • Email enumeration protection (uniform signup response timing)
  • Stripe billing integration (free→paid plan upgrade self-service)
  • Webhook delivery retry policy customization (current: 3-attempt sabit)