İlk WebAuthn uygulamanızı oluşturma

1. Başlamadan önce

WebAuthn olarak da bilinen Web Authentication API, kullanıcıların kimliğini doğrulamak için kaynak kapsamlı ve ortak anahtar kimlik bilgileri oluşturup kullanmanızı sağlar.

API; BDE, NFC ve USB ile dolaşan ve güvenlik anahtarları olarak da bilinen U2F veya FIDO2 kimlik doğrulayıcılarının yanı sıra kullanıcıların parmak izlerini veya ekran kilitlerini kullanarak kimlik doğrulaması yapabilmelerini sağlayan platform kimlik doğrulayıcı kullanımını destekler.

Bu codelab'de, parmak izi sensörü kullanan basit bir yeniden kimlik doğrulama işlevine sahip web sitesi oluşturacaksınız. Yeniden kimlik doğrulama, bir web sitesinde zaten oturum açmış kullanıcıların, web sitesinin önemli bölümlerine girmeye veya belirli bir süre sonra web sitesini yeniden ziyaret etmeye çalıştıklarında tekrar kimlik doğrulaması yapmalarını gerektirdiği için hesap verilerini korur.

Ön koşullar

  • WebAuthn'un nasıl çalıştığıyla ilgili temel bilgiler
  • JavaScript ile temel programlama becerileri

Yapacaklarınız

  • Parmak izi sensörü kullanan basit bir yeniden kimlik doğrulama işlevine sahip web sitesi oluşturma

Gerekenler

  • Aşağıdaki cihazlardan biri:
    • Android cihaz (tercihen biyometrik sensörle)
    • iOS 14 veya sonraki sürümlerde Touch ID ya da Face ID'ye sahip bir iPhone veya iPad
    • macOS Big Sur veya üzeri bir modelde Touch ID'ye sahip bir MacBook Pro veya Air
    • Windows Hello kurulumuyla birlikte Windows 10 19H1 veya sonraki sürümler
  • Aşağıdaki tarayıcılardan biri:
    • Google Chrome 67 veya sonraki sürümler
    • Microsoft Edge 85 veya sonraki sürümler
    • Safari 14 veya üzeri

2. Hazırlanın

Bu codelab'de aksan adlı bir hizmet kullanıyorsunuz. Burada, istemci ve sunucu tarafı kodunu JavaScript ile düzenleyebilir ve anında dağıtabilirsiniz.

https://glitch.com/edit/#!/webauthn-codelab-start adresine gidin.

İşleyiş şekli

Web sitesinin ilk durumunu görmek için şu adımları izleyin:

  1. 62bb7a6aac381af8.pngCanlı web sitesini görmek için Göster > 3343769d04c09851.png Yeni Pencerede'yi tıklayın.
  2. İstediğiniz bir kullanıcı adını girip İleri'yi tıklayın.
  3. Bir şifre girin ve Oturum aç'ı tıklayın.

Şifre yok sayılır, ancak hâlâ kimliğinizin doğrulaması yapılmış. Ana sayfaya ulaşırsınız.

  1. Yeniden kimlik doğrulaması yapmayı deneyin'i tıklayın ve ikinci, üçüncü ve dördüncü adımları tekrarlayın.
  2. Oturumu kapat'ı tıklayın.

Oturum açmayı her denediğinizde şifreyi girmeniz gerektiğini unutmayın. Bu işlem, web sitesinin önemli bir bölümüne erişebilmesi için önce yeniden kimlik doğrulaması yapması gereken bir kullanıcının emülasyonunu yapar.

Kodu yeniden düzenleyin

  1. WebAuthn / FIDO2 API Codelab'e gidin.
  2. Projenizi adına çatallayıp Remix Projesi306122647ce93305.png'ni tıklayın ve yeni bir URL'de kendi sürümünüzle devam edin.

8d42bd24f0fd185c.png

3. Parmak izinle kimlik bilgisi kaydetme

Cihazda yerleşik olarak bulunan ve kullanıcının kimliğini doğrulayan kimlik doğrulayıcı UVPA tarafından oluşturulan bir kimlik bilgisini kaydetmeniz gerekir. Bu genellikle kullanıcının cihazına bağlı olarak bir parmak izi sensörü olarak görülür.

Bu özelliği /home sayfasına eklersiniz:

260aab9f1a2587a7.png

registerCredential() işlevi oluşturma

Yeni bir kimlik bilgisi kaydeden bir registerCredential() işlevi oluşturun.

public/client.js

export const registerCredential = async () => {

};

Sunucu uç noktasından giriş sorgulamasını ve diğer seçenekleri edinme

Kullanıcıdan yeni bir kimlik bilgisi kaydettirmesini istemeden önce, sunucudan, kimlik doğrulaması dahil olmak üzere WebAuthn'da iletilecek parametreleri döndürmesini isteyin. Neyse ki bu tür parametrelerle yanıt veren bir sunucu uç noktanız zaten var.

registerCredential() koduna aşağıdaki kodu ekleyin.

public/client.js

const opts = {
  attestation: 'none',
  authenticatorSelection: {
    authenticatorAttachment: 'platform',
    userVerification: 'required',
    requireResidentKey: false
  }
};

const options = await _fetch('/auth/registerRequest', opts);

Sunucu ile istemci arasındaki protokol WebAuthn spesifikasyonunun bir parçası değildir. Ancak bu codelab, WebAuthn spesifikasyonuyla uyumlu olacak şekilde tasarlanmıştır ve sunucuya ilettiğiniz JSON nesnesi, sizin için sezgisel olması açısından PublicKeyCredentialCreationOptions ile çok benzerdir. Aşağıdaki tabloda, sunucuya iletebileceğiniz önemli parametreler ve bunların ne işe yaradığı açıklanmaktadır:

Parametreler

Açıklamalar

attestation

Onay aktarımı tercihi: none, indirect veya direct. İhtiyacınız yoksa none ürününü seçin.

excludeCredentials

Kimlik doğrulayıcının yinelenenler oluşturmasını önlemek için PublicKeyCredentialDescriptor dizisi.

authenticatorSelection

authenticatorAttachment

Mevcut kimlik doğrulayıcıları filtreleyin. Cihaza bağlı bir kimlik doğrulayıcı kullanmak istiyorsanız "platform" kullanın. Dolaşım kimlik doğrulayıcıları için "cross-platform" kullanın.

userVerification

Kimlik doğrulayıcı yerel kullanıcı doğrulamasının "required", "preferred" veya "&discouraged" olduğunu belirleyin. Parmak izi veya ekran kilidi kimlik doğrulaması istiyorsanız "required" kullanın.

requireResidentKey

Oluşturulan kimlik bilgilerinin gelecekteki hesap seçici kullanıcı deneyimi için kullanılabilmesi gerekiyorsa true kullanın.

Bu seçenekler hakkında daha fazla bilgi edinmek için 5.4 Kimlik Bilgisi Oluşturma seçenekleri (sözlük PublicKeyCredentialCreationOptions).

Aşağıda, sunucudan aldığınız örnek seçenekler verilmiştir.

{
  "rp": {
    "name": "WebAuthn Codelab",
    "id": "webauthn-codelab.glitch.me"
  },
  "user": {
    "displayName": "User Name",
    "id": "...",
    "name": "test"
  },
  "challenge": "...",
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    }, {
      "type": "public-key",
      "alg": -257
    }
  ],
  "timeout": 1800000,
  "attestation": "none",
  "excludeCredentials": [
    {
      "id": "...",
      "type": "public-key",
      "transports": [
        "internal"
      ]
    }
  ],
  "authenticatorSelection": {
    "authenticatorAttachment": "platform",
    "userVerification": "required"
  }
}

Kimlik bilgisi oluşturma

  1. Bu seçenekler HTTP protokolünden geçecek şekilde kodlandığından, bazı parametreleri özellikle user.id, challenge ve excludeCredentials dizisine dahil edilen id örneklerine geri dönüştürün:

public/client.js

options.user.id = base64url.decode(options.user.id);
options.challenge = base64url.decode(options.challenge);

if (options.excludeCredentials) {
  for (let cred of options.excludeCredentials) {
    cred.id = base64url.decode(cred.id);
  }
}
  1. Yeni bir kimlik bilgisi oluşturmak için navigator.credentials.create() yöntemini çağırın.

Bu çağrıda, tarayıcı kimlik doğrulayıcıyla etkileşimde bulunur ve kullanıcının kimliğini UVPA ile doğrulamaya çalışır.

public/client.js

const cred = await navigator.credentials.create({
  publicKey: options,
});

Kullanıcı, kimliğini doğruladıktan sonra sunucuya gönderebileceğiniz ve kimlik doğrulayıcıyı kaydedebileceğiniz bir kimlik bilgisi nesnesi alırsınız.

Kimlik bilgisini sunucu uç noktasına kaydedin

Almanız gereken örnek bir kimlik bilgisi nesnesi.

{
  "id": "...",
  "rawId": "...",
  "type": "public-key",
  "response": {
    "clientDataJSON": "...",
    "attestationObject": "..."
  }
}
  1. Tıpkı bir kimlik bilgisini kaydetmek için bir seçenek nesnesi aldığınızda, kimlik bilgisinin ikili parametreleri kodlayarak bir dize olarak sunucuya teslim edilebilmesini sağlayın:

public/client.js

const credential = {};
credential.id = cred.id;
credential.rawId = base64url.encode(cred.rawId);
credential.type = cred.type;

if (cred.response) {
  const clientDataJSON =
    base64url.encode(cred.response.clientDataJSON);
  const attestationObject =
    base64url.encode(cred.response.attestationObject);
  credential.response = {
    clientDataJSON,
    attestationObject,
  };
}
  1. Kullanıcı geri geldiğinde kimlik doğrulaması için kullanabileceğiniz kimlik bilgisi kimliğini yerel olarak depolayın:

public/client.js

localStorage.setItem(`credId`, credential.id);
  1. Nesneyi sunucuya gönderin ve HTTP code 200 değerini döndürürse yeni kimlik bilgisini başarıyla kaydedilmiş olarak kabul edin.

public/client.js

return await _fetch('/auth/registerResponse' , credential);

Artık registerCredential() işlevine sahipsiniz.

Bu bölümün nihai kodu

public/client.js

...
export const registerCredential = async () => {
  const opts = {
    attestation: 'none',
    authenticatorSelection: {
      authenticatorAttachment: 'platform',
      userVerification: 'required',
      requireResidentKey: false
    }
  };

  const options = await _fetch('/auth/registerRequest', opts);

  options.user.id = base64url.decode(options.user.id);
  options.challenge = base64url.decode(options.challenge);

  if (options.excludeCredentials) {
    for (let cred of options.excludeCredentials) {
      cred.id = base64url.decode(cred.id);
    }
  }
  
  const cred = await navigator.credentials.create({
    publicKey: options
  });

  const credential = {};
  credential.id =     cred.id;
  credential.rawId =  base64url.encode(cred.rawId);
  credential.type =   cred.type;

  if (cred.response) {
    const clientDataJSON =
      base64url.encode(cred.response.clientDataJSON);
    const attestationObject =
      base64url.encode(cred.response.attestationObject);
    credential.response = {
      clientDataJSON,
      attestationObject
    };
  }

  localStorage.setItem(`credId`, credential.id);
  
  return await _fetch('/auth/registerResponse' , credential);
};
...

4. Kimlik bilgilerini kaydetmek, almak ve kaldırmak için kullanıcı arayüzünü oluşturun

Kayıtlı kimlik bilgilerinin ve düğmelerin yer aldığı bir listeye sahip olmak çok güzel.

9b5b5ae4a7b316bd.png

Kullanıcı arayüzü yer tutucusu oluşturma

Kimlik bilgilerini listelemek için kullanıcı arayüzü, yeni kimlik bilgilerini kaydetmek için bir düğme ekleyin. Özelliğin kullanılabilir olup olmadığına bağlı olarak, hidden sınıfını uyarı mesajından veya yeni bir kimlik bilgisini kaydetmek için kullanılan düğmeden kaldırırsınız. ul#list, kayıtlı kimlik bilgilerinin listesini eklemek için yer tutucudur.

views/home.html

<p id="uvpa_unavailable" class="hidden">
  This device does not support User Verifying Platform Authenticator. You can't register a credential.
</p>
<h3 class="mdc-typography mdc-typography--headline6">
  Your registered credentials:
</h3>
<section>
  <div id="list"></div>
</section>
<mwc-button id="register" class="hidden" icon="fingerprint" raised>Add a credential</mwc-button>

Özellik algılama ve UVPA kullanılabilirliği

UVPA kullanılabilirliğini kontrol etmek için aşağıdaki adımları izleyin:

  1. WebAuthn'un kullanılabilir olup olmadığını öğrenmek için window.PublicKeyCredential dosyasını inceleyin.
  2. UVPA olup olmadığını kontrol etmek için PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() numaralı telefonu arayın . Sertifikalar uygunsa yeni bir kimlik bilgisi kaydetmek için düğmeyi gösterirsiniz. Bunların ikisi de yoksa uyarı mesajını görürsünüz.

views/home.html

const register = document.querySelector('#register');

if (window.PublicKeyCredential) {
  PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
  .then(uvpaa => {
    if (uvpaa) {
      register.classList.remove('hidden');
    } else {
      document
        .querySelector('#uvpa_unavailable')
        .classList.remove('hidden');
    }
  });        
} else {
  document
    .querySelector('#uvpa_unavailable')
    .classList.remove('hidden');
}

Kimlik bilgilerinin listesini alma ve görüntüleme

  1. Kayıtlı kimlik bilgilerini almak ve bunları bir listede görüntülemek için bir getCredentials() işlevi oluşturun. Neyse ki /auth/getKeys sunucusunda, oturum açmış olan kullanıcı için kayıtlı kimlik bilgilerini getirebileceğiniz yararlı bir uç nokta zaten var.

Döndürülen JSON, id ve publicKey gibi kimlik bilgisi bilgilerini içerir. Bunları kullanıcılara göstermek için HTML oluşturabilirsiniz.

views/home.html

const getCredentials = async () => {
  const res = await _fetch('/auth/getKeys');
  const list = document.querySelector('#list');
  const creds = html`${res.credentials.length > 0 ? res.credentials.map(cred => html`
    <div class="mdc-card credential">
      <span class="mdc-typography mdc-typography--body2">${cred.credId}</span>
      <pre class="public-key">${cred.publicKey}</pre>
      <div class="mdc-card__actions">
        <mwc-button id="${cred.credId}" @click="${removeCredential}" raised>Remove</mwc-button>
      </div>
    </div>`) : html`
    <p>No credentials found.</p>
    `}`;
  render(creds, list);
};
  1. Kullanıcı /home sayfasına ulaşır gelmez kullanılabilir kimlik bilgilerini göstermek için getCredentials()arayın.

views/home.html

getCredentials();

Kimlik bilgisini kaldır

Kimlik bilgisi listesine her kimlik bilgisini kaldırmak için bir düğme eklediniz. /auth/removeKey sorgusunu, kaldırmak için credId sorgu parametresiyle birlikte bir istek gönderebilirsiniz.

public/client.js

export const unregisterCredential = async (credId) => {
  localStorage.removeItem('credId');
  return _fetch(`/auth/removeKey?credId=${encodeURIComponent(credId)}`);
};
  1. Mevcut import ifadesine unregisterCredential ekleyin.

views/home.html

import { _fetch, unregisterCredential } from '/client.js';
  1. Kullanıcı Kaldır'ı tıkladığında çağırılacak bir işlev ekleyin.

views/home.html

const removeCredential = async e => {
  try {
    await unregisterCredential(e.target.id);
    getCredentials();
  } catch (e) {
    alert(e);
  }
};

Kimlik bilgisi kaydetme

Kullanıcı, Kimlik bilgisi ekle'yi tıkladığında yeni bir kimlik bilgisi kaydetmek için registerCredential() çağrısı yapabilirsiniz.

  1. Mevcut import ifadesine registerCredential ekleyin.

views/home.html

import { _fetch, registerCredential, unregisterCredential } from '/client.js';
  1. navigator.credentials.create() seçeneğiyle birlikte registerCredential() çağırın.

Kayıt sonrasında getCredentials() numaralı telefonu arayarak kimlik bilgisi listesini yenilemeyi unutmayın.

views/home.html

register.addEventListener('click', e => {
  registerCredential().then(user => {
    getCredentials();
  }).catch(e => alert(e));
});

Artık yeni bir kimlik bilgisi kaydedebilir ve kimlik bilgilerini görüntüleyebilirsiniz. Canlı web sitenizde deneyebilirsiniz.

Bu bölümün nihai kodu

views/home.html

...
      <p id="uvpa_unavailable" class="hidden">
        This device does not support User Verifying Platform Authenticator. You can't register a credential.
      </p>
      <h3 class="mdc-typography mdc-typography--headline6">
        Your registered credentials:
      </h3>
      <section>
        <div id="list"></div>
        <mwc-fab id="register" class="hidden" icon="add"></mwc-fab>
      </section>
      <mwc-button raised><a href="/reauth">Try reauth</a></mwc-button>
      <mwc-button><a href="/auth/signout">Sign out</a></mwc-button>
    </main>
    <script type="module">
      import { _fetch, registerCredential, unregisterCredential } from '/client.js';
      import { html, render } from 'https://unpkg.com/lit-html@1.0.0/lit-html.js?module';

      const register = document.querySelector('#register');

      if (window.PublicKeyCredential) {
        PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
        .then(uvpaa => {
          if (uvpaa) {
            register.classList.remove('hidden');
          } else {
            document
              .querySelector('#uvpa_unavailable')
              .classList.remove('hidden');
          }
        });        
      } else {
        document
          .querySelector('#uvpa_unavailable')
          .classList.remove('hidden');
      }

      const getCredentials = async () => {
        const res = await _fetch('/auth/getKeys');
        const list = document.querySelector('#list');
        const creds = html`${res.credentials.length > 0 ? res.credentials.map(cred => html`
          <div class="mdc-card credential">
            <span class="mdc-typography mdc-typography--body2">${cred.credId}</span>
            <pre class="public-key">${cred.publicKey}</pre>
            <div class="mdc-card__actions">
              <mwc-button id="${cred.credId}" @click="${removeCredential}" raised>Remove</mwc-button>
            </div>
          </div>`) : html`
          <p>No credentials found.</p>
          `}`;
        render(creds, list);
      };

      getCredentials();

      const removeCredential = async e => {
        try {
          await unregisterCredential(e.target.id);
          getCredentials();
        } catch (e) {
          alert(e);
        }
      };

      register.addEventListener('click', e => {
        registerCredential({
          attestation: 'none',
          authenticatorSelection: {
            authenticatorAttachment: 'platform',
            userVerification: 'required',
            requireResidentKey: false
          }
        })
        .then(user => {
          getCredentials();
        })
        .catch(e => alert(e));
      });
    </script>
...

public/client.js

...
export const unregisterCredential = async (credId) => {
  localStorage.removeItem('credId');
  return _fetch(`/auth/removeKey?credId=${encodeURIComponent(credId)}`);
};
...

5. Parmak iziyle kullanıcının kimliğini doğrulama

Artık kimlik bilgilerinizi kaydettirdiniz ve kullanıcının kimliğini doğrulama yöntemi olarak kullanmaya hazırsınız. Şimdi web sitesine yeniden kimlik doğrulama işlevi ekliyorsunuz. Kullanıcı deneyimi şöyledir:

/reauth sayfasına gelen kullanıcılar, biyometrik kimlik doğrulamanın mümkün olduğu durumlarda bir Kimlik doğrulama düğmesi görür. Parmak iziyle (UVPA) kimlik doğrulama, Kimlik doğrulaması yap seçeneğine dokunduktan, kimlik doğrulamasının başarıyla tamamlanmasından sonra /home sayfasına geldiğinde başlar. Biyometrik kimlik doğrulama kullanılamazsa veya biyometrik kimlik doğrulama başarısız olursa kullanıcı arayüzü, mevcut şifre formunu kullanmaya başlar.

b8770c4e7475b075.png

authenticate() işlevi oluşturma

Parmak iziyle kullanıcının kimliğini doğrulayan authenticate() adında bir işlev oluşturun. JavaScript kodunu buraya eklersiniz:

public/client.js

export const authenticate = async () => {

};

Sunucu uç noktasından giriş sorgulamasını ve diğer seçenekleri edinme

  1. Kimlik doğrulamasından önce, kullanıcının depolanmış bir kimlik bilgisi olup olmadığını inceleyin ve varsa sorgu parametresi olarak ayarlayın.

Diğer seçeneklerle birlikte kimlik bilgisi kimliğini sağladığınızda sunucu, alakalı allowCredentials sağlayabilir. Bu da kullanıcı doğrulamasının güvenilir olmasını sağlar.

public/client.js

const opts = {};

let url = '/auth/signinRequest';
const credId = localStorage.getItem(`credId`);
if (credId) {
  url += `?credId=${encodeURIComponent(credId)}`;
}
  1. Kullanıcıdan kimlik doğrulamasını istemeden önce sunucudan bir giriş sorgulaması ve diğer parametreleri göndermesini isteyin. Sunucuya POST isteği göndermek için bağımsız değişken olarak opts ile _fetch() çağrısı yapın.

public/client.js

const options = await _fetch(url, opts);

Almanız gereken örnek seçenekler şunlardır (PublicKeyCredentialRequestOptions ile uyumludur).

{
  "challenge": "...",
  "timeout": 1800000,
  "rpId": "webauthn-codelab.glitch.me",
  "userVerification": "required",
  "allowCredentials": [
    {
      "id": "...",
      "type": "public-key",
      "transports": [
        "internal"
      ]
    }
  ]
}

Buradaki en önemli seçenek allowCredentials. Sunucudan seçenekler aldığınızda allowCredentials, sorgu parametresinde kimliği olan bir kimlik bilgisinin sunucu tarafında bulunup bulunmadığına bağlı olarak bir dizideki tek bir nesne veya boş bir dizi olmalıdır.

  1. Kullanıcı arayüzü, şifre istemeye devam etmesi için allowCredentials boş bir dizi olduğunda null ile ilgili sözü çözün.
if (options.allowCredentials.length === 0) {
  console.info('No registered credentials found.');
  return Promise.resolve(null);
}

Kullanıcıyı yerel olarak doğrulayın ve kimlik bilgisi alın

  1. Bu seçenekler HTTP protokolünden geçmek için kodlanmış olarak yayınlandığından, bazı parametreleri, özellikle challenge ve allowCredentials dizisine dahil edilen id örneklerini geri dönüştürün:

public/client.js

options.challenge = base64url.decode(options.challenge);

for (let cred of options.allowCredentials) {
  cred.id = base64url.decode(cred.id);
}
  1. Kullanıcı kimliğini UVPA ile doğrulamak için navigator.credentials.get() yöntemini çağırın.

public/client.js

const cred = await navigator.credentials.get({
  publicKey: options
});

Kullanıcı, kimliğini doğruladıktan sonra sunucuya gönderebileceğiniz ve kullanıcının kimliğini doğrulayabileceğiniz bir kimlik bilgisi nesnesi alırsınız.

Kimlik bilgilerini doğrulayın

Aşağıda, almış olmanız gereken örnek bir PublicKeyCredential nesnesi (response, AuthenticatorAssertionResponse) verilmiştir:

{
  "id": "...",
  "type": "public-key",
  "rawId": "...",
  "response": {
    "clientDataJSON": "...",
    "authenticatorData": "...",
    "signature": "...",
    "userHandle": ""
  }
}
  1. Kimlik bilgisinin ikili parametreleri kodlayarak bir dize olarak sunucuya teslim edilebilmesini sağlayın:

public/client.js

const credential = {};
credential.id = cred.id;
credential.type = cred.type;
credential.rawId = base64url.encode(cred.rawId);

if (cred.response) {
  const clientDataJSON =
    base64url.encode(cred.response.clientDataJSON);
  const authenticatorData =
    base64url.encode(cred.response.authenticatorData);
  const signature =
    base64url.encode(cred.response.signature);
  const userHandle =
    base64url.encode(cred.response.userHandle);
  credential.response = {
    clientDataJSON,
    authenticatorData,
    signature,
    userHandle,
  };
}
  1. Nesneyi sunucuya gönderin ve HTTP code 200 değerini döndürürse, kullanıcının başarıyla oturum açtığını göz önünde bulundurun:

public/client.js

return await _fetch(`/auth/signinResponse`, credential);

Artık authentication() işlevine sahipsiniz.

Bu bölümün nihai kodu

public/client.js

...
export const authenticate = async () => {
  const opts = {};

  let url = '/auth/signinRequest';
  const credId = localStorage.getItem(`credId`);
  if (credId) {
    url += `?credId=${encodeURIComponent(credId)}`;
  }
  
  const options = await _fetch(url, opts);
  
  if (options.allowCredentials.length === 0) {
    console.info('No registered credentials found.');
    return Promise.resolve(null);
  }

  options.challenge = base64url.decode(options.challenge);

  for (let cred of options.allowCredentials) {
    cred.id = base64url.decode(cred.id);
  }

  const cred = await navigator.credentials.get({
    publicKey: options
  });

  const credential = {};
  credential.id = cred.id;
  credential.type = cred.type;
  credential.rawId = base64url.encode(cred.rawId);

  if (cred.response) {
    const clientDataJSON =
      base64url.encode(cred.response.clientDataJSON);
    const authenticatorData =
      base64url.encode(cred.response.authenticatorData);
    const signature =
      base64url.encode(cred.response.signature);
    const userHandle =
      base64url.encode(cred.response.userHandle);
    credential.response = {
      clientDataJSON,
      authenticatorData,
      signature,
      userHandle,
    };
  }

  return await _fetch(`/auth/signinResponse`, credential);
};
...

6. Yeniden kimlik doğrulama deneyimini etkinleştir

Derleme kullanıcı arayüzü

Kullanıcı geri döndüğünde, mümkün olduğunca kolay ve güvenli bir şekilde yeniden kimlik doğrulaması yapmasını istersiniz. Biyometrik kimlik doğrulama bu noktada devreye girer. Bununla birlikte, biyometrik kimlik doğrulamasının kullanılamayacağı durumlar vardır:

  • UVPA kullanılamıyor.
  • Kullanıcı henüz cihazında kimlik bilgisi kaydetmedi.
  • Depolama alanı temizlenir ve cihaz artık kimlik bilgisi kimliğini hatırlamaz.
  • Kullanıcı, parmağının ıslak olması veya maske takma gibi nedenlerle kimliğini doğrulayamıyor.

Bu nedenle, yedek olarak diğer oturum açma seçeneklerini sağlamanız her zaman için önemlidir. Bu codelab'de, form tabanlı şifre çözümünü kullanıyorsunuz.

19da999b0145054.png

  1. Şifre formuna ek olarak biyometrik kimlik doğrulamasını çağıran bir kimlik doğrulama düğmesi göstermek için kullanıcı arayüzü ekleyin.

Kullanıcının durumuna bağlı olarak bunlardan birini seçip göstermek için hidden sınıfını kullanın.

views/reauth.html

<div id="uvpa_available" class="hidden">
  <h2>
    Verify your identity
  </h2>
  <div>
    <mwc-button id="reauth" raised>Authenticate</mwc-button>
  </div>
  <div>
    <mwc-button id="cancel">Sign-in with password</mwc-button>
  </div>
</div>
  1. Forma class="hidden" öğesini ekleyin:

views/reauth.html

<form id="form" method="POST" action="/auth/password" class="hidden">

Özellik algılama ve UVPA kullanılabilirliği

Aşağıdaki koşullardan biri karşılanırsa kullanıcıların şifreyle oturum açması gerekir:

  • WebAuthn kullanılamıyor.
  • UVPA kullanılamaz.
  • Bu UVPA için kimlik bilgisi bulunamıyor.

Kimlik doğrulama düğmesini seçerek gösterin veya gizleyin:

views/reauth.html

if (window.PublicKeyCredential) {
  PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
  .then(uvpaa => {
    if (uvpaa && localStorage.getItem(`credId`)) {
      document
        .querySelector('#uvpa_available')
        .classList.remove('hidden');
    } else {
      form.classList.remove('hidden');
    }
  });        
} else {
  form.classList.remove('hidden');
}

Şifre formuna yedek

Kullanıcı şifreyle oturum açmayı da seçebilmelidir.

Kullanıcı Şifreyle oturum aç'ı tıkladığında şifre formunu göster ve kimlik doğrulama düğmesini gizle:

views/reauth.html

const cancel = document.querySelector('#cancel');
cancel.addEventListener('click', e => {
  form.classList.remove('hidden');
  document
    .querySelector('#uvpa_available')
    .classList.add('hidden');
});

c4a82800889f078c.png

Biyometrik kimlik doğrulamasını çağırın

Son olarak, biyometrik kimlik doğrulamayı etkinleştirin.

  1. authenticate ifadesini mevcut import ifadesine ekle:

views/reauth.html

import { _fetch, authenticate } from '/client.js';
  1. Kullanıcı, biyometrik kimlik doğrulamasını başlatmak için Kimlik doğrulaması'na dokunduğunda authenticate() çağırın.

Biyometrik kimlik doğrulama hatasının şifre formuna döndüğünden emin olun.

views/reauth.html

const button = document.querySelector('#reauth');
button.addEventListener('click', e => {
  authenticate().then(user => {
    if (user) {
      location.href = '/home';
    } else {
      throw 'User not found.';
    }
  }).catch(e => {
    console.error(e.message || e);
    alert('Authentication failed. Use password to sign-in.');
    form.classList.remove('hidden');
    document.querySelector('#uvpa_available').classList.add('hidden');
  });        
});

Bu bölümün nihai kodu

views/reauth.html

...
    <main class="content">
      <div id="uvpa_available" class="hidden">
        <h2>
          Verify your identity
        </h2>
        <div>
          <mwc-button id="reauth" raised>Authenticate</mwc-button>
        </div>
        <div>
          <mwc-button id="cancel">Sign-in with password</mwc-button>
        </div>
      </div>
      <form id="form" method="POST" action="/auth/password" class="hidden">
        <h2>
          Enter a password
        </h2>
        <input type="hidden" name="username" value="{{username}}" />
        <div class="mdc-text-field mdc-text-field--filled">
          <span class="mdc-text-field__ripple"></span>
          <label class="mdc-floating-label" id="password-label">password</label>
          <input type="password" class="mdc-text-field__input" aria-labelledby="password-label" name="password" />
          <span class="mdc-line-ripple"></span>
        </div>
        <input type="submit" class="mdc-button mdc-button--raised" value="Sign-In" />
        <p class="instructions">password will be ignored in this demo.</p>
      </form>
    </main>
    <script src="https://unpkg.com/material-components-web@7.0.0/dist/material-components-web.min.js"></script>
    <script type="module">
      new mdc.textField.MDCTextField(document.querySelector('.mdc-text-field'));
      import { _fetch, authenticate } from '/client.js';
      const form = document.querySelector('#form');
      form.addEventListener('submit', e => {
        e.preventDefault();
        const form = new FormData(e.target);
        const cred = {};
        form.forEach((v, k) => cred[k] = v);
        _fetch(e.target.action, cred)
        .then(user => {
          location.href = '/home';
        })
        .catch(e => alert(e));
      });

      if (window.PublicKeyCredential) {
        PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
        .then(uvpaa => {
          if (uvpaa && localStorage.getItem(`credId`)) {
            document
              .querySelector('#uvpa_available')
              .classList.remove('hidden');
          } else {
            form.classList.remove('hidden');
          }
        });        
      } else {
        form.classList.remove('hidden');
      }

      const cancel = document.querySelector('#cancel');
      cancel.addEventListener('click', e => {
        form.classList.remove('hidden');
        document
          .querySelector('#uvpa_available')
          .classList.add('hidden');
      });

      const button = document.querySelector('#reauth');
      button.addEventListener('click', e => {
        authenticate().then(user => {
          if (user) {
            location.href = '/home';
          } else {
            throw 'User not found.';
          }
        }).catch(e => {
          console.error(e.message || e);
          alert('Authentication failed. Use password to sign-in.');
          form.classList.remove('hidden');
          document.querySelector('#uvpa_available').classList.add('hidden');
        });        
      });
    </script>
...

7. Tebrikler!

Bu codelab'i tamamladınız!

Daha fazla bilgi

Yardımlarınız için FIDO Alliance'tan Yuriy Ackermann'a teşekkür ederiz.