Przegląd
Oto ogólny przegląd najważniejszych kroków związanych z uwierzytelnianiem za pomocą klucza dostępu:

- Zdefiniuj wyzwanie i inne opcje wymagane do uwierzytelniania za pomocą klucza dostępu. Wyślij je do klienta, aby można było przekazać je do wywołania uwierzytelniania klucza dostępu (
navigator.credentials.getw internecie). Gdy użytkownik potwierdzi uwierzytelnianie za pomocą klucza dostępu, wywołanie uwierzytelniania za pomocą klucza dostępu zostanie rozwiązane i zwróci dane logowania (PublicKeyCredential). Dane logowania zawierają asercję uwierzytelniania.
- Zweryfikuj asercję uwierzytelniania.
- Jeśli asercja uwierzytelniania jest prawidłowa, uwierzytelnij użytkownika.
W sekcjach poniżej znajdziesz szczegółowe informacje o poszczególnych krokach.
Tworzenie wyzwania
W praktyce wyzwanie to tablica losowych bajtów reprezentowana jako obiekt ArrayBuffer.
// Example challenge, base64URL-encoded
weMLPOSx1VfSnMV6uPwDKbjGdKRMaUDGxeDEUTT5VN8
Aby wyzwanie spełniło swoje zadanie, musisz:
- Upewnij się, że ten sam ciąg kontrolny nie jest używany więcej niż raz. Generuj nowe wyzwanie przy każdej próbie logowania. Odrzuć test zabezpieczający po każdej próbie logowania, niezależnie od tego, czy się powiodła, czy nie. Odrzuć wyzwanie po określonym czasie. Nigdy nie akceptuj tego samego wyzwania w odpowiedzi więcej niż raz.
- Upewnij się, że test jest zabezpieczony kryptograficznie. Odgadnięcie odpowiedzi na pytanie zabezpieczające powinno być praktycznie niemożliwe. Aby utworzyć kryptograficznie bezpieczne wyzwanie po stronie serwera, najlepiej użyć zaufanej biblioteki FIDO po stronie serwera. Jeśli zamiast tego tworzysz własne wyzwania, użyj wbudowanej funkcji kryptograficznej dostępnej w Twoim stosie technologicznym lub poszukaj bibliotek przeznaczonych do zastosowań kryptograficznych. Przykłady to iso-crypto w Node.js lub secrets w Pythonie. Zgodnie ze specyfikacją wyzwanie musi mieć co najmniej 16 bajtów, aby było uznawane za bezpieczne.
Po utworzeniu wyzwania zapisz je w sesji użytkownika, aby później je zweryfikować.
Tworzenie opcji żądania danych logowania
Utwórz opcje żądania danych logowania jako obiekt publicKeyCredentialRequestOptions.
Aby to zrobić, użyj biblioteki FIDO po stronie serwera. Zwykle oferuje funkcję użytkową, która może utworzyć te opcje. SimpleWebAuthn oferuje na przykład generateAuthenticationOptions.
publicKeyCredentialRequestOptions powinien zawierać wszystkie informacje potrzebne do uwierzytelniania za pomocą klucza dostępu. Przekaż te informacje do funkcji w bibliotece po stronie serwera FIDO, która odpowiada za tworzenie obiektu publicKeyCredentialRequestOptions.
Niektóre pola publicKeyCredentialRequestOptions mogą być stałymi. Inne powinny być definiowane dynamicznie na serwerze:
rpId: identyfikator RP, z którym mają być powiązane dane logowania, np.example.com. Uwierzytelnianie powiedzie się tylko wtedy, gdy podany tutaj identyfikator RP będzie zgodny z identyfikatorem RP powiązanym z danymi logowania. Aby wypełnić identyfikator RP, użyj tej samej wartości co identyfikator RP ustawiony wpublicKeyCredentialCreationOptionspodczas rejestracji danych logowania.challenge: dane, które dostawca klucza dostępu podpisze, aby potwierdzić, że użytkownik ma klucz dostępu w momencie wysyłania żądania uwierzytelnienia. Sprawdź szczegóły w sekcji Tworzenie wyzwania.allowCredentials: tablica akceptowalnych danych uwierzytelniających na potrzeby tego uwierzytelniania. Przekaż pustą tablicę, aby umożliwić użytkownikowi wybranie dostępnego klucza dostępu z listy wyświetlanej przez przeglądarkę. Szczegółowe informacje znajdziesz w sekcjach Pobieranie wyzwania z serwera RP i Szczegółowe omówienie wykrywalnych danych logowania.userVerification: wskazuje, czy weryfikacja użytkownika za pomocą blokady ekranu urządzenia jest „wymagana”, „preferowana” czy „niezalecana”. Zapoznaj się z sekcją Pobieranie wyzwania z serwera RP.timeout: czas (w milisekundach), w którym użytkownik może ukończyć uwierzytelnianie. Powinien być odpowiednio długi i krótszy niż czas życiachallenge. Zalecana wartość domyślna to 5 minut, ale możesz ją zwiększyć do 10 minut, co nadal mieści się w zalecanym zakresie. Długie czasy oczekiwania mają sens, jeśli oczekujesz, że użytkownicy będą korzystać z hybrydowego przepływu pracy, który zwykle trwa nieco dłużej. Jeśli przekroczony zostanie limit czasu operacji, zostanie wygenerowany błądNotAllowedError.
Po utworzeniu publicKeyCredentialRequestOptions wyślij go do klienta.
challenge dekodowanie odbywa się po stronie klienta.Przykładowy kod: tworzenie opcji żądania danych logowania
W naszych przykładach używamy biblioteki SimpleWebAuthn. W tym miejscu przekazujemy tworzenie opcji żądania danych logowania do funkcji generateAuthenticationOptions.
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 });
}
});
Weryfikowanie i logowanie użytkownika
Gdy metoda navigator.credentials.get zostanie pomyślnie rozwiązana na kliencie, zwraca obiekt PublicKeyCredential.
navigator.credentials.get zwraca PublicKeyCredential.response to AuthenticatorAssertionResponse. Reprezentuje odpowiedź dostawcy klucza dostępu na instrukcję klienta, aby utworzyć to, co jest potrzebne do uwierzytelnienia za pomocą klucza dostępu na stronie uzależnionej. Zawiera te informacje:
response.authenticatorDatairesponse.clientDataJSON, np. na etapie rejestracji klucza dostępu.response.signature, który zawiera podpis tych wartości.
Wyślij obiekt PublicKeyCredential na serwer.
Na serwerze wykonaj te czynności:
- Zbierz informacje potrzebne do zweryfikowania asercji i uwierzytelnienia użytkownika:
- Pobierz oczekiwany ciąg znaków, który został zapisany w sesji podczas generowania opcji uwierzytelniania.
- Uzyskaj oczekiwane źródło i identyfikator RP.
- Znajdź w bazie danych, kim jest użytkownik. W przypadku danych logowania możliwych do wykrycia nie wiesz, kto wysyła żądanie uwierzytelnienia. Możesz to sprawdzić na 2 sposoby:
- Opcja 1. Użyj pola
response.userHandlew obiekciePublicKeyCredential. W tabeli Użytkownicy znajdźpasskey_user_id, który odpowiadauserHandle. - Opcja 2. Użyj danych logowania
idobecnych w obiekciePublicKeyCredential. W tabeli Dane logowania klucza publicznego znajdź dane logowaniaid, które odpowiadają danym logowaniaidw obiekciePublicKeyCredential. Następnie wyszukaj odpowiedniego użytkownika, używając klucza obcegopasskey_user_iddo tabeli Użytkownicy.
- Opcja 1. Użyj pola
- Znajdź w bazie danych informacje o danych logowania klucza publicznego, które pasują do otrzymanej asercji uwierzytelniania. Aby to zrobić, w tabeli Dane logowania klucza publicznego znajdź dane logowania
id, które odpowiadają danym logowaniaidw obiekciePublicKeyCredential.
Zweryfikuj asercję uwierzytelniania. Przekaż ten krok weryfikacji do biblioteki FIDO po stronie serwera, która zwykle oferuje funkcję użytkową do tego celu. SimpleWebAuthn oferuje na przykład
verifyAuthenticationResponse. Więcej informacji o tym, co dzieje się w tle, znajdziesz w sekcji Dodatek: weryfikacja odpowiedzi uwierzytelniającej.Usuń wyzwanie niezależnie od tego, czy weryfikacja się powiedzie, czy nie, aby zapobiec atakom typu replay.
Zaloguj użytkownika. Jeśli weryfikacja się powiedzie, zaktualizuj informacje o sesji, aby oznaczyć użytkownika jako zalogowanego. Możesz też zwrócić obiekt
userdo klienta, aby interfejs mógł używać informacji powiązanych z nowo zalogowanym użytkownikiem.
Przykładowy kod: weryfikacja i logowanie użytkownika
W naszych przykładach używamy biblioteki SimpleWebAuthn. W tym miejscu przekazujemy weryfikację odpowiedzi uwierzytelniania do funkcji verifyAuthenticationResponse.
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 });
}
});
Dodatek: weryfikacja odpowiedzi uwierzytelniającej
Weryfikacja odpowiedzi uwierzytelniającej obejmuje te czynności:
- Upewnij się, że identyfikator RP pasuje do Twojej witryny.
- Upewnij się, że źródło żądania jest zgodne ze źródłem logowania w witrynie. W przypadku aplikacji na Androida zapoznaj się z sekcją Weryfikowanie pochodzenia.
- Sprawdź, czy urządzenie było w stanie odpowiedzieć na wyzwanie.
- Sprawdź, czy podczas uwierzytelniania użytkownik spełnił wymagania, które nakładasz jako strona uzależniona. Jeśli wymagasz weryfikacji użytkownika, upewnij się, że flaga
uv(użytkownik zweryfikowany) wauthenticatorDatama wartośćtrue. Sprawdź, czy flagaup(obecność użytkownika) wauthenticatorDatama wartośćtrue, ponieważ obecność użytkownika jest zawsze wymagana w przypadku kluczy dostępu. - Sprawdź podpis. Aby zweryfikować podpis, musisz mieć:
- podpis, czyli podpisane zadanie:
response.signature - klucz publiczny, za pomocą którego można zweryfikować podpis;
- Oryginalne podpisane dane. Są to dane, których podpis ma zostać zweryfikowany.
- algorytm kryptograficzny użyty do utworzenia podpisu,
- podpis, czyli podpisane zadanie:
Aby dowiedzieć się więcej o tych krokach, zapoznaj się z kodem źródłowym SimpleWebAuthn dla verifyAuthenticationResponse lub przejrzyj pełną listę weryfikacji w specyfikacji.