Skip to Content
GuidesNext.js Integration

Next.js Entegrasyonu

Solidus SDK’sını kullanarak bir Next.js uygulamasına merkeziyetsiz kimlik kimlik doğrulaması ekleyin. Bu kılavuz App Router’ı kullanır ve sunucu tarafı meydan okuma oluşturmayı, istemci tarafı DID kimlik doğrulamasını, JWT oturum yönetimini ve rota korumasını kapsar.

Ön koşullar

  • App Router ile Next.js 14+
  • Node.js 18+

1. Bağımlılıkları Kurun

Solidus bir JWT paketi yayınlamıyor (@solidus-network/jwt npm’e hiç yayınlanmadı — bkz. SDK genel bakış). Oturum token’ları, aşağıdaki DID imza doğrulamasıyla ilgisiz, sıradan bir uygulama meselesidir, bu yüzden bu kılavuz birinci sınıf Edge/Next.js ara katman desteğine sahip standart bir paket olan jose’yi kullanır.

npm install @solidus-network/sdk @solidus-network/auth jose @noble/ed25519
  • @solidus-network/sdk — DID çözümleme, kimlik bilgisi doğrulama, zincir sorguları
  • @solidus-network/auth — DID meydan okuma-yanıt kimlik doğrulama ilkelleri
  • jose — uygulamanın kendi oturum JWT’lerini imzalar ve doğrular (HMAC)
  • @noble/ed25519 — anahtar çifti üretimi

2. Ortam Değişkenleri

Proje kökünüzde bir .env.local dosyası oluşturun.

# Your app's Ed25519 private key (hex-encoded, 64 characters) — used by the # SDK for any signing operations your backend performs (e.g. did.create) SOLIDUS_PRIVATE_KEY=your_private_key_hex # Random secret for signing this app's own session JWTs (unrelated to DID # keys) — generate with: openssl rand -hex 32 SESSION_JWT_SECRET=your_session_secret_hex # Auth challenge domain — must match your deployment URL SOLIDUS_DOMAIN=localhost:3000

3. SDK Örneğini Oluşturun

Tüm sunucu tarafı kod tarafından kullanılan paylaşılan bir SDK örneği oluşturun.

// lib/solidus.ts import { createSdk } from '@solidus-network/sdk' export const sdk = createSdk({ mode: 'testnet', rpcUrl: 'https://rpc.solidus.network', signerPrivateKey: process.env.SOLIDUS_PRIVATE_KEY!, })

4. JWT Yardımcısı

Bu uygulamanın kendi oturum token’larını imzalamak ve doğrulamak için bir yardımcı modül oluşturun (adım 6’daki DID meydan okuma/sunum kontrolünden ayrı ve onun altında).

// lib/session.ts import { SignJWT, jwtVerify } from 'jose' const secret = new TextEncoder().encode(process.env.SESSION_JWT_SECRET!) interface SessionPayload { did: string credentials: string[] } export async function createSessionToken( payload: SessionPayload ): Promise<string> { return new SignJWT({ credentials: payload.credentials }) .setProtectedHeader({ alg: 'HS256' }) .setSubject(payload.did) .setAudience(process.env.SOLIDUS_DOMAIN!) .setIssuedAt() .setExpirationTime('24h') .sign(secret) } export async function verifySessionToken( token: string ): Promise<SessionPayload | null> { try { const { payload } = await jwtVerify(token, secret, { audience: process.env.SOLIDUS_DOMAIN!, }) return { did: payload.sub as string, credentials: payload['credentials'] as string[], } } catch { return null } }

5. API Rotası: Meydan Okuma Oluşturma

Bu uç nokta, kullanıcının özel anahtarıyla imzaladığı bir DID kimlik doğrulama meydan okuması üretir. createChallenge, düz, eşzamanlı bir fonksiyondur — meydan okumayı, meydan okunan DID’e anahtarlanmış olarak doğrudan döndürür; onu kalıcı hale getirmek (ör. kısa ömürlü bir depoda) rotanızın sorumluluğundadır, böylece adım 6 onu id’ye göre arayabilir.

// app/api/auth/challenge/route.ts import { NextResponse } from 'next/server' import { createChallenge } from '@solidus-network/auth' import { saveChallenge } from '@/lib/challenge-store' // your own storage export async function POST(request: Request) { const { did } = await request.json() const challenge = createChallenge(did) await saveChallenge(challenge) return NextResponse.json(challenge) // { id, did, nonce, issuedAt, expiresAt } }

6. API Rotası: Sunumu Doğrulama

Kullanıcı meydan okumayı imzaladıktan sonra, bu uç nokta sunumu doğrular ve bir oturum JWT’si düzenler. verifyPresentation, orijinal meydan okumayı, imzalanmış sunumu ve bir verificationMethod kimliğini imzalayanın ham Ed25519 açık anahtarına eşleyen bir çözümleyiciyi alır (sahibinin zincir üzerindeki DID belgesinden çözümlenir).

// app/api/auth/verify/route.ts import { NextResponse } from 'next/server' import { verifyPresentation } from '@solidus-network/auth' import { sdk } from '@/lib/solidus' import { createSessionToken } from '@/lib/session' import { loadChallenge } from '@/lib/challenge-store' import bs58 from 'bs58' export async function POST(request: Request) { const { challengeId, presentation } = await request.json() const challenge = await loadChallenge(challengeId) if (!challenge) { return NextResponse.json({ error: 'Unknown or expired challenge' }, { status: 401 }) } const verification = await verifyPresentation( challenge, presentation, async (verificationMethodId: string) => { const doc = await sdk.did.resolve(presentation.holder) const vm = doc?.verificationMethod.find((m) => m.id === verificationMethodId) if (!vm) throw new Error('Verification method not found') return bs58.decode(vm.publicKeyMultibase.slice(1)) // strip 'z' multibase prefix }, ) if (!verification.valid) { return NextResponse.json({ error: verification.error ?? 'Invalid presentation' }, { status: 401 }) } // Verify each credential referenced in the presentation, by ID const credentialTypes: string[] = [] for (const credential of presentation.verifiableCredential ?? []) { const result = await sdk.credentials.verify(credential.id) if (!result.valid) { return NextResponse.json( { error: 'Invalid credential', credentialId: credential.id }, { status: 401 } ) } credentialTypes.push(...credential.type) } // Issue this app's own session JWT const token = await createSessionToken({ did: presentation.holder, credentials: credentialTypes, }) const response = NextResponse.json({ token, did: presentation.holder, credentials: credentialTypes, }) // Also set as HTTP-only cookie for middleware access response.cookies.set('solidus-session', token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', maxAge: 60 * 60 * 24, // 24 hours path: '/', }) return response }

7. İstemci Bileşeni: Kimlik Doğrulama Düğmesi

DID kimlik doğrulama akışını tetikleyen bir istemci bileşeni.

// components/solidus-auth-button.tsx 'use client' import { useState } from 'react' // @solidus-network/auth exports verify-side primitives (createChallenge, // verifyPresentation) — it does not ship a browser wallet connector. Signing // the challenge happens wherever the user's private key lives (a browser // extension, a deep link into the Identity app, or your own key management). // `signChallengeWithWallet` below is a placeholder for that integration. declare function signChallengeWithWallet( did: string, nonce: string, ): Promise<{ verificationMethod: string; jws: string }> interface SolidusAuthButtonProps { did: string onSuccess: (session: { did: string; token: string }) => void onError?: (error: Error) => void } export function SolidusAuthButton({ did, onSuccess, onError, }: SolidusAuthButtonProps) { const [loading, setLoading] = useState(false) async function handleAuth() { setLoading(true) try { // Step 1: Request a challenge from the server for this DID const challengeRes = await fetch('/api/auth/challenge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ did }), }) const challenge = await challengeRes.json() // challenge = { id, did, nonce, issuedAt, expiresAt } // Step 2: Sign the challenge nonce with the user's DID key const { verificationMethod, jws } = await signChallengeWithWallet(did, challenge.nonce) // Step 3: Build the VerifiablePresentation (see the // @solidus-network/auth reference for the exact shape) const presentation = { '@context': ['https://www.w3.org/2018/credentials/v1'], type: ['VerifiablePresentation'], holder: did, proof: { type: 'Ed25519Signature2020', created: new Date().toISOString(), challenge: challenge.nonce, proofPurpose: 'authentication', verificationMethod, jws, }, } // Step 4: Send the challenge ID + signed presentation to the server const verifyRes = await fetch('/api/auth/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ challengeId: challenge.id, presentation }), }) if (!verifyRes.ok) { throw new Error('Verification failed') } const session = await verifyRes.json() onSuccess({ did: session.did, token: session.token }) } catch (err) { onError?.(err instanceof Error ? err : new Error(String(err))) } finally { setLoading(false) } } return ( <button onClick={handleAuth} disabled={loading} style={{ padding: '12px 24px', borderRadius: '8px', border: 'none', backgroundColor: '#0A0A0A', color: '#FFFFFF', fontSize: '16px', cursor: loading ? 'not-allowed' : 'pointer', opacity: loading ? 0.7 : 1, }} > {loading ? 'Connecting...' : 'Sign in with Solidus'} </button> ) }

8. Ara Katman: Rotaları Koruma

Kimlik doğrulaması gerektiren rotaları korumak için Next.js ara katmanını kullanın.

// middleware.ts import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' import { jwtVerify } from 'jose' const PROTECTED_PATHS = ['/dashboard', '/profile', '/settings'] const secret = new TextEncoder().encode(process.env.SESSION_JWT_SECRET!) export async function middleware(request: NextRequest) { const isProtected = PROTECTED_PATHS.some((path) => request.nextUrl.pathname.startsWith(path) ) if (!isProtected) { return NextResponse.next() } const token = request.cookies.get('solidus-session')?.value if (!token) { return NextResponse.redirect(new URL('/login', request.url)) } try { const { payload } = await jwtVerify(token, secret, { audience: process.env.SOLIDUS_DOMAIN!, }) // Attach user info to headers for downstream use const response = NextResponse.next() response.headers.set('x-solidus-did', payload.sub as string) return response } catch { // Invalid or expired token — clear cookie and redirect const response = NextResponse.redirect(new URL('/login', request.url)) response.cookies.delete('solidus-session') return response } } export const config = { matcher: ['/dashboard/:path*', '/profile/:path*', '/settings/:path*'], }

9. Kullanıcı Kimliğini Görüntüleme

Oturumu okuyan ve kullanıcının DID’ini ve kimlik bilgilerini görüntüleyen bir sunucu bileşeni.

// app/dashboard/page.tsx import { cookies } from 'next/headers' import { verifySessionToken } from '@/lib/session' import { sdk } from '@/lib/solidus' import { redirect } from 'next/navigation' export default async function DashboardPage() { const cookieStore = await cookies() const token = cookieStore.get('solidus-session')?.value if (!token) { redirect('/login') } const session = await verifySessionToken(token) if (!session) { redirect('/login') } // Resolve the user's DID document from the chain const didDocument = await sdk.did.resolve(session.did) return ( <div> <h1>Dashboard</h1> <section> <h2>Your Identity</h2> <dl> <dt>DID</dt> <dd>{session.did}</dd> <dt>Credentials</dt> <dd>{session.credentials.join(', ')}</dd> <dt>Verification Methods</dt> <dd>{didDocument.verificationMethod?.length ?? 0}</dd> </dl> </section> </div> ) }

Tam Proje Yapısı

my-nextjs-app/ .env.local lib/ solidus.ts # SDK instance session.ts # JWT sign/verify helpers app/ api/ auth/ challenge/ route.ts # POST — create auth challenge verify/ route.ts # POST — verify presentation, issue JWT dashboard/ page.tsx # Protected page showing user identity login/ page.tsx # Login page with SolidusAuthButton components/ solidus-auth-button.tsx middleware.ts # Route protection

Sonraki Adımlar

Last updated on