Pular para o conteúdo

.NET

Proteja APIs ASP.NET Core, chame APIs a partir de workers com client credentials e faça o login de SPAs por um Backend-for-Frontend com os pacotes .NET do Authio.

O SDK .NET do Authio são três pacotes no NuGet, versão 0.4.0, para .NET 8 e .NET 10. Eles se apoiam nos handlers padrão do ASP.NET Core — JWT bearer, OpenID Connect, cookies — configurados a partir do documento de discovery do seu realm, e já entendem as claims, os papéis e os erros do Authio.

Escolha o pacote

PacotePara que serve
Authio.AspNetCoreUma API que recebe access tokens: validação JWT bearer, políticas por escopo, papel, permissão, organização, client e força do login, e introspecção opcional.
Authio.AspNetCore.BffO host de um SPA (React, Angular, Vue…) que faz login com o Authio sem tokens no navegador.
Authio.ClientTodo o resto, sem dependência do ASP.NET Core: workers e CLIs chamando APIs com client credentials, todos os grants, introspecção, revogação, verificação de webhooks e a Admin API.
TerminalBash
dotnet add package Authio.AspNetCore        # uma API
dotnet add package Authio.AspNetCore.Bff    # o host de um SPA
dotnet add package Authio.Client            # um worker, uma CLI ou chamadas ao Authio

Aponte para o seu realm

Os três pacotes leem a mesma seção Authio. O emissor é {Authority}/realms/{Realm} (ou defina Issuer direto), e todos os endpoints são lidos do documento de discovery dele, guardado em cache por uma hora e recusado se informar outro emissor. ClientAuthentication é Basic por padrão; use Post ou None (client público) conforme o método registrado no client.

appsettings.jsonJSON
{
  "Authio": {
    "Authority": "https://authio.api.hyperio.tech",
    "Realm": "acme",
    "ClientId": "acme-web",
    "ClientSecret": "YOUR_CLIENT_SECRET"
  }
}
Mantenha o segredo fora do controle de versão: use dotnet user-secrets na sua máquina e a variável de ambiente Authio__ClientSecret em produção. Uma configuração ausente ou inválida para a aplicação na partida com o nome da chave, como Authio:ClientId is required.

Proteja uma API

Um access token é endereçado a uma audiência (aud), e por padrão essa audiência é Authio — todo token do realm a carrega. Registre a sua API como servidor de recurso em Servidores de recurso, por exemplo https://api.example.com com os escopos orders:read e orders:write, libere os clients que a chamam e aceite só o identificador dela. Os clients passam a pedir resource=https://api.example.com, e os tokens deles o levam em aud.

Program.csC#
using Authio.AspNetCore.Defaults;
using Authio.AspNetCore.Extensions;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddAuthentication(AuthioJwtBearerDefaults.AuthenticationScheme)
    .AddAuthioJwtBearer(builder.Configuration.GetSection("Authio"));
builder.Services.AddAuthioAuthorization();

var app = builder.Build();

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

app.MapGet("/orders", (HttpContext context) => context.GetAuthioUser())
    .RequireAuthioScope("orders:read");

app.MapDelete("/orders/{id}", (string id) => Results.NoContent())
    .RequireAuthioPermission("orders", "delete");

app.Run();
VerificaçãoO que o handler exige
Emissor e audiênciaExatamente o emissor do realm, e uma das Audiences.
Assinatura e validadeUma chave do JWKS do realm com algoritmo assimétrico (RS, PS, ES); exp e nbf com 30 segundos de ClockSkew.
Tipo do tokenA claim typ precisa ser Bearer, então ID tokens e refresh tokens são recusados.
ClaimsMantidas com os nomes do Authio (sub, scope, azp); papéis achatados em claims role e permission, para IsInRole funcionar.

Exija escopos, papéis e permissões

Toda exigência existe em endpoints e grupos de rotas de minimal API, em políticas e como atributos de controller, que precisam de AddAuthioAuthorization().

Minimal APIsC#
using Authio.AspNetCore.Enums;
using Authio.AspNetCore.Extensions;

app.MapGet("/orders/export", () => Results.Ok())
    .RequireAuthioScope(AuthioMatch.All, "orders:read", "orders:export");

app.MapGroup("/admin")
    .RequireAuthioRole("admin")
    .RequireAuthioAuthenticationMethod("mfa");
ExigênciaHelperPassa quando
EscopoRequireAuthioScope / [AuthioScope]Algum (ou, com AuthioMatch.All, todos) dos escopos está em scope.
PapelRequireAuthioRole / [AuthioRole]O usuário tem o papel, sem diferenciar maiúsculas. Precisa do escopo roles.
PermissãoRequireAuthioPermission / [AuthioPermission]Algum papel do usuário concede o método no recurso, como orders / write.
OrganizaçãoRequireAuthioOrganization / [AuthioOrganization]org_id está presente, ou bate com o alias ou o ID informado.
Força do loginRequireAuthioAuthenticationLevel / RequireAuthioAuthenticationMethodacr atinge o nível, ou amr contém o método (mfa, otp, swk…).
ClientRequireAuthioClient / [AuthioClient]azp é um dos clients — útil para admitir um único client de máquina.

Um escopo ausente responde 403 com WWW-Authenticate: Bearer error="insufficient_scope" e os escopos necessários; uma exigência de força do login não atendida responde insufficient_user_authentication, para o client mandar a pessoa por um login mais forte. WriteProblemDetails = true em AddAuthioAuthorization acrescenta um corpo problem details.

Leia quem está chamando

Program.csC#
app.MapGet("/me", (HttpContext context) =>
{
    var user = context.GetAuthioUser()!;

    return new { user.Subject, user.ClientId, user.OrganizationAlias, user.Roles, user.Scopes };
}).RequireAuthorization();

AuthioUser traz também a sessão (SessionId), as permissões por recurso, Acr e AuthenticationMethods, IdentityProvider num login federado, a cadeia Actor de um token delegado e as claims de perfil. Em controllers, use User.GetAuthioUser(); context.GetAuthioAccessToken() devolve o bearer token bruto.

Pegue tokens revogados

Um JWT continua válido até expirar, mesmo depois de um logout, uma revogação ou uma suspensão. Com Introspection em ActiveCheck, a API pergunta ao Authio, depois da validação local, se o token ainda está ativo — guardando uma resposta ativa por token durante 30 segundos e recusando a requisição se o Authio não responder. Isso exige ClientId e ClientSecret de um client confidencial que seja o client de introspecção do servidor de recurso, e builder.Services.AddAuthioClient(builder.Configuration.GetSection("Authio")) ao lado do esquema bearer.

appsettings.jsonJSON
{
  "Authio": {
    "Authority": "https://authio.api.hyperio.tech",
    "Realm": "acme",
    "ClientId": "orders-api",
    "ClientSecret": "YOUR_CLIENT_SECRET",
    "Audiences": [ "https://api.example.com" ],
    "Introspection": "ActiveCheck"
  }
}

IntrospectionOnly valida só pela introspecção, para clients que emitem tokens assinados com HMAC ou criptografados, que não estão no JWKS.

Chame uma API a partir de um worker

Um serviço em segundo plano não tem usuário: ele se autentica como ele mesmo, com o grant client_credentials. AddAuthioClientCredentials coloca um token em cache em toda requisição de um HttpClient, renova o token antes de expirar, faz uma única requisição para chamadas simultâneas e, num 401 da API, busca um token novo e tenta mais uma vez.

Program.csC#
using Authio.Client.Extensions;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddAuthioClient(builder.Configuration.GetSection("Authio"));
builder.Services
    .AddHttpClient<OrdersApi>(client => client.BaseAddress = new Uri("https://api.example.com"))
    .AddAuthioClientCredentials(options =>
    {
        options.Scopes = ["orders:read"];
        options.Resource = "https://api.example.com";
    });
builder.Services.AddHostedService<NightlyReport>();

builder.Build().Run();

O client precisa do grant client_credentials e precisa estar liberado no servidor de recurso. Para obter um token você mesmo, injete IAuthioClientCredentialsTokenCache e chame GetTokenAsync(["orders:read"], "https://api.example.com"). Credenciais, consumers e rotação estão em Máquina a máquina.

Login de um SPA por um BFF

Tokens guardados no navegador podem ser lidos por qualquer script da página. Com o Authio.AspNetCore.Bff, o SPA e o host ASP.NET Core dele compartilham a mesma origem: o host é um client confidencial que roda o fluxo de código com PKCE, guarda os tokens no servidor e os renova, e o navegador só fica com um cookie HttpOnly __Host-authio. Toda chamada de API do SPA leva o cabeçalho X-CSRF: 1, que uma página de outro site não consegue acrescentar.

  1. Registre o client

    Um client confidencial com o grant authorization_code, o redirect URI https://app.example.com/signin-oidc, o post-logout redirect URI https://app.example.com/signout-callback-oidc e os escopos openid profile email offline_access — offline_access dá o refresh token —, mais roles se você ligar RequestRoles, e os escopos das suas APIs.

  2. Configure o host

    Program.csC#
    using Authio.AspNetCore.Bff.Extensions;
    
    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services.AddAuthioBff(builder.Configuration.GetSection("Authio"));
    
    var app = builder.Build();
    
    app.UseDefaultFiles();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthioBff();
    app.UseAuthorization();
    
    app.MapAuthioBffEndpoints();
    
    app.MapGet("/api/me", (HttpContext context) => new { name = context.User.FindFirst("name")?.Value })
        .RequireAuthorization()
        .AsAuthioBffApiEndpoint();
    
    app.MapAuthioRemoteApi("/api/orders", "https://api.example.com/orders");
    
    app.Run();
  3. Chame a partir do SPA

    api.tsTypeScript
    export const api = (url: string, init: RequestInit = {}) =>
      fetch(url, {
        ...init,
        credentials: 'same-origin',
        headers: { ...init.headers, 'X-CSRF': '1' },
      })
    
    const response = await api('/bff/user')
    
    if (response.status === 401) {
      window.location.href = '/bff/login?returnUrl=' + encodeURIComponent(location.pathname)
    }
EndpointO que faz
/bff/loginInicia o login. Recebe returnUrl (só local) e, opcionalmente, prompt, login_hint, ui_locales, organization, max_age e resource.
/bff/userCom X-CSRF: 1: as claims da sessão, um role e um permission por concessão, bff:session_expires_in e bff:logout_url. 401 sem sessão, nunca um redirecionamento.
/bff/logoutNavegue até o valor de bff:logout_url: ele revoga o refresh token, apaga a sessão e faz o logout no Authio.

MapAuthioRemoteApi encaminha para a API com o access token do usuário anexado e sem os cookies. A cada 5 minutos o BFF faz a introspecção do refresh token da sessão e desconecta a pessoa se a sessão dela no Authio terminou. Com mais de uma instância, registre um IDistributedCache compartilhado (Redis) e persista as chaves do Data Protection antes de AddAuthioBff.

Próximos passos

Cookies de analytics

Gostaríamos de medir como o console é usado, com o Google Analytics, para enxergar em quais telas as pessoas travam. Isso grava um cookie que reconhece este navegador na sua próxima visita.

Recusar não muda mais nada. Os cookies que mantêm você conectado e lembram o seu idioma e o seu tema são necessários para o console funcionar, e ficam de qualquer forma. Ler a Política de Privacidade