INCONE60 Green - Digital and green transition of small ports
Andrzej Chybicki: projekty związane z wykorzystaniem sztucznej inteligencji to znacząca część naszych projektów
Wdrażanie uwierzytelniania wieloskładnikowego za pomocą wiadomości e-mail w Keycloak
Keycloak natywnie obsługuje wiele bezpiecznych metod logowania i zawiera wbudowane mechanizmy jednorazowych haseł (OTP), takie jak uwierzytelnianie za pomocą aplikacji mobilnych, np. Google Authenticator lub naszego rozwiązania AuthM8. Jednak jeśli chcemy korzystać z innych zaawansowanych metod uwierzytelniania i na przykład wysyłać kody OTP przez e-mail, to – podobnie jak w przypadku uwierzytelniania wieloskładnikowego za pomocą SMS (więcej szczegółów TUTAJ) – musimy samodzielnie zaimplementować tę funkcjonalność. W tym artykule przyjrzymy się niestandardowej implementacji MFA, która wysyła jednorazowy kod uwierzytelniający na adres e-mail użytkownika.

Jak działa uwierzytelnianie wieloskładnikowe oparte na e-mailu?

Proces uwierzytelniania składa się z dwóch głównych etapów:

    • Generowanie i wysyłanie kodu MFA

Jeśli użytkownik ma już aktywny plik cookie potwierdzający wcześniejszą weryfikację MFA, powinien zostać natychmiast uwierzytelniony. W przeciwnym razie Keycloak tworzy nowe poświadczenie dla użytkownika i generuje jednorazowy kod na podstawie konfigurowalnych parametrów, takich jak długość lub czas ważności. Kod jest przechowywany w poświadczeniach użytkownika, a następnie wysyłany e-mailem za pośrednictwem dostawcy poczty e-mail.

 

    • Weryfikacja wprowadzonego kodu

Gdy użytkownik wprowadzi kod, Keycloak pobiera zapisane poświadczenie i porównuje wprowadzoną wartość. Jeśli kod jest poprawny i nadal ważny (nie wygasł), uwierzytelnienie kończy się sukcesem, a plik cookie zostaje zapisany w celu zapamiętania weryfikacji. Jeśli kod jest niepoprawny, użytkownik zostaje poproszony o ponowne jego wprowadzenie. Jeśli kod wygasł, wyświetlany jest komunikat o błędzie i proces musi zostać rozpoczęty od nowa.

Plusy i minusy

Uwierzytelnianie wieloskładnikowe (MFA) oparte na e-mailu zapewnia dodatkowe zabezpieczenie w przypadku naruszenia podstawowego czynnika, takiego jak hasło. Jest to szczególnie przydatne w sytuacjach, gdy hasła zostają złamane metodą brute-force lub są łatwe do odgadnięcia, na przykład w przypadku popularnych kombinacji, takich jak „123456”. Podobnie, to rozwiązanie chroni przed atakami typu credential stuffing, w których cyberprzestępcy wykorzystują wyciekłe hasła z innych naruszeń do prób logowania na konta.


Dodatkowe korzyści z używania za pomocą e-maila jako metody MFA:

    • Brak konieczności podawania dodatkowych wrażliwych informacji, takich jak numer telefonu, co zmniejsza obawy o prywatność.
    • Brak potrzeby instalowania osobnej aplikacji ani przechodzenia przez skomplikowaną konfigurację, co upraszcza cały proces.
    • Użytkownicy są przyzwyczajeni do podawania adresu e-mail w różnych celach, takich jak otrzymywanie ważnych powiadomień o koncie czy resetowanie haseł. Ta znajomość sprawia, że metoda ta jest łatwiejsza do zaakceptowania.


Ograniczenia MFA opartego na e-mailu:

E-mail jako kanał dostarczania kodów ma również pewne wady. Jeśli atakujący przejmie dostęp do skrzynki e-mail (np. uzyska dane logowania lub wykorzysta aktywną sesję), może potencjalnie zresetować hasła do innych kont. W przypadku użytkowników znajdujących się w szczególnie narażonych sytuacjach, np. korzystających ze współdzielonych urządzeń, MFA oparte na e-mailu może nie zapewnić pełnej ochrony.

Jak w przypadku każdej metody zabezpieczeń, kluczowe jest rozważenie korzyści w stosunku do potencjalnych ryzyk i uzupełnienie MFA opartego na e-mailu innymi środkami bezpieczeństwa, takimi jak silna polityka haseł i bezpieczne praktyki korzystania z poczty e-mail.

Wdrożenie MFA opartego na e-mailu

W tym zmodyfikowanym przepływie uwierzytelniania w przeglądarce, integrujemy niestandardowe MFA jako dodatkową metodę uwierzytelniania. Dodane zostały dwa nowe kroki:

    • Konfiguracja MFA opartego na e-mailu – ten etap zapewnia, że e-mail użytkownika jest skonfigurowany i zweryfikowany przed kontynuacją procesu. Jeśli użytkownik nie posiada niestandardowego poświadczenia MFA (które przechowuje kody OTP jako tajne dane), zostanie ono również ustawione.
public class MfaEmailSetupAuthenticator implements Authenticator, CredentialValidator<MfaEmailCredentialProvider> {
@Override
public void authenticate(AuthenticationFlowContext context) {
[…]
// Require email verification
if (!userModel.isEmailVerified()) {
userModel.addRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL);
}
// Add MFA email credential if not present
if (!getCredentialProvider(context.getSession()).isConfiguredFor(realmModel, userModel, MfaEmailCredentialModel.TYPE)) {
userModel.credentialManager().createStoredCredential(new MfaEmailCredentialModel(new MfaEmailCredentialData()));
}
[…]
    • Uwierzytelnianie MFA oparte na e-mailu – to właściwy etap uwierzytelniania, w którym jednorazowy kod jest wysyłany na adres e-mail użytkownika. Jest oznaczony jako Alternatywny, co oznacza, że może być używany zamiast innych metod MFA, takich jak OTP z aplikacji mobilnej.
    • Max Cookie Age – to ustawienie określa, jak długo sesja MFA (plik cookie) pozostaje ważna. Jeśli plik cookie jest nadal aktywny, użytkownik nie zostanie ponownie poproszony o uwierzytelnienie MFA.
    • Time-to-live – wskazuje czas życia kodu MFA.

 

Teraz przyjrzyjmy się kodowi.

 

Poniższa metoda obsługuje sam proces MFA. Jeśli istnieje ważny plik cookie (co oznacza, że użytkownik już ukończył MFA), metoda natychmiast zwraca sukces, kończąc przepływ uwierzytelniania bez konieczności podejmowania dodatkowych działań.

@Override
public void authenticate(AuthenticationFlowContext context) {
if (hasValidCookie(context)) {
context.success();
return;
}
[…]

Jeśli plik cookie nie istnieje, należy spróbować pobrać istniejące poświadczenie MFA użytkownika z dostawcy poświadczeń. Jeśli użytkownik go nie posiada, tworzona jest nowa instancja przy użyciu MfaEmailCredentialModel, który rozszerza wbudowany CredentialModel.

 

[…]
// get existing credential or create a new one
CredentialModel credentialModel = getCredentialProvider(session)
.getDefaultCredential(session, context.getRealm(), user);
if (credentialModel == null) {
credentialModel = user.credentialManager().createStoredCredential(new MfaEmailCredentialModel(new MfaEmailCredentialData()));
}
[…]

Następnie metoda authenticate odczytuje właściwości konfiguracyjne, takie jak długość kodu i TTL (time-to-live). Sam kod może zostać wygenerowany za pomocą odpowiedniej metody narzędziowej i zostanie zapisany jako secretData w modelu poświadczeń.

// generate and store code
int length = Integer.parseInt(configMap.get(CONFIG_CODE_LENGTH));
int ttl = Integer.parseInt(configMap.get(CONFIG_CODE_TTL));
String code = MfaEmailCodesUtils.generateCode(length);
credentialModel.setSecretData(code);
user.credentialManager().updateStoredCredential(credentialModel);
AuthenticationSessionModel authSession = context.getAuthenticationSession();
authSession.setAuthNote("ttl", Long.toString(System.currentTimeMillis() + (ttl * 1000L)));

Na końcu wywoływana jest metoda sendCode, która wysyła wygenerowany kod na adres e-mail użytkownika. Jeśli wiadomość e-mail zostanie wysłana pomyślnie, metoda wyświetla formularz, w którym użytkownik może wprowadzić kod MFA.

// send email and show input form
try {
MfaEmailCodesUtils.sendCode(session, user, ttl, code, configMap);
context.challenge(context.form().setAttribute("realm", context.getRealm()).createForm(TPL_CODE));
} catch (Exception e) {
context.failureChallenge(AuthenticationFlowError.INTERNAL_ERROR,
context.form().setError("mfaEmailNotSent", e.getMessage())  .createErrorPage(Response.Status.INTERNAL_SERVER_ERROR));
}

Drugą kluczową częścią naszego mechanizmu uwierzytelniania jest metoda action, która odpowiada za walidację kodu wprowadzonego przez użytkownika. Jest ona wywoływana w momencie, gdy użytkownik przesyła formularz po otrzymaniu wiadomości e-mail.

Metoda pobiera poświadczenie użytkownika od dostawcy, a następnie kod jest weryfikowany poprzez porównanie go z zapisanym poświadczeniem za pomocą niestandardowej metody isValid.

[…]
final MfaEmailCredentialModel credentialModel = getCredentialProvider(session)
       .getDefaultCredential(session, context.getRealm(), user);
boolean isValid = getCredentialProvider(session).isValid(context.getRealm(), user,
     new UserCredentialModel(credentialModel.getId(), getCredentialProvider(context.getSession()).getType(), enteredCode));
[…]

Jeśli kod jest poprawny, następnym krokiem jest sprawdzenie, czy nie wygasł. Możemy również ustawić plik cookie przechowujący sesję MFA, aby zapobiec ponownemu proszeniu użytkownika o uwierzytelnienie MFA w okresie ważności tego pliku cookie.

[…]
// valid
HttpResponse response = context.getSession().getContext().getHttpResponse();
response.setCookieIfAbsent(createCookie(context));
context.success();
[…]

 

Oczywiście, w tym artykule nie omówimy całego zagadnienia, pomijając szczegóły implementacyjne, takie jak wysyłanie kodu, jego generowanie, walidacja oraz tworzenie niestandardowego pliku cookie.

 

Jednak przeanalizowaliśmy kluczowe kroki implementacji uwierzytelniania dwuskładnikowego (2FA) przy użyciu kodów wysyłanych na e-mail. Z jednej strony podejście to oferuje prostą i łatwo dostępną metodę zabezpieczeń. Choć ma pewne wady, zastosowanie go w rozwiązaniach takich jak Keycloak pozwala zminimalizować wiele z tych zagrożeń. Keycloak zapewnia również elastyczność w łączeniu MFA opartego na e-mailu z innymi mechanizmami zabezpieczeń, tworząc bardziej warstwowy i odporny proces uwierzytelniania, który może skutecznie chronić przed rozwijającymi się zagrożeniami cybernetycznymi.

Czy potrzebujesz pomocy w konfiguracji uwierzytelniania wieloskładnikowego?

Umów spotkanie, aby dowiedzieć się, jak możemy Ci pomóc.