.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
| Pacote | Para que serve |
|---|---|
| Authio.AspNetCore | Uma 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.Bff | O host de um SPA (React, Angular, Vue…) que faz login com o Authio sem tokens no navegador. |
| Authio.Client | Todo 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. |
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 AuthioAponte 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.
{
"Authio": {
"Authority": "https://authio.api.hyperio.tech",
"Realm": "acme",
"ClientId": "acme-web",
"ClientSecret": "YOUR_CLIENT_SECRET"
}
}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.
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ção | O que o handler exige |
|---|---|
| Emissor e audiência | Exatamente o emissor do realm, e uma das Audiences. |
| Assinatura e validade | Uma chave do JWKS do realm com algoritmo assimétrico (RS, PS, ES); exp e nbf com 30 segundos de ClockSkew. |
| Tipo do token | A claim typ precisa ser Bearer, então ID tokens e refresh tokens são recusados. |
| Claims | Mantidas 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().
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ência | Helper | Passa quando |
|---|---|---|
| Escopo | RequireAuthioScope / [AuthioScope] | Algum (ou, com AuthioMatch.All, todos) dos escopos está em scope. |
| Papel | RequireAuthioRole / [AuthioRole] | O usuário tem o papel, sem diferenciar maiúsculas. Precisa do escopo roles. |
| Permissão | RequireAuthioPermission / [AuthioPermission] | Algum papel do usuário concede o método no recurso, como orders / write. |
| Organização | RequireAuthioOrganization / [AuthioOrganization] | org_id está presente, ou bate com o alias ou o ID informado. |
| Força do login | RequireAuthioAuthenticationLevel / RequireAuthioAuthenticationMethod | acr atinge o nível, ou amr contém o método (mfa, otp, swk…). |
| Client | RequireAuthioClient / [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
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.
{
"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.
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.
Registre o client
Um client confidencial com o grant
authorization_code, o redirect URIhttps://app.example.com/signin-oidc, o post-logout redirect URIhttps://app.example.com/signout-callback-oidce os escoposopenid profile email offline_access—offline_accessdá o refresh token —, maisrolesse você ligarRequestRoles, e os escopos das suas APIs.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();Chame a partir do SPA
api.tsTypeScriptexport 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) }
| Endpoint | O que faz |
|---|---|
/bff/login | Inicia o login. Recebe returnUrl (só local) e, opcionalmente, prompt, login_hint, ui_locales, organization, max_age e resource. |
/bff/user | Com 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/logout | Navegue 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.