OAuth2 é o protocolo de autorização e OpenID Connect (OIDC) é a camada de identidade construída sobre ele. Juntos resolvem o problema central de autenticação distribuída: como um usuário prova sua identidade uma vez e acessa múltiplos serviços sem re-autenticar? Duende IdentityServer é a implementação de referência para .NET — usado por Microsoft, Okta e grandes SaaS ao redor do mundo.
Os três fluxos que você precisa conhecer
Authorization Code + PKCE: para aplicações web e SPAs onde um usuário humano faz login. O mais seguro — o access token nunca fica exposto na URL. PKCE (Proof Key for Code Exchange) protege contra interceptação do código de autorização.
Client Credentials: para comunicação máquina-a-máquina (M2M). Um microsserviço se autentica com client_id + client_secret e recebe um token para chamar outros serviços. Sem usuário no fluxo.
Refresh Token: access tokens têm vida curta (15–60 min). Refresh tokens permitem obter novos access tokens sem pedir ao usuário para fazer login novamente.
Configurando o Duende IdentityServer
dotnet add package Duende.IdentityServer
// Program.cs do projeto IdentityServer:
builder.Services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
})
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryApiResources(Config.ApiResources)
.AddInMemoryClients(Config.Clients)
.AddAspNetIdentity<ApplicationUser>() // integração com ASP.NET Core Identity
.AddDeveloperSigningCredential(); // em produção: .AddSigningCredential(cert)
app.UseIdentityServer();
// Config.cs — definindo recursos, escopos e clientes:
public static class Config
{
public static IEnumerable<IdentityResource> IdentityResources =>
[
new IdentityResources.OpenId(), // sub (subject/user id)
new IdentityResources.Profile(), // name, email, picture
new IdentityResources.Email(),
new IdentityResource("tenant", "Tenant ID", ["tenant_id", "tenant_slug"]),
];
public static IEnumerable<ApiScope> ApiScopes =>
[
new ApiScope("api.read", "Leitura da API"),
new ApiScope("api.write", "Escrita na API"),
new ApiScope("api.admin", "Acesso administrativo"),
];
public static IEnumerable<ApiResource> ApiResources =>
[
new ApiResource("neryx-api", "Neryx API")
{
Scopes = { "api.read", "api.write", "api.admin" },
UserClaims = { "tenant_id", "role" },
}
];
public static IEnumerable<Client> Clients =>
[
// SPA React — Authorization Code + PKCE (sem client_secret)
new Client
{
ClientId = "neryx-spa",
ClientName = "Neryx Web App",
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
RequireClientSecret = false, // SPAs não podem guardar secrets
RedirectUris = { "https://app.neryx.com.br/auth/callback" },
PostLogoutRedirectUris = { "https://app.neryx.com.br/" },
AllowedCorsOrigins = { "https://app.neryx.com.br" },
AllowedScopes = { "openid", "profile", "email", "tenant", "api.read", "api.write" },
AllowOfflineAccess = true, // habilita refresh tokens
AccessTokenLifetime = 900, // 15 minutos
RefreshTokenExpiration = TokenExpiration.Sliding,
SlidingRefreshTokenLifetime = 86400 * 30, // 30 dias
},
// Microsserviço — Client Credentials (M2M, sem usuário)
new Client
{
ClientId = "pedidos-service",
ClientSecrets = { new Secret("secret-pedidos".Sha256()) },
AllowedGrantTypes = GrantTypes.ClientCredentials,
AllowedScopes = { "api.read", "api.write" },
AccessTokenLifetime = 3600, // 1 hora
},
// App mobile — Authorization Code + PKCE com custom scheme
new Client
{
ClientId = "neryx-mobile",
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
RequireClientSecret = false,
RedirectUris = { "com.neryx.app://auth/callback" },
AllowedScopes = { "openid", "profile", "email", "api.read", "api.write" },
AllowOfflineAccess = true,
AccessTokenLifetime = 900,
},
];
}
Protegendo a API com JWT Bearer
// Na API .NET que será protegida: builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = "https://auth.neryx.com.br"; // URL do IdentityServer options.Audience = "neryx-api";options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ClockSkew = TimeSpan.FromSeconds(30), // tolerância de clock entre servidores }; // Para Reference Tokens (mais seguro que JWT — revogação imediata): // options.ForwardDefaultSelector = Selector.ForwardReferenceToken("introspection"); });builder.Services.AddAuthorization(options => { options.AddPolicy(“api-leitura”, policy => policy.RequireAuthenticatedUser() .RequireClaim(“scope”, “api.read”));
options.AddPolicy("api-escrita", policy => policy.RequireAuthenticatedUser() .RequireClaim("scope", "api.write")); options.AddPolicy("admin", policy => policy.RequireAuthenticatedUser() .RequireClaim("scope", "api.admin") .RequireClaim("role", "admin")); options.AddPolicy("mesmo-tenant", policy => policy.RequireAuthenticatedUser() .RequireAssertion(ctx => ctx.User.HasClaim("tenant_id", ctx.Resource?.ToString() ?? "")));});
// Endpoints protegidos: app.MapGet(“/api/produtos”, GetProdutos).RequireAuthorization(“api-leitura”); app.MapPost(“/api/produtos”, CriarProduto).RequireAuthorization(“api-escrita”); app.MapDelete(“/api/admin/usuarios”, DeletarUsuario).RequireAuthorization(“admin”);
Client Credentials: microsserviço chamando outro
dotnet add package IdentityModel// Registrar cliente M2M com renovação automática de token: builder.Services.AddClientCredentialsTokenManagement() .AddClient(“pedidos-service”, client => { client.TokenEndpoint = “https://auth.neryx.com.br/connect/token”; client.ClientId = “pedidos-service”; client.ClientSecret = builder.Configuration[“Auth:ClientSecret”]; client.Scope = “api.read api.write”; });
// HttpClient que injeta o token automaticamente: builder.Services.AddClientCredentialsHttpClient(“produtos-api”, “pedidos-service”, client => client.BaseAddress = new Uri(“https://api.neryx.com.br”));
// Uso no serviço: public class PedidoService { private readonly HttpClient _produtosApi;
public PedidoService(IHttpClientFactory factory) => _produtosApi = factory.CreateClient("produtos-api"); // O token Bearer é injetado automaticamente — zero boilerplate public async Task<ProdutoDto?> GetProdutoAsync(Guid id, CancellationToken ct) => await _produtosApi.GetFromJsonAsync<ProdutoDto>($"/api/produtos/{id}", ct);
}
Claims customizadas: enriquecendo o token com dados do tenant
// IProfileService — adiciona claims ao token no momento da geração: public class ProfileService : IProfileService { private readonly UserManager<ApplicationUser> _users; private readonly ITenantRepository _tenants;public async Task GetProfileDataAsync(ProfileDataRequestContext context) { var user = await _users.GetUserAsync(context.Subject); if (user == null) return; var tenant = await _tenants.GetByUserIdAsync(user.Id); var claims = new List<Claim> { new("email", user.Email!), new("name", user.FullName), new("role", user.Role), }; // Adiciona claims do tenant se o cliente solicitou o scope "tenant" if (context.RequestedClaimTypes.Contains("tenant_id") && tenant != null) { claims.Add(new("tenant_id", tenant.Id.ToString())); claims.Add(new("tenant_slug", tenant.Slug)); claims.Add(new("tenant_plan", tenant.Plan)); } context.IssuedClaims.AddRange(claims); } public async Task IsActiveAsync(IsActiveContext context) { var user = await _users.GetUserAsync(context.Subject); context.IsActive = user != null && !user.IsBlocked; }}
// Registrar: builder.Services.AddTransient<IProfileService, ProfileService>();
Revogação de tokens e logout global (SSO)
// Logout que invalida sessão em todos os clientes (back-channel logout): [HttpPost("logout")] public async Task<IActionResult> Logout(string? returnUrl = null) { // Limpa cookie de autenticação local await HttpContext.SignOutAsync();// Redireciona para IdentityServer que notifica todos os clientes via back-channel return SignOut(new AuthenticationProperties { RedirectUri = returnUrl ?? "/" }, CookieAuthenticationDefaults.AuthenticationScheme, OpenIdConnectDefaults.AuthenticationScheme);}
// Introspection endpoint — para Reference Tokens (revogação imediata): // Ao usar Reference Tokens, a API chama o IdentityServer a cada request para validar // Mais seguro que JWT (pode revogar imediatamente), mas adiciona latência // Trade-off: JWT = sem latência extra, mas token válido até expirar mesmo após logout // Reference Token = latência extra por request, mas revogação instantânea
builder.Services.AddAuthentication() .AddOAuth2Introspection(“introspection”, options => { options.Authority = “https://auth.neryx.com.br”; options.ClientId = “neryx-api”; options.ClientSecret = “api-introspection-secret”; options.EnableCaching = true; options.CacheDuration = TimeSpan.FromMinutes(5); // cache para reduzir chamadas });
Ambiente de produção: signing certificates e persistência
// Em produção, substitua AddDeveloperSigningCredential por certificado real: var cert = new X509Certificate2( builder.Configuration["IdentityServer:CertPath"], builder.Configuration["IdentityServer:CertPassword"]);
builder.Services.AddIdentityServer() .AddSigningCredential(cert) // assina os tokens JWTs .AddValidationKey(certAnterior) // mantém chave anterior para tokens ainda válidos .AddOperationalStore(options => // persiste grants, tokens e device codes { options.ConfigureDbContext = b => b.UseNpgsql(connString); options.EnableTokenCleanup = true; // limpeza automática de tokens expirados options.TokenCleanupInterval = 3600; // a cada hora });
Quando usar IdentityServer vs Auth0 vs Cognito
Duende IdentityServer é ideal quando você precisa de controle total sobre o fluxo de autenticação (claims customizadas, lógica de multi-tenancy, integração com LDAP/AD), quer rodar on-premise ou em VPC privada, ou tem volume suficiente para justificar o custo de licença. Auth0 e AWS Cognito são serviços gerenciados — menos controle, mais conveniência. Para SaaS early-stage sem requisitos especiais, Auth0 ou Cognito evitam a complexidade de operar o servidor de identidade.
Conclusão
OAuth2 + OIDC com Duende IdentityServer centraliza toda a autenticação da plataforma — uma única fonte de verdade para identidade, tokens e sessões. O Client Credentials Flow elimina tokens hardcoded entre microsserviços, e o SSO melhora a experiência do usuário em sistemas com múltiplas aplicações. O investimento em configurar corretamente paga dividendos quando o sistema escala.
Se você precisa implementar autenticação centralizada para uma plataforma SaaS ou migrar de tokens hardcoded para OAuth2, a Neryx pode desenhar e implementar a solução. Consultoria inicial gratuita.
Leitura complementar: