ErtisAuth

.NET SDK

İstemci SDK'sı ve ASP.NET Core entegrasyonu.

ErtisAuth iki .NET kütüphanesiyle gelir:

PaketNe yapar
ErtisAuth.SdkErtisAuth REST API'si için tipli bir istemci: giriş, token'lar, kullanıcılar, roller, uygulamalar, hook'lar…
ErtisAuth.Sdk.AspNetCoreKendi ASP.NET Core API'lerinizi attribute'larla, ErtisAuth token'ları ve yetkileriyle korur. ErtisAuth.Sdk'yı içerir.

Endpoint'lerini ErtisAuth kullanıcılarının ve uygulamalarının çağırdığı bir ASP.NET Core API'si için ErtisAuth.Sdk.AspNetCore'u kullanın. ErtisAuth'u bir worker'dan, bir console uygulamasından ya da başka bir .NET programından çağırmak için yalnızca ErtisAuth.Sdk yeterlidir. İkisi de .NET 10 hedefler.

Hızlı başlangıç#

Bir ASP.NET Core API'sini dört adımda koruyun.

1. Paketi ekleyin:

shell
dotnet add package ErtisAuth.Sdk.AspNetCore

2. Ayarları appsettings.json dosyasına ekleyin:

json
{
	"ErtisAuth": {
		"BaseUrl": "https://auth.example.com",
		"MembershipId": "66f1c0d2a4b5c6d7e8f90123"
	}
}

3. SDK'yı Program.cs içinde kaydedin:

csharp
using ErtisAuth.Sdk.AspNetCore.Extensions;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddErtisAuth();

var app = builder.Build();

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

app.Run();

4. Bir controller'ı koruyun:

csharp
using ErtisAuth.Core.Models.Roles;
using ErtisAuth.Extensions.Authorization.Attributes;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("orders")]
[Authorized]
[RbacResource("orders")]
public class OrdersController : ControllerBase
{
	// Requires "orders.read"
	[HttpGet]
	[RbacAction(Rbac.CrudActions.Read)]
	public IActionResult List() => this.Ok();
}

GET /orders endpoint'ini bir ErtisAuth kullanıcısının access token'ıyla (Authorization: Bearer <access_token>) ya da bir uygulamanın Basic token'ıyla çağırın:

İstekYanıt
Token olmadan ya da geçersiz, süresi dolmuş veya iptal edilmiş bir token'la401
Rolü orders.read izni vermeyen bir kullanıcı ya da uygulama403 AccessDenied
Rolü orders.read izni veren bir kullanıcı ya da uygulama200

Yapılandırma#

AyarZorunluAçıklama
BaseUrlevetErtisAuth'un, varsa yol öneki dahil, mutlak http ya da https URL'si.
MembershipIdevetUygulamanızın ait olduğu membership.
BasicTokenCacheTTLhayırDoğrulanmış bir Basic token'ın API'niz tarafından önbellekte tutulacağı saniye (ErtisAuth.Sdk.AspNetCore). 0 ya da verilmezse önbellek kapalıdır.

Ayarlar nereden okunur#

AddErtisAuth(), uygulamanızın yapılandırmasındaki (builder.Configuration) ErtisAuth bölümünü okur. Uygulamanın tüm yapılandırma kaynakları geçerlidir: appsettings.json ve appsettings.{Environment}.json, ortam değişkenleri, user secrets, komut satırı argümanları, Azure Key Vault vb. Örneğin Kubernetes'te:

yaml
env:
  - name: ErtisAuth__BaseUrl
    value: https://auth.example.com
  - name: ErtisAuth__MembershipId
    value: 66f1c0d2a4b5c6d7e8f90123

Ayarlar uygulama başlarken doğrulanır. Geçersiz ayarlar, tüm sorunları listeleyen bir mesajla uygulamayı durdurur.

Ayarları vermenin diğer yolları:

ÇağrıOkunan
AddErtisAuth()ErtisAuth bölümü
AddErtisAuth("Identity")Yapılandırmanın başka bir bölümü
AddErtisAuth(builder.Configuration.GetSection("Identity"))Verilen bölüm, hemen doğrulanır
AddErtisAuth(options => { … })Yalnızca verilen değerler; hiçbir yapılandırma okunmaz
csharp
builder.Services.AddErtisAuth(options =>
{
	options.BaseUrl = "https://auth.example.com";
	options.MembershipId = "66f1c0d2a4b5c6d7e8f90123";
});

AddErtisAuth SDK'yı bir kez kaydeder: sonraki çağrılar, ayarları ne olursa olsun yok sayılır.

Not: .NET host'u olmayan bir programın (WebApplication.CreateBuilder ya da Host.CreateApplicationBuilder kullanmayan) uygulama yapılandırması yoktur. Bu durumda AddErtisAuth(), çıktı klasöründeki appsettings.json ve appsettings.{ASPNETCORE_ENVIRONMENT}.json dosyalarını ve ortam değişkenlerini okur.

Endpoint'leri koruma#

Hangi endpoint'ler korunur#

Controller'ı [Authorized] ile işaretleyin: controller'ın her action'ı geçerli bir token ve rbac attribute'larının tanımladığı yetkiyi gerektirir. Tek bir action için istisna yapmak isterseniz action'ı işaretleyin:

En spesifik attribute geçerlidir: action'daki attribute controller'dakini ezer.

ControllerActionEndpoint
[Authorized]token ve yetki gerektirir
[Authorized][Unauthorized]herkese açıktır
[Authorized][SelfAuthorized]token gerektirir; yetkiyi action kontrol eder
[Unauthorized][SelfAuthorized]token gerektirir; yetkiyi action kontrol eder
[SelfAuthorized]token gerektirir; yetkiyi action kontrol eder
hiç korunmaz

[Authorized] yalnızca controller'a konabilir; action'larda tekrarlamaya gerek yoktur.

Uyarı: [Authorized] ya da [SelfAuthorized] yoksa endpoint herkese açıktır ve rbac attribute'ları hiçbir işe yaramaz. SDK bunu derleme sırasında bildirir (ERTISAUTH610, bkz. Derleme zamanı kontrolleri).

Attribute'lar#

Attribute'lar ErtisAuth.Extensions.Authorization.Attributes namespace'indedir.

AttributeNeredeAnlamı
[Authorized]controllerHer action'da geçerli bir token ve rbac attribute'larının tanımladığı yetki gerekir.
[SelfAuthorized]controller ya da actionGeçerli bir token gerekir; yetkiyi action'ın kendisi kontrol eder.
[Unauthorized]controller ya da actionHerkese açık: token gerekmez.
[RbacResource("orders")]controller ya da actionYetkinin resource bölümü.
[RbacAction("approve")] ya da [RbacAction(Rbac.CrudActions.Read)]actionaction bölümü.
[RbacObject("{id}")]actionobject bölümü; genellikle route'taki id.
[RbacSubject("…")]actionsubject bölümü.

Kontrol edilen yetki#

Rbac attribute'ları bir yetki ifadesi oluşturur: subject.resource.action.object. ErtisAuth bu ifadeyi çağıranın rolüne, kendi yetkilerine ve token'ının scope'larına göre kontrol eder.

BölümKaynağıAttribute verilmezse
subject[RbacSubject]Çağıranın id'si; neredeyse her zaman istediğiniz de budur
resourceAction'ın, yoksa controller'ın [RbacResource]'u⚠️ Route şablonunun son parçası, ör. orders/{id} için {id}
action[RbacAction]⚠️ *: yalnızca kaynak üzerindeki her işleme izin veren yetkiler geçer
object[RbacObject]*

[RbacResource] ve [RbacAction]'ı her zaman verin. Bir action'daki [RbacResource], controller'dakini ezer; örneğin [RbacResource("users")] bir controller'ın tek bir action'ındaki [RbacResource("otp")].

csharp
using ErtisAuth.Core.Models.Roles;
using ErtisAuth.Extensions.Authorization.Attributes;
using ErtisAuth.Sdk.AspNetCore.Extensions;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("orders")]
[Authorized]
[RbacResource("orders")]
public class OrdersController : ControllerBase
{
	// Requires "orders.read"
	[HttpGet]
	[RbacAction(Rbac.CrudActions.Read)]
	public IActionResult List() { … }

	// Requires "orders.read.{id}" (granted by "orders.read" too)
	[HttpGet("{id}")]
	[RbacAction(Rbac.CrudActions.Read)]
	[RbacObject("{id}")]
	public IActionResult Get(string id) { … }

	// A custom action: requires "orders.approve.{id}"
	[HttpPost("{id}/approve")]
	[RbacAction("approve")]
	[RbacObject("{id}")]
	public IActionResult Approve(string id)
	{
		var utilizer = this.GetUtilizer();
		// utilizer.Id, utilizer.Username, utilizer.Role, utilizer.Type (User or Application)…
		…
	}

	// Another resource: requires "invoices.read.{id}"
	[HttpGet("{id}/invoice")]
	[RbacResource("invoices")]
	[RbacAction(Rbac.CrudActions.Read)]
	[RbacObject("{id}")]
	public IActionResult Invoice(string id) { … }

	// Public
	[HttpGet("statuses")]
	[Unauthorized]
	public IActionResult Statuses() { … }
}

Yetkiyi kendisi kontrol eden endpoint'ler#

Yetki, attribute'ların erişemediği bir veriye bağlıysa (örneğin istenen kaydın bir alanına) action'ı [SelfAuthorized] ile işaretleyin. SDK token'ın kimliğini doğrular; action ise yetkiyi çağıranın token'ıyla IRoleService.CheckPermissionAsync üzerinden kontrol eder:

csharp
using ErtisAuth.Core.Models.Identity;
using ErtisAuth.Extensions.Authorization.Attributes;
using ErtisAuth.Sdk.AspNetCore.Extensions;
using ErtisAuth.Sdk.Services.Interfaces;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("documents")]
public class DocumentsController(IDocumentStore documents, IRoleService roleService) : ControllerBase
{
	// Requires "documents-{category}.read.{id}", where the category is a field of the document
	[HttpGet("{id}")]
	[SelfAuthorized]
	public async Task<IActionResult> Get(string id, CancellationToken cancellationToken)
	{
		var document = await documents.GetAsync(id, cancellationToken);
		if (document == null)
		{
			return this.NotFound();
		}

		var utilizer = this.GetUtilizer()!.Value;
		TokenBase token = utilizer.TokenType == SupportedTokenTypes.Basic ? new BasicToken(utilizer.Token!) : BearerToken.CreateTemp(utilizer.Token!);
		if (!await roleService.CheckPermissionAsync($"documents-{document.Category}.read.{id}", token, cancellationToken))
		{
			return this.StatusCode(StatusCodes.Status403Forbidden);
		}

		return this.Ok(document);
	}
}

CheckPermissionAsync, attribute'lar gibi rolü, çağıranın kendi yetkilerini ve token'ının scope'larını uygular. ErtisAuth'a ulaşılamazsa exception fırlatır.

Minimal API'ler#

Minimal API endpoint'leri aynı attribute'ları metadata olarak alır:

csharp
app.MapGet("/orders", () => Results.Ok())
	.WithMetadata(new AuthorizedAttribute(), new RbacResourceAttribute("orders"), new RbacActionAttribute(Rbac.CrudActions.Read));

app.MapGet("/profile", () => Results.Ok())
	.WithMetadata(new SelfAuthorizedAttribute());

Derleme zamanı kontrolleri minimal API endpoint'lerini kapsamaz, çünkü attribute'ları ancak çalışma zamanında bilinir.

Bir istekte ne olur#

Bearer token için SDK:

  1. token'ın kimliğini doğrulamak için ErtisAuth'un /whoami endpoint'ini çağırır,
  2. attribute'lardan oluşturulan ifadeyle /roles/check-permission endpoint'ini çağırır; bu endpoint rolü, kullanıcının kendi yetkilerini ve token'ın scope'larını uygular.

Basic token için uygulamayı token'ın kendisiyle ErtisAuth'tan okur (bu, token'ın kimliğini doğrular) ve yetkiyi aynı şekilde kontrol eder. BasicTokenCacheTTL ile doğrulanmış bir Basic token o kadar saniye önbellekte tutulur; bu çağrıları azaltır ama bir secret yenilemesinin etkisini en fazla o süre kadar geciktirir.

API'nizin yanıtıNe zaman
ErtisAuth hata gövdesiyle 401Token yok, desteklenmeyen token tipi ya da geçersiz, süresi dolmuş veya iptal edilmiş bir token
403 AccessDeniedToken geçerli ama yetki eksik
503 AuthenticationServiceUnavailableErtisAuth'a ulaşılamadı ya da bir hatayla yanıt verdi. İstemciler kullanıcıyı çıkış yaptırmak yerine yeniden denemelidir.

Placeholder'lar#

Rbac attribute değerleri, her istekte çözülen placeholder'lar içerebilir:

PlaceholderKaynak
{id} ya da {route::id}Bir route değeri
{query::id}Bir query string parametresi
{header::X-Tenant}Bir istek header'ı
{env::REGION}Bir ortam değişkeni
csharp
[HttpGet("reports")]
[RbacAction(Rbac.CrudActions.Read)]
[RbacObject("{query::reportId}")]
public IActionResult Report([FromQuery] string reportId) { … }

Kontrol ile action'ın aynı şeye bakmasını sağlayan kurallar:

  • çözülemeyen bir query, header ya da env placeholder'ı isteği reddeder; değeri eksik bir route placeholder'ı ise yazıldığı gibi kalır;
  • çözülen değer * ise ya da nokta (%2E), boşluk veya kontrol karakteri içeriyorsa istek reddedilir;
  • [RbacSubject], istemcinin kontrol ettiği kaynakları (query, header) kullanamaz.

Derleme zamanı kontrolleri#

ErtisAuth.Sdk.AspNetCore, hataları derleme sırasında bildiren Roslyn analyzer'ları içerir:

TanılamaÖnemSorun
ERTISAUTH601uyarıPlaceholder, action'ın bir route parametresi değil
ERTISAUTH602hataPlaceholder bilinmeyen bir kaynak belirtiyor (yalnızca route, query, header ve env vardır)
ERTISAUTH603hata[RbacSubject] istemcinin kontrol ettiği bir kaynaktan okuyor
ERTISAUTH610uyarıAction'ın rbac attribute'ları var, ama ne action'da ne controller'ında [Authorized] ya da [SelfAuthorized] var: endpoint herkese açık
ERTISAUTH611uyarı[Authorized], [SelfAuthorized] ve [Unauthorized] aynı seviyede çelişiyor (base sınıflarıyla birlikte controller ya da action)
ERTISAUTH612bilgi[SelfAuthorized] ya da [Unauthorized] bir action'ın rbac attribute'ları kontrol edilmiyor
ERTISAUTH613uyarıKimlik doğrulanmayan bir action GetUtilizer() çağırıyor; orada her zaman null döner

Çağıran#

Bir action içinde this.GetUtilizer(), ErtisAuth'un kimliğini doğruladığı çağıranı döner: Id, Username, Role, Permissions, Forbidden, MembershipId, Type, Token ve TokenType özellikleriyle bir kullanıcı ya da uygulama. Kimlik doğrulanmayan endpoint'lerde ([Unauthorized] ya da ErtisAuth attribute'u olmayan) null döner; böyle bir action'daki çağrıyı ERTISAUTH613 bildirir.

this.GetUnverifiedUtilizer() bu endpoint'lerde de çalışır: orada isteğin token'ını doğrulamadan okur (Bearer token'larda imza, süre ve iptal kontrolü, Basic token'larda secret kontrolü yapılmaz).

Uyarı: herkes, istediği kimliği iddia eden bir token gönderebilir. GetUnverifiedUtilizer()'ı yetki kararlarında ya da veriye erişimde asla kullanmayın; [Authorized] ya da [SelfAuthorized] bir endpoint'te GetUtilizer() kullanın.

ErtisAuth API'sini çağırma#

İstemciyi tek başına (ASP.NET Core entegrasyonu olmadan) ErtisAuth.Sdk ile kaydedin:

csharp
using ErtisAuth.Sdk.Extensions;

builder.Services.AddErtisAuth();

Aynı ayarları kullanır. Ardından ihtiyacınız olan servisleri inject edin:

ServisMetotlar
IAuthenticationServiceGetTokenAsync, RefreshTokenAsync, VerifyTokenAsync, RevokeTokenAsync, MeAsync, WhoAmIAsync
IUserServiceGetAsync, QueryAsync, CreateAsync, UpdateAsync, DeleteAsync, BulkDeleteAsync, GetActiveTokensAsync, GetRevokedTokensAsync
IPasswordServiceChangePasswordAsync, ResetPasswordAsync, SetPasswordAsync
IRoleServiceCRUD, CheckPermissionAsync, CheckPermissionByRoleAsync
IApplicationService, IWebhookService, IMailHookServiceCRUD
IMembershipServiceGetMembershipAsync, GetMembershipsAsync, QueryMembershipsAsync, CreateMembershipAsync, UpdateMembershipAsync, DeleteMembershipAsync
IActiveTokensService, IRevokedTokensServiceGetAsync, QueryAsync

IAuthenticationService, ASP.NET Core'un Microsoft.AspNetCore.Authentication.IAuthenticationService arayüzüyle aynı adı taşır; iki namespace birden import edilmişse tam adı ya da bir alias kullanın.

Token'lar#

Kaynak servisleri, çağrıda kullanılacak token'ı parametre olarak alır:

  • kullanıcı adına yapılan çağrılar için giriş yapmış kullanıcının bir BearerToken'ı: BearerToken.CreateTemp(accessToken) ham bir access token'ı sarmalar;
  • backend'inizin çağrıları için uygulamanızın bir BasicToken'ı: new BasicToken($"{applicationId}:{applicationSecret}").

Uygulama secret'ını sunucuda tutun: bir secret store'da ya da bir ortam değişkeninde; asla bir tarayıcıda ya da mobil uygulamada değil.

Yanıtları işleme#

Her metot, HTTP hatalarında exception fırlatmak yerine bir IResponseResult (ya da IResponseResult<T>) döner:

Özellikİçerik
IsSuccessErtisAuth'un başarılı bir durum koduyla yanıt verip vermediği
DataBaşarı durumunda sonuç
StatusCodeErtisAuth yanıtının HTTP durum kodu; yanıt hiç gelmediyse (ör. bir ağ hatası) null
JsonYanıtın gövdesi: hata durumunda ErtisAuth'un hata JSON'u
ExceptionYanıt hiç gelmediğinde exception

response.IsServiceUnavailable() (namespace ErtisAuth.Sdk.Extensions), ErtisAuth'un erişilemez olmasını (yanıt yok ya da 5xx) bir reddetmeden (ör. 401, 403) ayırır. İkisini farklı ele alın: erişilemezlik durumunda yeniden denenmeli, reddetmede denenmemelidir.

ErtisAuth'un hatalarını istemciye ileten, backend'inizdeki bir giriş endpoint'i:

csharp
using System.Net;
using ErtisAuth.Extensions.Authorization.Attributes;
using ErtisAuth.Sdk.Extensions;
using Microsoft.AspNetCore.Mvc;
using IErtisAuthAuthenticationService = ErtisAuth.Sdk.Services.Interfaces.IAuthenticationService;

[ApiController]
[Route("account")]
public class AccountController(IErtisAuthAuthenticationService authenticationService) : ControllerBase
{
	[HttpPost("sign-in")]
	[Unauthorized]
	public async Task<IActionResult> SignIn(SignInRequest request, CancellationToken cancellationToken)
	{
		var response = await authenticationService.GetTokenAsync(
			request.Username,
			request.Password,
			ipAddress: this.HttpContext.Connection.RemoteIpAddress?.ToString(),
			userAgent: this.Request.Headers.UserAgent,
			cancellationToken: cancellationToken);

		if (response.IsSuccess)
		{
			return this.Ok(response.Data);
		}

		if (response.IsServiceUnavailable())
		{
			return this.StatusCode((int)HttpStatusCode.ServiceUnavailable);
		}

		// e.g. 401 InvalidCredentials, as ErtisAuth answered it
		return new ContentResult { StatusCode = (int?)response.StatusCode, Content = response.Json, ContentType = "application/json" };
	}
}

Sık yapılan işler#

Access token'ın süresi dolmadan token çiftini yenileme:

csharp
var response = await authenticationService.RefreshTokenAsync(refreshToken, cancellationToken);
if (response.IsSuccess)
{
	var tokens = response.Data!; // tokens.AccessToken, tokens.RefreshToken…
}

Çıkış yapma (logoutFromAllDevices: true ile tüm cihazlarda):

csharp
await authenticationService.RevokeTokenAsync(accessToken, logoutFromAllDevices: false, cancellationToken);

Uygulamanın token'ıyla kullanıcı oluşturma. Kullanıcı tipinin özel alanları, UserWithPassword'dan türeyen bir sınıfın özellikleridir:

csharp
public class Employee : UserWithPassword
{
	[JsonPropertyName("department")]
	public string? Department { get; set; }
}

var response = await userService.CreateAsync(new Employee
{
	MembershipId = membershipId, // the MembershipId of the SDK settings
	Username = "ada",
	EmailAddress = "ada@example.com",
	FirstName = "Ada",
	LastName = "Lovelace",
	Password = password,
	Role = "support",
	UserType = "employee",
	Department = "Engineering"
}, appToken, cancellationToken);

Parola sıfırlama (bkz. Hesap Kurtarma): sıfırlamayı sıfırlama sayfanızın URL'siyle başlatın, ardından linkteki token'la yeni parolayı belirleyin:

csharp
await passwordService.ResetPasswordAsync(emailAddress, "https://app.example.com/reset-password", appToken, cancellationToken);

// On the reset page, with the token of the link
await passwordService.SetPasswordAsync(emailAddress, newPassword, resetToken, appToken, cancellationToken);

Kullanıcıları sorgulama:

csharp
var users = await userService.QueryAsync(appToken, """{ "where": { "is_active": false } }""", limit: 50, sorting: null);
Not: GetAsync ve QueryAsync'in yalnızca sıralama parametreleriyle ayrılan iki overload'ı vardır (sorting, ya da orderBy ve sortDirection). Sıralama yapmıyorsanız birini adıyla verin (sorting: null); aksi halde çağrı belirsiz olur ve derlenmez (CS0121).

ASP.NET Core olmayan uygulamalar#

Bir worker ya da console uygulaması ErtisAuth.Sdk'yı .NET generic host ile kullanır; bu ona aynı yapılandırmayı sağlar:

csharp
using ErtisAuth.Sdk.Extensions;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddErtisAuth();
builder.Services.AddHostedService<InactiveUsersReport>();

builder.Build().Run();
csharp
using ErtisAuth.Core.Models.Identity;
using ErtisAuth.Sdk.Services.Interfaces;

public class InactiveUsersReport(IUserService userService, IConfiguration configuration, ILogger<InactiveUsersReport> logger) : BackgroundService
{
	protected override async Task ExecuteAsync(CancellationToken stoppingToken)
	{
		var appToken = new BasicToken($"{configuration["Report:ApplicationId"]}:{configuration["Report:ApplicationSecret"]}");
		var response = await userService.QueryAsync(appToken, """{ "where": { "is_active": false } }""", withCount: true, sorting: null, cancellationToken: stoppingToken);
		if (response.IsSuccess)
		{
			logger.LogInformation("{Count} inactive users", response.Data!.Count);
		}
	}
}

Özel authentication handler#

AddErtisAuth<THandler>(), SDK'nın handler'ı yerine kendi authentication handler'ınızı kaydeder ve AddErtisAuth() ile aynı argümanları alır. ErtisAuth kontrollerini koruyup çevresine kendi davranışınızı eklemek için handler'ı ErtisAuthAuthenticationHandler'dan türetin:

csharp
using System.Text.Encodings.Web;
using ErtisAuth.Core.Models.Identity;
using ErtisAuth.Sdk.AspNetCore.Middleware;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;

public class AuditedAuthenticationHandler(
	IAuthorizationHandler<BasicToken> basicAuthorizationHandler,
	IAuthorizationHandler<BearerToken> bearerAuthorizationHandler,
	IOptionsMonitor<AuthenticationSchemeOptions> options,
	ILoggerFactory logger,
	UrlEncoder encoder)
	: ErtisAuthAuthenticationHandler(basicAuthorizationHandler, bearerAuthorizationHandler, options, logger, encoder)
{
	protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
	{
		var result = await base.HandleAuthenticateAsync();
		if (result.Failure != null)
		{
			this.Logger.LogWarning("Request to {Path} was not authenticated: {Reason}", this.Request.Path, result.Failure.Message);
		}

		return result;
	}
}
csharp
builder.Services.AddErtisAuth<AuditedAuthenticationHandler>();

API'nizi test etme#

API'nizin entegrasyon testleri (örneğin WebApplicationFactory ile) çalışan bir ErtisAuth'a ihtiyaç duymamalı. Program.cs'i değiştirmeden handler'ı, bir test çağıranını giriş yaptıran bir handler'la değiştirin. AddErtisAuth yalnızca bir kez kaydettiği için test handler'ını ErtisAuthAuthenticationHandler olarak kaydedin:

csharp
using System.Security.Claims;
using System.Text.Encodings.Web;
using ErtisAuth.Core.Models.Identity;
using ErtisAuth.Extensions.Authorization.Extensions;
using ErtisAuth.Sdk.AspNetCore.Middleware;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;

public class TestAuthenticationHandler(
	IAuthorizationHandler<BasicToken> basicAuthorizationHandler,
	IAuthorizationHandler<BearerToken> bearerAuthorizationHandler,
	IOptionsMonitor<AuthenticationSchemeOptions> options,
	ILoggerFactory logger,
	UrlEncoder encoder)
	: ErtisAuthAuthenticationHandler(basicAuthorizationHandler, bearerAuthorizationHandler, options, logger, encoder)
{
	protected override Task<AuthenticateResult> HandleAuthenticateAsync()
	{
		var utilizer = new Utilizer
		{
			Id = "test-user",
			Username = "test",
			Role = "admin",
			Type = Utilizer.UtilizerType.User,
			MembershipId = "test-membership",
			Token = "test-token",
			TokenType = SupportedTokenTypes.Bearer
		};

		var principal = new ClaimsPrincipal(utilizer.ToClaimsIdentity());
		return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(principal, this.Scheme.Name)));
	}
}
csharp
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;

public class OrdersApiTests(WebApplicationFactory<Program> factory) : IClassFixture<WebApplicationFactory<Program>>
{
	private HttpClient CreateClient() => factory
		.WithWebHostBuilder(builder => builder.ConfigureTestServices(services =>
			services.AddTransient<ErtisAuthAuthenticationHandler, TestAuthenticationHandler>()))
		.CreateClient();
}

Böylece her istek test çağıranı olarak doğrulanır ve hiçbir yetki kontrol edilmez. Token ve TokenType verilmelidir: GetUtilizer() bunları gerektirir.

Kontrol listesi#

  • Korunacak endpoint'leri olan her controller'da [Authorized] var ya da action'larında [SelfAuthorized] var. Her ERTISAUTH610 uyarısını düzeltin.
  • Korunan her action'da (ya da controller'ında) [RbacResource] ve action'da [RbacAction] var.
  • Yetki kararlarında GetUtilizer() kullanılıyor, asla GetUnverifiedUtilizer() değil.
  • İstemciler 503 AuthenticationServiceUnavailable aldığında kullanıcıyı çıkış yaptırmak yerine yeniden deniyor.
  • Uygulama secret'ı yalnızca sunucuda.
  • BasicTokenCacheTTL ile, yenilenmiş bir secret API'nizde en fazla o kadar saniye çalışmaya devam eder.
  • Ayarlar kodda değil, uygulamanın yapılandırmasında (appsettings.json, ortam değişkenleri…).

Dokümantasyonda bir hata mı buldunuz? Issue açın