Sunucu tarafı geçiş anahtarı kaydı

Genel bakış

Aşağıda, geçiş anahtarı kaydıyla ilgili temel adımlara üst düzey bir genel bakış verilmiştir:

Geçiş anahtarı kayıt akışı

  • Geçiş anahtarı oluşturma seçeneklerini tanımlayın. Bunları, geçiş anahtarı oluşturma çağrınıza (web'de navigator.credentials.create ve Android'de credentialManager.createCredential) iletebilmeniz için istemciye gönderin. Kullanıcı, geçiş anahtarı oluşturulmasını onayladıktan sonra geçiş anahtarı oluşturma çağrısı çözümlenir ve bir kimlik bilgisi PublicKeyCredential döndürür.
  • Kimlik bilgisini doğrulayın ve sunucuda saklayın.

Aşağıdaki bölümlerde her adımın ayrıntıları açıklanmaktadır.

Kimlik bilgisi oluşturma seçenekleri oluşturma

Sunucuda atmanız gereken ilk adım bir PublicKeyCredentialCreationOptions nesnesi oluşturmaktır.

Bunu yapmak için FIDO sunucu tarafı kitaplığınızı kullanın. Bu özellik genellikle sizin için bu seçenekleri oluşturabilecek bir yardımcı program işlevi sunar. SimpleWebAuthn teklifleri (örneğin, generateRegistrationOptions).

PublicKeyCredentialCreationOptions, geçiş anahtarı oluşturmak için gereken her şeyi içermelidir: kullanıcı ve kısıtlanmış taraf hakkında bilgiler ve oluşturduğunuz kimlik bilgisinin özellikleriyle ilgili yapılandırma. Bunların tümünü tanımladıktan sonra, bunları FIDO sunucu tarafı kitaplığınızda PublicKeyCredentialCreationOptions nesnesini oluşturmaktan sorumlu olan işleve gerektiği şekilde iletin.

PublicKeyCredentialCreationOptions'lik alanların bazıları sabit değer olabilir. Diğerleri sunucuda dinamik olarak tanımlanmalıdır:

  • rpId: Sunucudaki RP kimliğini doldurmak için web uygulamanızın ana makine adını veren sunucu tarafı işlevleri veya değişkenleri (ör. example.com) kullanın.
  • user.name ve user.displayName: Bu alanları doldurmak için oturum açmış kullanıcının oturum bilgilerini (veya kullanıcı kayıt sırasında geçiş anahtarı oluşturuyorsa yeni kullanıcı hesabı bilgilerini) kullanın. user.name genellikle bir e-posta adresidir ve kısıtlanmış taraf için benzersizdir. user.displayName kullanımı kolay bir ad. Tüm platformların displayName kullanmayacağını unutmayın.
  • user.id: Hesap oluşturulduktan sonra oluşturulan rastgele, benzersiz bir dizedir. Düzenlenebilir olabilecek bir kullanıcı adının aksine kalıcı olmalıdır. User-ID bir hesabı tanımlar, ancak kimliği tanımlayabilecek bilgiler (PII) içermemelidir. Muhtemelen sisteminizde zaten bir kullanıcı kimliği vardır, ancak gerekirse geçiş anahtarlarının kimliği tanımlayabilecek bilgiler (PII) içermemesi için özel olarak bir kimlik oluşturun.
  • excludeCredentials: Geçiş anahtarı sağlayıcıdan geçiş anahtarı oluşturulmasını önlemek için mevcut kimlik bilgisi kimliklerinin listesi. Bu alanı doldurmak için veritabanınızda bu kullanıcıya ait mevcut kimlik bilgilerini arayın. Zaten varsa yeni bir geçiş anahtarı oluşturulmasını önleme başlıklı makaleden ayrıntıları inceleyin.
  • challenge: Kimlik bilgisi kaydı söz konusu olduğunda, geçiş anahtarı sağlayıcısının kimliğini ve yayınladığı verileri doğrulamak için daha gelişmiş bir teknik olan onay yöntemini kullanmadığınız sürece bu soru geçerli olmaz. Ancak onay kullanmasanız bile giriş sorgulaması zorunlu bir alandır. Bu durumda, basit olması için bu sorgulamayı tek bir 0 olarak ayarlayabilirsiniz. Kimlik doğrulama için güvenli giriş sorgulaması oluşturma talimatlarını Sunucu tarafı geçiş anahtarı kimlik doğrulaması başlıklı makalede bulabilirsiniz.

Kodlama ve kod çözme

Sunucu tarafından gönderilen PublicKeyCredentialCreationOptions
PublicKeyCredentialCreationOptions sunucu tarafından gönderildi. PublicKeyCredentialCreationOptions öğesinin HTTPS üzerinden yayınlanabilmesi için challenge, user.id ve excludeCredentials.credentials değerlerinin sunucu tarafında base64URL olarak kodlanması gerekir.

PublicKeyCredentialCreationOptions, ArrayBuffer alanları içerdiği için JSON.stringify() tarafından desteklenmez. Bu, şu anda HTTPS üzerinden PublicKeyCredentialCreationOptions yayınlamak için bazı alanların base64URL kullanılarak sunucuda manuel olarak kodlanması ve ardından istemcide kodunun çözülmesi gerektiği anlamına gelir.

  • Sunucuda, kodlama ve kod çözme işlemleri genellikle FIDO sunucu tarafı kitaplığınız tarafından gerçekleştirilir.
  • İstemcide ise kodlama ve kod çözme işlemlerinin şu anda manuel olarak yapılması gerekir. Gelecekte daha kolay hale gelecektir: Seçenekleri JSON olarak PublicKeyCredentialCreationOptions biçimine dönüştürmek için bir yöntem kullanıma sunulacaktır. Chrome'da uygulamanın durumunu kontrol edin.

Örnek kod: Kimlik bilgisi oluşturma seçenekleri oluşturma

Örneklerimizde SimpleWebAuthn kitaplığını kullanıyoruz. Burada, ortak anahtar kimlik bilgisi seçeneklerinin oluşturulmasını bu komutun generateRegistrationOptions işlevine devrediyoruz.

import {
  generateRegistrationOptions,
  verifyRegistrationResponse,
  generateAuthenticationOptions,
  verifyAuthenticationResponse
} from '@simplewebauthn/server';
import { isoBase64URL } from '@simplewebauthn/server/helpers';

router.post('/registerRequest', csrfCheck, sessionCheck, async (req, res) => {
  const { user } = res.locals;
  // Ensure you nest verification function calls in try/catch blocks.
  // If something fails, throw an error with a descriptive error message.
  // Return that message with an appropriate error code to the client.
  try {
    // `excludeCredentials` prevents users from re-registering existing
    // credentials for a given passkey provider
    const excludeCredentials = [];
    const credentials = Credentials.findByUserId(user.id);
    if (credentials.length > 0) {
      for (const cred of credentials) {
        excludeCredentials.push({
          id: isoBase64URL.toBuffer(cred.id),
          type: 'public-key',
          transports: cred.transports,
        });
      }
    }

    // Generate registration options for WebAuthn create
    const options = generateRegistrationOptions({
      rpName: process.env.RP_NAME,
      rpID: process.env.HOSTNAME,
      userID: user.id,
      userName: user.username,
      userDisplayName: user.displayName || '',
      attestationType: 'none',
      excludeCredentials,
      authenticatorSelection: {
        authenticatorAttachment: 'platform',
        requireResidentKey: true
      },
    });

    // Keep the challenge in the session
    req.session.challenge = options.challenge;

    return res.json(options);
  } catch (e) {
    console.error(e);
    return res.status(400).send({ error: e.message });
  }
});

Ortak anahtarı saklama

Sunucu tarafından gönderilen PublicKeyCredentialCreationOptions
navigator.credentials.create, PublicKeyCredential nesnesi döndürür.

navigator.credentials.create istemcide başarıyla çözümlendiğinde geçiş anahtarı başarıyla oluşturulmuştur. Bir PublicKeyCredential nesnesi döndürülür.

PublicKeyCredential nesnesi, geçiş anahtarı sağlayıcısının istemcinin geçiş anahtarı oluşturma talimatına verdiği yanıtı temsil eden bir AuthenticatorAttestationResponse nesnesi içerir. Kullanıcının kimliğini daha sonra doğrulamak için kısıtlanmış taraf olarak ihtiyaç duyacağınız yeni kimlik bilgisi bilgilerini içerir. AuthenticatorAttestationResponse hakkında daha fazla bilgiyi Ek: AuthenticatorAttestationResponse bölümünde bulabilirsiniz.

PublicKeyCredential nesnesini sunucuya gönderin. Aldıktan sonra doğrulayın.

Bu doğrulama adımını FIDO sunucu tarafı kitaplığınıza aktarın. Bu amaçla genellikle bir yardımcı program işlevi sunar. SimpleWebAuthn teklifleri (örneğin, verifyRegistrationResponse). Ek: Kayıt yanıtının doğrulanması başlıklı makaleden, arka planda neler olduğunu öğrenebilirsiniz.

Doğrulama başarılı olduktan sonra kullanıcının, söz konusu kimlik bilgileriyle ilişkili geçiş anahtarıyla kimlik doğrulaması yapabilmesi için kimlik bilgisi bilgilerini veritabanınızda depolayın.

Geçiş anahtarlarıyla ilişkili ortak anahtar kimlik bilgileri için özel bir tablo kullanın. Bir kullanıcının tek bir şifresi olabilir, ancak birden fazla geçiş anahtarı olabilir. Örneğin, Apple iCloud Anahtar Zinciri ile senkronize edilen bir geçiş anahtarı ve Google Şifre Yöneticisi aracılığıyla başka bir şifre anahtarı kullanılabilir.

Aşağıda, kimlik bilgilerini depolamak için kullanabileceğiniz bir örnek şema verilmiştir:

Geçiş anahtarları için veritabanı şeması

  • Kullanıcılar tablosu:
    • user_id: Birincil kullanıcı kimliği. Kullanıcının rastgele, benzersiz, kalıcı kimliği. Bunu Kullanıcılar tablonuz için birincil anahtar olarak kullanın.
    • username. Düzenlenebilir olabilecek, kullanıcı tanımlı bir kullanıcı adı.
    • passkey_user_id: Kayıt seçeneklerinizde user.id ile gösterilen, geçiş anahtarına özel, kimliği tanımlayabilecek bilgiler (PII) içermeyen kullanıcı kimliği. Kullanıcı daha sonra kimlik doğrulaması yapmaya çalıştığında, kimlik doğrulayıcı, userHandle içindeki kimlik doğrulama yanıtında bu passkey_user_id öğesini kullanılabilir hale getirecektir. passkey_user_id öğesini birincil anahtar olarak ayarlamamanızı öneririz. Birincil anahtarlar, yaygın bir şekilde kullanıldığından sistemlerde fiili kimlik bilgisi haline gelme eğilimindedir.
  • Ortak anahtar kimlik bilgileri tablosu:
    • id: Kimlik bilgisi kimliği. Bunu Ortak anahtar kimlik bilgileri tablonuz için birincil anahtar olarak kullanın.
    • public_key: Kimlik bilgisinin ortak anahtarı.
    • passkey_user_id: Kullanıcılar tablosuyla bağlantı oluşturmak için bunu yabancı anahtar olarak kullanın.
    • backed_up: Geçiş anahtarı, geçiş anahtarı sağlayıcı tarafından senkronize ediliyorsa yedeklenir. Yedekleme durumunu saklamak, gelecekte backed_up geçiş anahtarına sahip olan kullanıcıların şifrelerini bırakmayı düşünüyorsanız kullanışlıdır. Geçiş anahtarının yedeklenip yedeklenmediğini authenticatorData ürünündeki işaretleri inceleyerek veya bu bilgilere kolayca erişebilmeniz için genellikle kullanılabilen bir FIDO sunucu tarafı kitaplık özelliğini kullanarak kontrol edebilirsiniz. Yedek uygunluğu depolamak, potansiyel kullanıcı sorgularını ele almanıza yardımcı olabilir.
    • name: İsteğe bağlı olarak, kullanıcıların kimlik bilgileri için özel adlar verebilmesine olanak tanıyan kimlik bilgisi görünen adı.
    • transports: Bir dizi transspor Aktarımların depolanması, kimlik doğrulama kullanıcı deneyimi için yararlıdır. Aktarımlar mevcut olduğunda tarayıcı uygun şekilde davranabilir ve geçiş anahtarı sağlayıcısının istemcilerle iletişim kurmak için kullandığı aktarımla eşleşen bir kullanıcı arayüzü gösterebilir. Bu, özellikle de allowCredentials alanının boş olmadığı yeniden kimlik doğrulama kullanım alanları için geçerlidir.

Geçiş anahtarı sağlayıcısı, kimlik bilgisi oluşturma zamanı ve son kullanım zamanı gibi öğeler de dahil olmak üzere diğer bilgiler kullanıcı deneyimi açısından saklanmasına yardımcı olabilir. Daha fazla bilgi için Geçiş anahtarı kullanıcı arayüzü tasarımı konusuna bakın.

Örnek kod: kimlik bilgilerini depolama

Örneklerimizde SimpleWebAuthn kitaplığını kullanıyoruz. Bu bölümde, kayıt yanıtı doğrulamasını verifyRegistrationResponse işlevine devrediyoruz.

import { isoBase64URL } from '@simplewebauthn/server/helpers';


router.post('/registerResponse', csrfCheck, sessionCheck, async (req, res) => {
  const expectedChallenge = req.session.challenge;
  const expectedOrigin = getOrigin(req.get('User-Agent'));
  const expectedRPID = process.env.HOSTNAME;
  const response = req.body;
  // This sample code is for registering a passkey for an existing,
  // signed-in user

  // Ensure you nest verification function calls in try/catch blocks.
  // If something fails, throw an error with a descriptive error message.
  // Return that message with an appropriate error code to the client.
  try {
    // Verify the credential
    const { verified, registrationInfo } = await verifyRegistrationResponse({
      response,
      expectedChallenge,
      expectedOrigin,
      expectedRPID,
      requireUserVerification: false,
    });

    if (!verified) {
      throw new Error('Verification failed.');
    }

    const { credentialPublicKey, credentialID } = registrationInfo;

    // Existing, signed-in user
    const { user } = res.locals;
    
    // Save the credential
    await Credentials.update({
      id: base64CredentialID,
      publicKey: base64PublicKey,
      // Optional: set the platform as a default name for the credential
      // (example: "Pixel 7")
      name: req.useragent.platform, 
      transports: response.response.transports,
      passkey_user_id: user.passkey_user_id,
      backed_up: registrationInfo.credentialBackedUp
    });

    // Kill the challenge for this session
    delete req.session.challenge;

    return res.json(user);
  } catch (e) {
    delete req.session.challenge;

    console.error(e);
    return res.status(400).send({ error: e.message });
  }
});

Ek: AuthenticatorAttestationResponse

AuthenticatorAttestationResponse iki önemli nesne içerir:

  • response.clientDataJSON, istemci verilerinin bir JSON sürümüdür. Bu sürüm, web'deki veriler olarak tarayıcının gördüğü şekildedir. Kısıtlanmış taraf kaynağını, giriş sorgulamasını ve istemci bir Android uygulamasıysa androidPackageName bilgisini içerir. Kısıtlanmış taraf olarak clientDataJSON okuması, create isteği yapılırken tarayıcının gördüğü bilgilere erişmenize olanak tanır.
  • response.attestationObjectşu iki bilgi içerir:
    • attestationStatement. Onay kullanmadığınız sürece uygun değildir.
    • authenticatorData, geçiş anahtarı sağlayıcısının gördüğü verilerdir. Kısıtlanmış taraf olarak okuma authenticatorData izni, geçiş anahtarı sağlayıcısının gördüğü ve create isteği sırasında döndürülen verilere erişmenizi sağlar.

authenticatorData, yeni oluşturulan geçiş anahtarıyla ilişkili ortak anahtar kimlik bilgisi hakkında önemli bilgileri içerir:

  • Ortak anahtar kimlik bilgilerinin kendisi ve benzersiz kimlik bilgisi kimliği.
  • Kimlik bilgisiyle ilişkilendirilen RP kimliği.
  • Geçiş anahtarı oluşturulduğunda kullanıcı durumunu (kullanıcının gerçekten mevcut olup olmadığı ve başarıyla doğrulanıp doğrulanmadığı) açıklayan işaretler (bkz. userVerification).
  • Geçiş anahtarı sağlayıcısını tanımlayan AAGUID. Geçiş anahtarı sağlayıcısının gösterilmesi, özellikle birden fazla geçiş anahtarı sağlayıcısında hizmetiniz için kayıtlı bir geçiş anahtarı varsa kullanıcılarınız için yararlı olabilir.

authenticatorData, attestationObject içinde iç içe yerleştirilmiş olsa da içerdiği bilgiler, onay kullanıp kullanmadığınızdan bağımsız olarak geçiş anahtarı uygulamanız için gereklidir. authenticatorData kodlanmıştır ve ikili biçimde kodlanmış alanlar içerir. Sunucu tarafı kitaplığınız genellikle ayrıştırma ve kod çözme işlemlerini gerçekleştirir. Sunucu tarafı kitaplığı kullanmıyorsanız sunucu tarafında işlerin ayrıştırılması ve kodunun çözülmesi için gerekenden tasarruf etmek için getAuthenticatorData() istemci tarafını kullanmayı düşünün.

Ek: kayıt yanıtının doğrulanması

Kayıt yanıtının doğrulanması, arka planda aşağıdaki kontrollerden oluşur:

  • RP kimliğinin sitenizle eşleştiğinden emin olun.
  • İstek kaynağının siteniz için beklenen bir kaynak (ana site URL'si, Android uygulaması) olduğundan emin olun.
  • Kullanıcı doğrulaması gerekiyorsa authenticatorData.uv kullanıcı doğrulama işaretinin true olduğundan emin olun. Geçiş anahtarları için kullanıcının varlığı her zaman zorunludur. Bu nedenle, authenticatorData.up kullanıcı varlığı işaretinin true olup olmadığını kontrol edin.
  • Müşterinin, verdiğiniz giriş sorgulamasını sağlayıp sağlayamadığını kontrol edin. Onay kullanmıyorsanız bu kontrol önemsizdir. Ancak bu denetimi uygulamak en iyi uygulamadır: İleride onay kullanmaya karar verirseniz kodunuzun hazır olmasını sağlar.
  • Kimlik bilgisi kimliğinin herhangi bir kullanıcı için henüz kaydedilmediğinden emin olun.
  • Geçiş anahtarı sağlayıcısının kimlik bilgisini oluşturmak için kullandığı algoritmanın sizin listelediğiniz bir algoritma olduğunu doğrulayın (publicKeyCredentialCreationOptions.pubKeyCredParams öğesinin her alg alanında, genellikle sunucu tarafı kitaplığınızda tanımlanır ve sizin tarafınızdan görülemez). Bu, kullanıcıların yalnızca izin vermeyi seçtiğiniz algoritmalarla kaydolabilmelerini sağlar.

Daha fazla bilgi edinmek için SimpleWebAuthn'un verifyRegistrationResponse için kaynak kodunu kontrol edin veya spesifikasyondaki doğrulamaların tam listesini inceleyin.

Sıradaki

Sunucu tarafı geçiş anahtarı kimlik doğrulaması