https://d226lax1qjow5r.cloudfront.net/blog/blogposts/securing-asp-net-app-2fa-using-nexmo-sms-sendgrid-email-dr/2fa-with-sms-or-email.png

Absicherung Ihrer ASP.NET-App mit 2FA unter Verwendung von Nexmo SMS und SendGrid Email

Zuletzt aktualisiert am May 17, 2021

Lesedauer: 5 Minuten

2FA (2-Faktor-Authentifizierung) ist heutzutage ein Muss, um die Sicherheit innerhalb Ihrer Anwendung zu erhöhen. Sie ist in allen Arten von Anwendungen zu finden: von der Anmeldung bis zur Überprüfung von Benutzeraktionen. Die gebräuchlichsten Arten von 2FA sind die Telefonverifizierung und die E-Mail-Verifizierung.

In diesem Tutorial zeigen wir Ihnen, wie Sie 2FA in Ihrer .NET Applikation mit ASP .NET Identity, der Nexmo C# Client-Bibliothek für SMS-Authentifizierung und dem SendGrid C#-Client für E-Mail-Authentifizierung.

Wenn Sie nur das Ergebnis sehen wollen, können Sie sich das folgende Video ansehen das Video oder den Code ansehen.

ASP.NET MVC-Anwendung einrichten

Öffnen Sie Visual Studio und erstellen Sie eine neue ASP.NET MVC-Anwendung. Für diese Demo löschen wir die Abschnitte Kontakt und Über aus der standardmäßig erstellten Website.

Installieren Sie den Nexmo Client über den NuGet Package Manager in Ihrer Anwendung

Fügen Sie den Nexmo Client über die NuGet Package Console zu Ihrer Anwendung hinzu.

PM> Install-Package Nexmo.Csharp.Client

Installieren Sie den SendGrid-Client über den NuGet Package Manager

PM> Install-Package SendGrid -Version 8.0.3

Nexmo und SendGrid Anmeldeinformationen hinzufügen

Für die Zwecke der Demo werden wir die Nexmo und SendGrid Anmeldeinformationen in den <appSettings> Abschnitt der Web.config Datei. Wenn wir diese Anwendung für den Vertrieb entwickeln würden, könnten wir diese Anmeldeinformationen in unser Azure-Portal eingeben.

<add key="Nexmo.Url.Rest" value="https://rest.nexmo.com" />
<add key="Nexmo.Url.Api" value="https://api.nexmo.com" />
<add key="Nexmo.api_key" value="NEXMO_API_KEY" />
<add key="Nexmo.api_secret" value="NEXMO_API_SECRET" />
<add key="SMSAccountFrom" value="SMS_FROM_NUMBER" />
<add key="mailAccount" value="SENDGRID_API_KEY" />

Einbindung von Nexmo in den SMS-Dienst, SendGrid in den E-Mail-Dienst

Innerhalb der IdentityConfig.cs Datei fügen Sie die SendGrid-Konfiguration in der SMSService Methode hinzu. Dann fügen Sie den Nexmo Client in der SMSService Methode der IdentityConfig.cs Datei ein.

Denken Sie daran, die using-Direktiven für die Nexmo.Api und SendGrid Namespaces und alle anderen Namespaces, die als fehlend gekennzeichnet sind, hinzuzufügen.

public class EmailService : IIdentityMessageService
{
    public async Task SendAsync(IdentityMessage message)
    {
        // Plug in your email service here to send an email.
        await configSendGridasync(message);
    }
    private async Task configSendGridasync(IdentityMessage message)
    {
        string apiKey = ConfigurationManager.AppSettings["mailAPIKey"];       
        dynamic sg = new SendGridAPIClient(apiKey, "https://api.sendgrid.com");
        
        Email from = new Email("demo@nexmo.com");
        string subject = message.Subject;
        Email to = new Email(message.Destination);
        Content content = new Content("text/plain", message.Body);
        Mail mail = new Mail(from, subject, to, content); 

        dynamic response = await sg.client.mail.send.post(requestBody: mail.Get()); 
    }
}

public class SmsService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        var sms = SMS.Send(new SMS.SMSRequest
        {
            from = ConfigurationManager.AppSettings["SMSAccountFrom"],
            to = message.Destination,
            text = message.Body
        });
        return Task.FromResult(0);
    }
}

Hinzufügen der Methode 'SendEmailConfirmationTokenAsync()' zum 'AccountController'.

Fügen Sie die folgende Methode zu Ihrer AccountController die bei der Benutzerregistrierung aufgerufen wird, um eine Bestätigungs-E-Mail an die angegebene E-Mail-Adresse zu senden.

private async Task<string> SendEmailConfirmationTokenAsync(string userID, string subject)
{
    string code = await UserManager.GenerateEmailConfirmationTokenAsync(userID);
    var callbackUrl = Url.Action("ConfirmEmail", "Account",  new { userId = userID, code = code }, protocol: Request.Url.Scheme);
    await UserManager.SendEmailAsync(userID, subject, "Please confirm your account by clicking <a href="" + callbackUrl + "">here</a>");
    return callbackUrl;
}

Aktualisierung der Aktionsmethode "Registrieren

Innerhalb der Register Methode der AccountControllerfügen Sie der neu erstellten Variablen vom Typ ApplicationUser ein paar Eigenschaften hinzu: TwoFactorEnabled (true), PhoneNumberConfirmed (false). Sobald der Benutzer erfolgreich erstellt wurde, speichern Sie die Benutzer-ID in einem Sitzungszustand und leiten den Benutzer an die AddPhoneNumber Aktionsmethode in der Datei ManageController.

[AllowAnonymous]
public ActionResult AddPhoneNumber()
{
    return View();
}

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser { UserName = model.Email, Email = model.Email, TwoFactorEnabled = true, PhoneNumberConfirmed = false};
        var result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            Session["UserID"] = user.Id;
            return RedirectToAction("AddPhoneNumber", "Manage");
        }
        AddErrors(result);
    }
    // If we got this far, something failed, redisplay form
    return View(model);
}

Überprüfung der DB auf vorhandene Telefonnummern und Hinzufügen von SMS-Logik zur AddPhoneNumber-Aktionsmethode

In der ManageController fügen Sie das [AllowAnonymous] Attribut sowohl zu den GET- als auch zu den POST AddPhoneNumber Aktionsmethoden hinzu. Dadurch erhält der derzeit nicht registrierte Benutzer Zugriff auf den Workflow zur Bestätigung der Telefonnummer. Führen Sie eine Datenbankabfrage durch, um zu prüfen, ob die vom Benutzer eingegebene Telefonnummer bereits mit einem Account verknüpft ist. Wenn nicht, leiten Sie den Benutzer auf die VerifyPhoneNumber Aktionsmethode um.

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> AddPhoneNumber(AddPhoneNumberViewModel model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }
    var db = new ApplicationDbContext();
    if (db.Users.FirstOrDefault(u => u.PhoneNumber == model.Number) == null)
    {
        // Generate the token and send it
        var code = await UserManager.GenerateChangePhoneNumberTokenAsync((string)Session["UserID"], model.Number);
        if (UserManager.SmsService != null)
        {
            var message = new IdentityMessage
            {
                Destination = model.Number,
                Body = "Your security code is: " + code
            };
            await UserManager.SmsService.SendAsync(message);
        }
        return RedirectToAction("VerifyPhoneNumber", new { PhoneNumber = model.Number });
    }
    else
    {
        ModelState.AddModelError("", "The provided phone number is associated with another account.");
        return View();
    }
}

Update VerifyPhoneNumber Aktionsmethode

Fügen Sie das [AllowAnonymous] Attribut zur GET-Aktionsmethode hinzu und löschen Sie alles in der Methode außer der Rückgabeanweisung, die den Überprüfungsablauf steuert,

[AllowAnonymous]
public async Task<ActionResult> VerifyPhoneNumber(string phoneNumber)
{
    return phoneNumber == null ? View("Error") : View(new VerifyPhoneNumberViewModel { PhoneNumber = phoneNumber });
}

Ersetzen Sie User.Identity.GetUserId() durch Session["UserID"] in der Methode wie unten gezeigt. Wenn der Benutzer den Pincode erfolgreich eingibt, wird er zur Indexansicht der ManageController. Die boolesche Eigenschaft des Benutzers PhoneNumberConfirmed wird dann auf true.

[AllowAnonymous]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> VerifyPhoneNumber(VerifyPhoneNumberViewModel model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }
    var result = await UserManager.ChangePhoneNumberAsync((string)Session["UserID"], model.PhoneNumber, model.Code);
    if (result.Succeeded)
    {
        var user = await UserManager.FindByIdAsync((string)Session["UserID"]);
        if (user != null)
        {
            await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
        }
        return RedirectToAction("Index", new { Message = ManageMessageId.AddPhoneSuccess });
    }
    // If we got this far, something failed, redisplay form
    ModelState.AddModelError("", "Failed to verify phone");
    return View(model);
}

Prüfen, ob der Benutzer eine bestätigte E-Mail beim Login hat

Zurück im AccountControlleraktualisieren Sie die Login() Action-Methode, um zu prüfen, ob der Benutzer seine E-Mail bestätigt hat oder nicht. Wenn nicht, geben Sie eine Fehlermeldung zurück und leiten Sie den Benutzer zur Ansicht "Info" weiter. Rufen Sie außerdem die SendEmailConfirmationTokenAsync() Methode mit der Übergabe der user.Id und einen E-Mail-Betreff.

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

    var user = await UserManager.FindByNameAsync(model.Email);
    if (user != null)
    {
        if (!await UserManager.IsEmailConfirmedAsync(user.Id))
        {
            string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your account");
            ViewBag.title = "Check Email";
            ViewBag.message = "You must have a confirmed email to login.";
            return View("Info");
        }
    }

    ...

Info-Ansicht hinzufügen

Innerhalb der Views/Accounterstellen Sie eine neue Ansicht namens Info zu der der Benutzer weitergeleitet wird, wenn seine E-Mail nicht bestätigt wurde. Die Ansicht sollte den folgenden Code enthalten:

<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3>

Sicherstellen, dass 2FA nicht umgangen werden kann

In der Views/Account/Login.cshtml löschen Sie das <div class="form-group"> mit dem Kontrollkästchen 'Remember Me'. Unter Views/Account/VerifyCode.cshtml löschen Sie die <div class="form-group"> für das Kontrollkästchen "RememberBrowser" und die versteckte RememberMe-Eingabe. Löschen Sie die entsprechende Variable in jedem der View-Modelle in AccountViewModels.cs: SendCodeViewModel und VerifyCodeViewModel. Entfernen Sie abschließend jegliche Verwendung dieser Variablen (einschließlich der Methodensignaturen) oder ersetzen Sie, falls erforderlich, die Verwendung dieser Variablen in den beiden mit false. Dadurch wird der Benutzer daran gehindert, die 2FA-Verifizierung zu umgehen.

Schlussfolgerung

Damit haben Sie eine Webanwendung mit ASP.NET Identity, die die 2-Faktor-Authentifizierung (2FA) mit Nexmo SMS und SendGrid Email als verschiedene Verifizierungsmethoden ermöglicht.

SMS und E-Mail bieten zusätzliche Sicherheitsebenen, um Benutzer korrekt zu identifizieren und sensible Benutzerdaten weiter zu schützen. Mit der Nexmo C#-Client-Bibliothek und dem C#-Client von SendGrid können Sie sowohl die SMS- als auch die E-Mail-Verifizierung mühelos hinzufügen.

Bitte schnappen Sie sich den Code und probieren Sie ihn selbst aus.

Sie können mir gerne Ihre Gedanken/Fragen auf Twitter schicken @sidsharma_27 oder mailen Sie mir an sidharth.sharma@nexmo.com!

Teilen Sie:

https://a.storyblok.com/f/270183/150x150/a3d03a85fd/placeholder.svg
Sidharth Sharma