Repository navigation
Authentication
ArgusTransfer authenticates requests with a pluggable IArgusAuthenticationHandler run by the built-in ArgusAuthenticationMiddleware. Clients identify themselves with the standard Authorization header; the middleware rejects requests to protected endpoints with 401 Unauthorized or 403 Forbidden problem details.
Requests carry credentials in the Authorization header in the form {scheme} {parameter}:
Authorization: Bearer token123
ArgusRequest has convenience members over Headers["Authorization"]:
| Member | Description |
|---|---|
Authorization |
The raw header value (get/set). Setting null or an empty string removes the header. |
AuthorizationScheme |
The scheme parsed at the first space or tab, e.g. Bearer. null when the header is absent. Schemes are case-insensitive -- compare with StringComparison.OrdinalIgnoreCase. |
AuthorizationParameter |
Everything after the scheme, trimmed, e.g. token123. Parameters containing : or spaces (Basic, Digest) stay intact. null when absent or scheme-only. |
SetAuthorization(scheme, parameter) |
Sets the header to {scheme} {parameter}, or {scheme} when the parameter is empty. The scheme must be a single token. |
Values containing a carriage return or line feed are rejected with ArgumentException, so a value cannot inject additional headers on the wire. The header carries credentials -- never log it.
On the client:
var request = new ArgusRequest { Verb = ArgusVerb.GET, Route = "/items" };
request.SetAuthorization("Bearer", token);
var response = await client.SendAsync(request);public class ApiKeyHandler : IArgusAuthenticationHandler
{
public Task<ArgusAuthenticationResult> AuthenticateAsync(ArgusContext context)
{
var request = context.Request;
if (request.AuthorizationScheme == null)
{
return Task.FromResult(ArgusAuthenticationResult.NoResult());
}
if (!string.Equals(request.AuthorizationScheme, "ApiKey", StringComparison.OrdinalIgnoreCase)
|| !IsValidKey(request.AuthorizationParameter))
{
return Task.FromResult(ArgusAuthenticationResult.Fail("Invalid API key"));
}
var identity = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "reporting-service") }, "ApiKey");
return Task.FromResult(ArgusAuthenticationResult.Success(new ClaimsPrincipal(identity)));
}
}Handlers are registered as singletons and must be thread-safe. The handler receives the full ArgusContext, including EndpointMetadata (for per-endpoint decisions) and RequestAborted.
| Result | Meaning | Response on a protected endpoint |
|---|---|---|
ArgusAuthenticationResult.Success(principal) |
Authenticated; principal becomes context.User
|
request continues |
ArgusAuthenticationResult.NoResult() |
No credentials the handler recognizes | 401 Unauthorized |
ArgusAuthenticationResult.Fail(reason) |
Invalid credentials | 401 Unauthorized |
ArgusAuthenticationResult.Forbidden(reason) |
Valid credentials, but the caller may not access the endpoint | 403 Forbidden |
A handler returning null is treated as NoResult(). The optional reason becomes the problem details detail; without one a generic message is used (ArgusAuthenticationMiddleware.UnauthorizedDetail / ForbiddenDetail). Don't put secrets in reasons. The instance is the request correlation token, and each rejection is logged as a warning with verb, route, correlation token and status -- never the credentials.
services.AddArgusModules();
services.AddArgusAuthentication<ApiKeyHandler>();AddArgusAuthentication<THandler>(configure) registers the handler and the middleware as singletons. When called more than once, the first registered handler is used. The router created by AddArgusModules() adds the middleware as a global middleware directly after the exception handler and before any middleware your modules add, regardless of the order of the calls:
ArgusExceptionHandlerMiddleware (AddArgusExceptionHandler, optional)
ArgusAuthenticationMiddleware (AddArgusAuthentication)
module global middleware (app.UseMiddleware in AddRoutes)
per-endpoint middleware (.WithMiddleware)
endpoint handler
So unauthenticated requests never reach module middleware, and an exception thrown by a handler becomes a 500 from the exception handler.
By default every endpoint requires an authenticated caller (ArgusAuthenticationOptions.RequireAuthentication = true). Override it per endpoint:
app.MapGet("/health", HealthHandler).AllowAnonymous(); // open
app.MapGet("/items", ItemsHandler); // protected (default)Or make endpoints open by default and protect selected ones:
services.AddArgusAuthentication<ApiKeyHandler>(options => options.RequireAuthentication = false);
app.MapDelete("/items/{id:Guid}", DeleteHandler).RequireAuthentication();.RequireAuthentication() and .AllowAnonymous() set the endpoint metadata authorize (ArgusAuthenticationOptions.AuthorizeMetadataKey) to "true" / "false"; you can also call .WithMetadata("authorize", "true") directly. A value other than true/false requires authentication, so a typo never opens an endpoint.
The handler also runs for anonymous endpoints, so context.User is set when a caller sends valid credentials -- useful for endpoints that behave differently for known callers.
app.MapGet("/items", context =>
{
var caller = context.User.Identity.Name; // ClaimsPrincipal from the handler
var isAdmin = context.User.IsInRole("admin");
// ...
return Task.CompletedTask;
});context.User is null for anonymous requests or when no authentication is registered.
- The
Authorizationheader is supplied by the client: its value is only as trustworthy as the credential it carries. Use secrets (tokens, keys) that other local processes cannot guess, and protect them at rest. - Restrict who can connect to the pipe at all with Server Configuration → Pipe Security.
- Routes that do not match return
404/501before the pipeline runs, so authentication does not hide which routes exist.
- Middleware -- pipeline order and the other built-in middleware
- Error Handling -- problem details responses
- Dependency Injection -- registration methods
copyright @ Sam Gerené