As aplicações modernas estão cada vez mais a afastar-se das palavras-passe tradicionais e a adotar métodos de autenticação sem palavra-passe que são mais seguros e fáceis para os utilizadores. Neste post, mostrarei como autenticar utilizadores numa aplicação ASP.NET Core utilizando FoxIDs com OpenID Connect e ativar uma experiência de início de sessão totalmente sem palavra-passe utilizando códigos únicos entregues por e-mail.

Começaremos com um ambiente FoxIDs limpo, criaremos uma nova aplicação web ASP.NET Core, ligá-la-emos com OpenID Connect e, finalmente, activaremos o login passwordless com OTP baseado em e-mail.

Criação de um ambiente de desenvolvimento FoxIDs

Comecei por criar um novo ambiente de desenvolvimento vazio em FoxIDs. Um ambiente limpo simplifica a configuração da autenticação a partir do zero e garante que pode replicar estes passos no seu próprio tenant.

Ambiente FoxIDs vazio

Se nunca utilizou FoxIDs antes, pode criar um tenant gratuito em:

https://www.foxids.com

Cada tenant vem com dois ambientes prontos a usar para que possa experimentar livremente.

Configurar autenticação

Comece por configurar como os utilizadores devem fazer login. Neste caso, queremos que os utilizadores iniciem sessão sem palavra-passe, utilizando apenas o endereço de e-mail e uma palavra-passe de utilização única (OTP) que lhes foi enviada.

  1. Selecione o separador Autenticações no seu ambiente FoxIDs.
  2. Clique em Padrão — UI de início de sessão do utilizador.
  3. Na secção Autenticação, desative Autenticação por palavra-passe e ative Sem palavra-passe com e-mail (palavra-passe de utilização única).
  4. Clique em Atualizar para guardar as alterações.
Configurar método de login de autenticação

Isto permite aos utilizadores criar uma nova conta e proporcionar uma experiência sem palavra-passe, onde se autenticam simplesmente introduzindo o seu endereço de e-mail e o OTP que recebem.

Criação da aplicação ASP.NET Core

Em seguida, precisamos de uma aplicação para autenticação. Pode utilizar o Visual Studio ou o VS Code – qualquer coisa que suporte o desenvolvimento ASP.NET Core padrão.

Leia o post “Autenticação sem palavra-passe numa aplicação ASP.NET Core gerada por IA com FoxIDs e OTP baseada em e-mail” se pretender gerar a aplicação ASP.NET Core com um aviso de IA.

Para este passo a passo, estou a criar uma aplicação ASP.NET Core Razor Pages simples direcionada para .NET 10 no Visual Studio, mas a mesma abordagem funciona para MVC, uma Blazor Web App ou qualquer outro projeto ASP.NET Core.

Criar aplicação ASP.NET padrão

Execute a aplicação; a aplicação de exemplo é executada localmente em https://localhost:7283/

Execute a aplicação ASP.NET padrão

Pode encontrar o código de exemplo em: https://github.com/ITfoxtec/dotnet.samples/tree/main/WebAppPasswordLessEmail

Configure a aplicação web em FoxIDs

Agora que já sabemos o endereço local da aplicação web, podemos registá-la em FoxIDs para que possa autenticar os utilizadores através de OpenID Connect.

  1. Selecione o separador Aplicações no seu ambiente FoxIDs.
  2. Escolha Nova aplicação e selecione Aplicação Web — OpenID Connect.
  3. Em Nome, introduza um nome, por exemplo: ASP.NET aplicação web.
  4. No Redirect URI, adicione o endereço base da aplicação: https://localhost:7283/ Por defeito, o FoxIDs permite o redireccionamento para qualquer página subsequente neste URL base. Se preferir uma validação mais rigorosa, pode exigir URIs de redireccionamento absolutos.
  5. Clique em Criar.
Adicionar aplicação em FoxIDs

FoxIDs apresenta agora os detalhes da ligação para a sua nova aplicação, incluindo:

  • Authority
  • ID do cliente
  • Segredo do cliente
Aplicação criada em FoxIDs

Copie Authority e Client ID para a secção IdentitySettings do projeto. Armazene o Segredo do cliente em segredos do utilizador .NET localmente ou numa variável de ambiente segura ou armazenamento de segredos na produção.

Configurar a autenticação na aplicação ASP.NET

Com a aplicação FoxIDs criada, o passo seguinte é configurar a autenticação no projeto ASP.NET Core. Utilizaremos autenticação de cookies para a sessão local e OpenID Connect para comunicação com FoxIDs.

Abra o Program.cs e adicione a configuração de autenticação com (builder.Services.AddAuthentication... e 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();

Adicione os detalhes da ligação não secreta FoxIDs a appsettings.json. Mantenha o segredo do cliente fora do controlo de origem e carregue-o a partir dos segredos do utilizador .NET durante o desenvolvimento local ou de uma variável de ambiente segura ou de armazenamento secreto na produção.

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

Armazene o segredo do cliente localmente sem o gravar em appsettings.json:

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

*Substitua os valores de exemplo por Authority, ID do cliente e segredo do cliente do seu próprio ambiente FoxIDs. Nunca cometa o verdadeiro segredo. *

Mostrar claims na página principal

Para efeitos deste exemplo, mostre os claims na página principal. Abra o Index.cshtml e altere o conteúdo:

@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>
}

Adicione um menu de início de sessão à aplicação

Para permitir que os utilizadores iniciem sessão, adicionámos uma visualização parcial dos controlos de início de sessão e de logout.

Crie um novo ficheiro:Pages/Shared/_LoginPartial.cshtml com o conteúdo:

@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>
}

Em seguida, inclua o login parcial no menu de navegação. Em Pages/Shared/_Layout.cshtml adicione o elemento de login parcial no final da barra de navegação:

<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>

Em seguida, adicione uma página inicial para os pedidos de início de sessão e de logout. Crie uma pasta Controladores e crie um novo ficheiro controlador na pasta: Controllers/AuthController.cs com o conteúdo:

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);
        }
    }
}

Execute e recarregue a aplicação e verá a opção Login no menu superior.

Exemplo de aplicação

Conecte-se

Clique em Login e será redirecionado para o ecrã de login do FoxIDs.

Selecione Criar utilizador.

Ecrã de login do FoxIDs

Preencha o formulário Criar utilizador com o seu e-mail (ou um e-mail de teste ao qual tenha acesso), nome e apelido e clique em Criar.

Crie um utilizador de teste

Introduza a palavra-passe de utilização única (OTP) que recebeu por e-mail e clique em Efetuar login.

Iniciar sessão com palavra-passe de utilização única (OTP)

Está ligado e os claims do seu utilizador de teste são exibidas.

Ligado, mostrar claims

Em seguida, tente fazer logoff e login novamente para experimentar o fluxo de login sem palavra-passe utilizando o e-mail OTP.