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

Genel Bakış

Geçiş anahtarı kimlik doğrulamasındaki temel adımlara genel bir bakış:

Geçiş anahtarı kimlik doğrulama akışı

  • Geçiş anahtarıyla kimlik doğrulaması yapmak için gereken sorgulamayı ve diğer seçenekleri tanımlayın. Bunları istemciye göndererek geçiş anahtarı kimlik doğrulama çağrınıza (web'de navigator.credentials.get) iletebilirsiniz. Kullanıcı geçiş anahtarı kimlik doğrulamasını onayladıktan sonra geçiş anahtarı kimlik doğrulama çağrısı çözümlenir ve bir kimlik bilgisi (PublicKeyCredential) döndürür. Kimlik bilgisi, bir kimlik doğrulama onayı içerir.
  • Kimlik doğrulama onayını doğrulayın.
  • Kimlik doğrulama onayı geçerliyse kullanıcının kimliğini doğrulayın.

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

Meydan okumayı oluşturma

Uygulamada, sorgu ArrayBuffer nesnesi olarak temsil edilen rastgele baytlardan oluşan bir dizidir.

// Example challenge, base64URL-encoded
weMLPOSx1VfSnMV6uPwDKbjGdKRMaUDGxeDEUTT5VN8

Mücadelenin amacına ulaşması için şunları yapmanız gerekir:

  1. Aynı sorgulamanın birden fazla kez kullanılmadığından emin olun. Her oturum açma denemesinde yeni bir sorgulama oluşturun. Başarılı veya başarısız her oturum açma denemesinden sonra sorgulamayı silin. Ayrıca, belirli bir süre sonra sorgulamayı silin. Bir yanıtta aynı sınamayı birden fazla kez kabul etmeyin.
  2. Sınamanın kriptografik olarak güvenli olduğundan emin olun. Sınamanın tahmin edilmesi neredeyse imkansız olmalıdır. Sunucu tarafında şifreleme açısından güvenli bir sorgulama oluşturmak için güvendiğiniz bir FIDO sunucu tarafı kitaplığını kullanmanız önerilir. Kendi sorgularınızı oluşturuyorsanız teknoloji yığınınızda bulunan yerleşik kriptografik işlevleri kullanın veya kriptografik kullanım alanları için tasarlanmış kitaplıkları arayın. Node.js'de iso-crypto veya Python'da secrets buna örnek olarak verilebilir. Spesifikasyona göre, sorgulamanın güvenli kabul edilmesi için en az 16 bayt uzunluğunda olması gerekir.

Oluşturduğunuz sorgulamayı daha sonra doğrulamak için kullanıcının oturumuna kaydedin.

Kimlik bilgisi isteği oluşturma seçenekleri

Kimlik bilgisi isteği seçeneklerini publicKeyCredentialRequestOptions nesnesi olarak oluşturun.

Bunun için FIDO sunucu tarafı kitaplığınızı kullanın. Genellikle bu seçenekleri sizin için oluşturabilecek bir hizmet işlevi sunar. Örneğin, SimpleWebAuthn generateAuthenticationOptions sunar.

publicKeyCredentialRequestOptions, geçiş anahtarı kimlik doğrulaması için gereken tüm bilgileri içermelidir. Bu bilgileri, FIDO sunucu tarafı kitaplığınızda publicKeyCredentialRequestOptions nesnesini oluşturmaktan sorumlu işleve iletin.

publicKeyCredentialRequestOptions alanlarından bazıları sabit olabilir. Diğerleri sunucuda dinamik olarak tanımlanmalıdır:

  • rpId: Kimlik bilgisinin ilişkilendirilmesini beklediğiniz RP kimliği (ör. example.com). Kimlik doğrulama, yalnızca burada sağladığınız RP kimliği, kimlik bilgisiyle ilişkili RP kimliğiyle eşleşirse başarılı olur. RP kimliğini doldurmak için kimlik bilgisi kaydı sırasında publicKeyCredentialCreationOptions içinde ayarladığınız RP kimliğiyle aynı değeri kullanın.
  • challenge: Kimlik doğrulama isteği sırasında kullanıcının geçiş anahtarına sahip olduğunu kanıtlamak için geçiş anahtarı sağlayıcının imzalayacağı bir veri parçası. Yarışmayı oluşturma bölümündeki ayrıntıları inceleyin.
  • allowCredentials: Bu kimlik doğrulama için kabul edilebilir kimlik bilgilerinin dizisi. Kullanıcının, tarayıcı tarafından gösterilen listeden kullanılabilir bir geçiş anahtarı seçmesine izin vermek için boş bir dizi iletin. Ayrıntılar için RP sunucusundan sorgu alma ve Keşfedilebilir kimlik bilgilerini ayrıntılı inceleme başlıklı makaleleri inceleyin.
  • userVerification: Cihaz ekran kilidi kullanılarak kullanıcı doğrulamasının "gerekli", "tercih edilen" veya "önerilmeyen" olup olmadığını gösterir. RP sunucusundan sorgulama alma bölümünü inceleyin.
  • timeout: Kullanıcının kimlik doğrulama işlemini tamamlaması için tanınan süre (milisaniye cinsinden). Bu süre makul ölçüde uzun ve challenge süresinden kısa olmalıdır. Önerilen varsayılan değer 5 dakikadır ancak bu süreyi 10 dakikaya kadar artırabilirsiniz. Bu süre de önerilen aralık içindedir. Kullanıcıların genellikle biraz daha uzun süren karma iş akışını kullanmasını bekliyorsanız uzun zaman aşımı süreleri mantıklıdır. İşlem zaman aşımına uğrarsa NotAllowedError hatası verilir.

publicKeyCredentialRequestOptions oluşturduktan sonra müşteriye gönderin.

Sunucu tarafından gönderilen publicKeyCredentialCreationOptions
Sunucu tarafından gönderilen seçenekler. challenge kod çözme işlemi istemci tarafında gerçekleşir.

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

Örneklerimizde SimpleWebAuthn kitaplığını kullanıyoruz. Burada, kimlik bilgisi isteği seçeneklerinin oluşturulmasını generateAuthenticationOptions işlevine devrediyoruz.

import {
  generateAuthenticationOptions,
} from '@simplewebauthn/server';

router.post('/signinRequest', csrfCheck, async (req, res) => {

  // Ensure you nest 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 {
    // Use the generateAuthenticationOptions function from SimpleWebAuthn
    const options = await generateAuthenticationOptions({
      rpID: process.env.HOSTNAME,
      allowCredentials: [],
    });
    // Save the challenge in the user session
    req.session.challenge = options.challenge;

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

Kullanıcıyı doğrulama ve oturumunu açma

navigator.credentials.get, istemcide başarıyla çözümlendiğinde bir PublicKeyCredential nesnesi döndürür.

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

response, AuthenticatorAssertionResponse'dir. Geçiş anahtarı sağlayıcının, istemcinin RP'de geçiş anahtarıyla kimlik doğrulaması yapmak için gerekenleri oluşturma talimatına verdiği yanıtı temsil eder. Şunları içerir:

  • response.authenticatorDataveresponse.clientDataJSON, geçiş anahtarı kaydı adımında olduğu gibi.
  • response.signature. Bu değerler üzerinde bir imza içerir.

PublicKeyCredential nesnesini sunucuya gönderin.

Sunucuda aşağıdakileri yapın:

Veritabanı şeması
Önerilen veritabanı şeması. Bu tasarım hakkında daha fazla bilgiyi Sunucu tarafı geçiş anahtarı kaydı bölümünde bulabilirsiniz.
  • Onaylamayı doğrulamak ve kullanıcının kimliğini doğrulamak için ihtiyacınız olan bilgileri toplayın:
    • Kimlik doğrulama seçeneklerini oluştururken oturumda sakladığınız beklenen sorgulamayı alın.
    • Beklenen kaynağı ve RP kimliğini alın.
    • Veritabanınızda kullanıcının kim olduğunu bulun. Keşfedilebilir kimlik bilgileri söz konusu olduğunda, kimlik doğrulama isteğinde bulunan kullanıcının kim olduğunu bilemezsiniz. Bunu öğrenmek için iki seçeneğiniz vardır:
      • 1. seçenek: PublicKeyCredential nesnesindeki response.userHandle özelliğini kullanın. Kullanıcılar tablosunda userHandle ile eşleşen passkey_user_id değerini bulun.
      • 2. seçenek: PublicKeyCredential nesnesinde bulunan id kimlik bilgisini kullanın. Ortak anahtar kimlik bilgileri tablosunda, PublicKeyCredential nesnesindeki id kimlik bilgisiyle eşleşen id kimlik bilgisini bulun. Ardından, Kullanıcılar tablonuzun yabancı anahtarını (passkey_user_id) kullanarak ilgili kullanıcıyı bulun.
    • Veritabanınızda, aldığınız kimlik doğrulama onayıyla eşleşen ortak anahtar kimlik bilgisi bilgilerini bulun. Bunun için Ortak anahtar kimlik bilgileri tablosunda, PublicKeyCredential nesnesinde bulunan id kimlik bilgisiyle eşleşen id kimlik bilgisini bulun.
  • Kimlik doğrulama onayını doğrulayın. Bu doğrulama adımını, genellikle bu amaçla bir hizmet işlevi sunan FIDO sunucu tarafı kitaplığınıza devredin. Örneğin, SimpleWebAuthn verifyAuthenticationResponse sunar. Ek: Kimlik doğrulama yanıtının doğrulanması bölümünde arka planda neler olduğunu öğrenin.

  • Tekrar oynatma saldırılarını önlemek için doğrulamanın başarılı olup olmadığına bakılmaksızın sorgulamayı silin.

  • Kullanıcı oturum açar. Doğrulama başarılı olduysa oturum bilgilerini güncelleyerek kullanıcının oturum açtığını belirtin. Ayrıca, ön ucun yeni oturum açan kullanıcıyla ilişkili bilgileri kullanabilmesi için istemciye bir user nesnesi döndürmek de isteyebilirsiniz.

Örnek kod: Kullanıcıyı doğrulama ve oturumunu açma

Örneklerimizde SimpleWebAuthn kitaplığını kullanıyoruz. Burada, kimlik doğrulama yanıtının doğrulanmasını verifyAuthenticationResponse işlevine devrediyoruz.

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

router.post('/signinResponse', csrfCheck, async (req, res) => {
  const response = req.body;
  const expectedChallenge = req.session.challenge;
  const expectedOrigin = getOrigin(req.get('User-Agent'));
  const expectedRPID = process.env.HOSTNAME;

  // 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 {
    // Find the credential stored to the database by the credential ID
    const cred = Credentials.findById(response.id);
    if (!cred) {
      throw new Error('Credential not found.');
    }
    // Find the user - Here alternatively we could look up the user directly
    // in the Users table via userHandle
    const user = Users.findByPasskeyUserId(cred.passkey_user_id);
    if (!user) {
      throw new Error('User not found.');
    }
    // Prepare credential information for verification
    const credential = {
      id: cred.id,
      publicKey: isoBase64URL.toBuffer(cred.publicKey),
      transports: cred.transports,
    };

    // Verify the credential
    const { verified, authenticationInfo } = await verifyAuthenticationResponse({
      response,
      expectedChallenge,
      expectedOrigin,
      expectedRPID,
      credential,
      requireUserVerification: false,
    });

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

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

    req.session.username = user.username;
    req.session['signed-in'] = 'yes';

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

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

Ek: Kimlik doğrulama yanıtının doğrulanması

Kimlik doğrulama yanıtının doğrulanması aşağıdaki kontrollerden oluşur:

  • RP kimliğinin sitenizle eşleştiğinden emin olun.
  • İsteğin kaynağının, sitenizin oturum açma kaynağıyla eşleştiğinden emin olun. Android uygulamaları için Kaynağı doğrulama bölümünü inceleyin.
  • Cihazın, verdiğiniz sorgulamayı sağlayabildiğinden emin olun.
  • Kimlik doğrulama sırasında kullanıcının, RP olarak zorunlu kıldığınız şartlara uyduğunu doğrulayın. Kullanıcı doğrulaması gerekiyorsa authenticatorData içindeki uv (kullanıcı doğrulandı) işaretinin true olduğundan emin olun. Geçiş anahtarları için kullanıcı varlığı her zaman gerekli olduğundan authenticatorData içindeki up (kullanıcı var) işaretinin true olduğunu kontrol edin.
  • İmzayı doğrulayın. İmzayı doğrulamak için gerekenler:
    • İmzalanmış sorgulama olan imza: response.signature
    • İmzayı doğrulamak için kullanılacak ortak anahtar.
    • Orijinal imzalı veriler. İmzası doğrulanacak verilerdir.
    • İmzayı oluşturmak için kullanılan şifreleme algoritması.

Bu adımlar hakkında daha fazla bilgi edinmek için SimpleWebAuthn'un verifyAuthenticationResponse için kaynak kodunu inceleyin veya teknik özelliklerdeki doğrulamaların tam listesine göz atın.