Nowoczesne aplikacje w coraz większym stopniu odchodzą od tradycyjnych haseł na rzecz metod uwierzytelniania bez hasła, które są zarówno bezpieczniejsze, jak i łatwiejsze dla użytkowników. W tym poście pokażę, jak uwierzytelniać użytkowników w aplikacji ASP.NET Core przy użyciu FoxIDs z OpenID Connect i umożliwić logowanie się całkowicie bez hasła przy użyciu jednorazowych kodów dostarczanych pocztą elektroniczną.

Zaczniemy od czystego środowiska FoxIDs, stworzymy nową aplikację internetową ASP.NET Core, połączymy ją z OpenID Connect, a na koniec umożliwimy logowanie passwordless za pomocą OTP opartego na e-mailu.

Tworzenie środowiska deweloperskiego FoxIDs

Zacząłem od stworzenia nowego, pustego środowiska programistycznego w FoxIDs. Czyste środowisko ułatwia skonfigurowanie uwierzytelniania od podstaw i zapewnia możliwość powtórzenia tych kroków we własnym tenancie.

Puste środowisko FoxIDs

Jeśli nie korzystałeś wcześniej z FoxIDs, możesz stworzyć darmowy tenant pod adresem:

https://www.foxids.com

Każdy tenant ma dwa gotowe do użycia środowiska, dzięki czemu możesz swobodnie eksperymentować.

Skonfiguruj uwierzytelnianie

Zacznij od skonfigurowania w jaki sposób użytkownicy powinni się logować. Zależy nam na tym, aby użytkownicy logowali się bez hasła, korzystając wyłącznie ze swojego adresu e-mail i przesłanego im hasła jednorazowego (OTP).

  1. Wybierz zakładkę Uwierzytelnianie w swoim środowisku FoxIDs.
  2. Kliknij Domyślny — interfejs logowania użytkownika.
  3. W sekcji Uwierzytelnianie wyłącz Uwierzytelnianie hasłem i włącz Bez hasła za pomocą poczty elektronicznej (hasło jednorazowe).
  4. Kliknij Aktualizuj, aby zapisać zmiany.
Skonfiguruj metodę logowania uwierzytelniającego

Umożliwia to użytkownikom utworzenie nowego konta i zapewnia obsługę bez hasła, podczas której uwierzytelniają się, po prostu wprowadzając swój adres e-mail i otrzymany OTP.

Tworzenie aplikacji ASP.NET Core

Następnie potrzebujemy aplikacji do uwierzytelnienia. Możesz użyć Visual Studio lub VS Code — wszystkiego, co obsługuje standardowe programowanie ASP.NET Core.

Przeczytaj post „Uwierzytelnianie bez hasła w aplikacji ASP.NET Core generowanej przez sztuczną inteligencję za pomocą FoxIDs i OTP”, jeśli chcesz wygenerować aplikację ASP.NET Core z monitem AI.

Na potrzeby tego przewodnika tworzę prostą aplikację ASP.NET Core Razor Pages dla .NET 10 w Visual Studio, ale to samo podejście działa w przypadku MVC, aplikacji Blazor Web App lub dowolnego innego projektu ASP.NET Core.

Utwórz domyślną aplikację ASP.NET

Uruchom aplikację; przykładowa aplikacja działa lokalnie na https://localhost:7283/

Uruchom domyślną aplikację ASP.NET

Przykładowy kod można znaleźć w: https://github.com/ITfoxtec/dotnet.samples/tree/main/WebAppPasswordLessEmail

Skonfiguruj aplikację internetową w FoxIDs

Teraz, gdy znamy lokalny adres aplikacji internetowej, możemy ją zarejestrować w FoxIDs, aby mogła uwierzytelniać użytkowników poprzez OpenID Connect.

  1. Wybierz zakładkę Aplikacje w swoim środowisku FoxIDs.
  2. Wybierz Nowa aplikacja, a następnie wybierz Aplikacja internetowa — OpenID Connect.
  3. W polu Nazwa wpisz nazwę, np.: Aplikacja internetowa ASP.NET.
  4. W Redirect URI dodaj adres bazowy aplikacji: https://localhost:7283/ Domyślnie FoxIDs umożliwia przekierowanie do dowolnej kolejnej strony pod tym bazowym adresem URL. Jeśli wolisz bardziej rygorystyczną weryfikację, możesz wymagać bezwzględnych identyfikatorów URI przekierowań.
  5. Kliknij Utwórz.
Dodaj aplikację w FoxIDs

FoxIDs wyświetla teraz szczegóły połączenia dla Twojej nowej aplikacji, w tym:

  • Authority
  • Identyfikator klienta
  • Tajemnica klienta
Aplikacja stworzona w FoxIDs

Skopiuj Authority i Identyfikator klienta do sekcji IdentitySettings projektu. Przechowuj Tajemnicę klienta w kluczach użytkownika .NET lokalnie lub w bezpiecznej zmiennej środowiskowej lub magazynie sekretów w środowisku produkcyjnym.

Skonfiguruj uwierzytelnianie w aplikacji ASP.NET

Po utworzeniu aplikacji FoxIDs kolejnym krokiem jest skonfigurowanie uwierzytelnienia w projekcie ASP.NET Core. Będziemy używać uwierzytelniania plików cookie dla sesji lokalnej i OpenID Connect do komunikacji z FoxIDs.

Otwórz Program.cs i dodaj konfigurację uwierzytelniania za pomocą (builder.Services.AddAuthentication... i app.UseAuthentication()😉:

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Logging;

var builder = WebApplication.CreateBuilder(args);

// Detailed authentication errors can help locally, but must not be enabled in production.
if (builder.Environment.IsDevelopment())
{
    IdentityModelEventSource.ShowPII = true;
}

// Add authentication with cookie and OpenID Connect
builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
    {
        options.Authority = builder.Configuration["IdentitySettings:Authority"];
        options.ClientId = builder.Configuration["IdentitySettings:ClientId"];
        options.ClientSecret = builder.Configuration["IdentitySettings:ClientSecret"];
        options.ResponseType = OpenIdConnectResponseType.Code;
        options.SaveTokens = true;

        options.Scope.Add("email");
        options.Scope.Add("profile");
        options.Scope.Add("offline_access");

        options.MapInboundClaims = false;
        options.TokenValidationParameters.NameClaimType = "sub";
        options.TokenValidationParameters.RoleClaimType = "role";

        options.Events = new OpenIdConnectEvents
        {
            OnTokenValidated = async context =>
            {
                // Custom claims transformation or other logic can be added here.
                await Task.CompletedTask;
            },
            OnAuthenticationFailed = context =>
            {
                if (!builder.Environment.IsDevelopment())
                {
                    return Task.CompletedTask;
                }

                context.HandleResponse();
                context.Response.StatusCode = 500;
                context.Response.ContentType = "text/plain";
                return context.Response.WriteAsync(context.Exception.ToString());
            }
        };
    });

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddControllers();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapStaticAssets();
app.MapControllers();
app.MapRazorPages()
   .WithStaticAssets();

app.Run();

Dodaj niejawne szczegóły połączenia FoxIDs do appsettings.json. Trzymaj klucz tajny klienta poza kontrolą źródła i załaduj go z kluczy tajnych użytkownika .NET podczas lokalnego programowania lub z bezpiecznej zmiennej środowiskowej lub tajnego magazynu w środowisku produkcyjnym.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "IdentitySettings": {
    "Authority": "https://foxids.com/{tenant}/{environment}/{application}/",
    "ClientId": "your-client-id"
  }
}

Przechowuj klucz tajny klienta lokalnie, bez zapisywania go w appsettings.json:

dotnet user-secrets init
dotnet user-secrets set "IdentitySettings:ClientSecret" "your-client-secret"

Zastąp przykładowe wartości Authority, identyfikatorem klienta i kluczem klienta z własnego środowiska FoxIDs. Nigdy nie zdradzaj prawdziwego sekretu.

Pokaż claims na stronie głównej

Na potrzeby tego przykładu wyświetl claims na stronie głównej. Otwórz Index.cshtml i zmień zawartość:

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

@if (User.Identity?.IsAuthenticated == true)
{
    <h2 class="mt-4">Claims</h2>
    <table class="table table-striped table-bordered">
        <thead>
            <tr>
                <th>Type</th>
                <th>Value</th>
            </tr>
        </thead>
        <tbody>
        @foreach (var claim in User.Claims)
        {
            <tr>
                <td>@claim.Type</td>
                <td>@claim.Value</td>
            </tr>
        }
        </tbody>
    </table>
}
else
{
    <p class="mt-4 text-muted">Not signed in.</p>
}

Dodaj menu logowania do aplikacji

Aby umożliwić użytkownikom logowanie się, dodaliśmy częściowy widok kontroli logowania i wylogowywania.

Utwórz nowy plik:Pages/Shared/_LoginPartial.cshtml o zawartości:

@if (User.Identity?.IsAuthenticated == true)
{
    <ul class="navbar-nav">
        <li class="nav-item">
            <form class="form-inline" asp-controller="Auth" asp-action="Logout" method="post">
                @Html.AntiForgeryToken()
                <button type="submit" class="nav-link btn btn-link text-dark">Log off</button>
            </form>
        </li>
    </ul>
}
else
{
    <ul class="navbar-nav navbar-right">
        <li class="nav-item">
            <a class="nav-link text-dark" asp-controller="Auth" asp-action="Login">Log in</a>
        </li>
    </ul>
}

Następnie dołącz część logowania do menu nawigacyjnego. W Pages/Shared/_Layout.cshtml dodaj częściowy element logowania na końcu paska nawigacyjnego:

<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
    <ul class="navbar-nav flex-grow-1">
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
        </li>
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
        </li>
    </ul>
    <partial name="_LoginPartial" />
</div>

Następnie dodaj stronę docelową dla żądań logowania i wylogowania. Utwórz folder Controllers i utwórz nowy plik kontrolera w folderze: Controllers/AuthController.cs o zawartości:

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace WebAppPasswordLessEmail.Controllers
{
    [AllowAnonymous]
    [Route("[controller]/[action]")]
    public class AuthController : Controller
    {
        [HttpGet]
        public IActionResult Login()
        {
            var redirectUri = Url.Content("~/");
            if (User.Identity?.IsAuthenticated == true)
            {
                return LocalRedirect(redirectUri);
            }

            return Challenge(new AuthenticationProperties { RedirectUri = redirectUri },
                OpenIdConnectDefaults.AuthenticationScheme);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Logout()
        {
            var redirectUri = Url.Content("~/");
            if (User.Identity?.IsAuthenticated != true)
            {
                return LocalRedirect(redirectUri);
            }

            return SignOut(new AuthenticationProperties { RedirectUri = redirectUri },
                CookieAuthenticationDefaults.AuthenticationScheme, OpenIdConnectDefaults.AuthenticationScheme);
        }
    }
}

Uruchom i załaduj ponownie aplikację, a w górnym menu zobaczysz opcję Zaloguj się.

Przykładowa aplikacja

Zaloguj się

Kliknij Zaloguj się, a nastąpi przekierowanie do ekranu logowania FoxIDs.

Wybierz Utwórz użytkownika.

Ekran logowania FoxIDs

Wypełnij formularz Utwórz użytkownika, podając swój adres e-mail (lub e-mail testowy, do którego masz dostęp), imię i nazwisko, a następnie kliknij Utwórz.

Utwórz użytkownika testowego

Wpisz hasło jednorazowe (OTP) otrzymane e-mailem, a następnie kliknij Zaloguj się.

Zaloguj się hasłem jednorazowym (OTP)

Jesteś zalogowany i wyświetlane są claims Twojego użytkownika testowego.

Zalogowany, pokaż claims

Następnie spróbuj się wylogować i zalogować ponownie, aby doświadczyć procesu logowania bez hasła przy użyciu adresu e-mail OTP.