Authio.Client
0.4.0
See the version list below for details.
dotnet add package Authio.Client --version 0.4.0
NuGet\Install-Package Authio.Client -Version 0.4.0
<PackageReference Include="Authio.Client" Version="0.4.0" />
<PackageVersion Include="Authio.Client" Version="0.4.0" />
<PackageReference Include="Authio.Client" />
paket add Authio.Client --version 0.4.0
#r "nuget: Authio.Client, 0.4.0"
#:package Authio.Client@0.4.0
#addin nuget:?package=Authio.Client&version=0.4.0
#tool nuget:?package=Authio.Client&version=0.4.0
Authio.Client
The Authio client for .NET 8 and .NET 10, with no ASP.NET Core dependency. It covers:
- Discovery: the realm's OpenID Connect document, cached, single-flight, issuer-checked.
- Every token grant Authio supports: client credentials, authorization code with PKCE, refresh (with rotation), token exchange (delegation / on-behalf-of), connected-accounts exchange, device flow, and the discouraged password grant.
- Introspection, revocation and userinfo.
- Machine-to-machine calls: a client-credentials token cache and an
HttpClienthandler that adds the token, renews it before expiry and retries once on401. - Authio roles: a parser for the
*_resource_accessclaims with Authio's exact grant rule. - Webhooks: signature verification (
X-Authio-Signature) and typed event bodies. - Admin API: users, sessions, roles and groups, authenticated with the account secret.
dotnet add package Authio.Client
Configuration
{
"Authio": {
"Authority": "https://auth.example.com",
"Realm": "acme",
"ClientId": "orders-service",
"ClientSecret": "<secret>",
"ClientAuthentication": "Basic",
"DiscoveryCacheDuration": "01:00:00",
"HttpTimeout": "00:00:30",
"TokenRefreshSkew": "00:01:00",
"AdminApiKey": "<account secret, only for the Admin API>",
"AdminAuthentication": "ApiKey"
}
}
The issuer is {Authority}/realms/{Realm}; set Issuer instead to give it directly (it wins). Every endpoint is then
read from {issuer}/.well-known/openid-configuration. ClientAuthentication is Basic (default,
client_secret_basic, each part percent-encoded as RFC 6749 §2.3.1 asks), Post (client_secret_post) or None
(public client). Invalid settings fail with a message naming the key, e.g. Authio:ClientId is required.
builder.Services.AddAuthioClient(builder.Configuration.GetSection("Authio"));
// or
builder.Services.AddAuthioClient(options =>
{
options.Authority = "https://auth.example.com";
options.Realm = "acme";
options.ClientId = "orders-service";
options.ClientSecret = builder.Configuration["Authio:ClientSecret"];
});
Every protocol call goes through the named HttpClient AuthioDefaults.HttpClientName ("Authio"), and Admin API
calls through AuthioDefaults.AdminHttpClientName, so you can add handlers (logging, resilience, proxies):
builder.Services.AddHttpClient(AuthioDefaults.HttpClientName).AddStandardResilienceHandler();
Calling another API as this service (client credentials)
builder.Services
.AddHttpClient<BillingApi>(client => client.BaseAddress = new Uri("https://billing.example.com"))
.AddAuthioClientCredentials(options =>
{
options.Scopes = ["invoice:read"];
options.Resource = "https://billing.example.com"; // becomes the token's aud (RFC 8707)
});
The token is cached per scopes and resource, renewed TokenRefreshSkew before it expires, shared by concurrent
callers, and on a 401 from the downstream API dropped and fetched again once. To get a token yourself:
var token = await tokenCache.GetTokenAsync(["invoice:read"], "https://billing.example.com");
Token grants
public sealed class Example(IAuthioTokenClient tokens)
{
public async Task Run(string code, string verifier, string userAccessToken)
{
var signIn = await tokens.ExchangeAuthorizationCodeAsync(code, "https://app.example.com/callback", verifier);
var refreshed = await tokens.RefreshTokenAsync(signIn.RefreshToken!); // keep the new refresh token
var onBehalf = await tokens.ExchangeTokenAsync(userAccessToken, ["orders:read"], "https://orders.example.com");
var result = await tokens.ExchangeForConnectionAsync(userAccessToken, "github");
var message = result switch
{
AuthioConnectionGranted granted => $"GitHub token for {granted.ExternalAccount?.Login}",
AuthioConnectionRequired required => $"Connect GitHub first: {required.ConnectUri}",
AuthioConnectionSelectionRequired selection => $"Pick one of {selection.Connections.Count} accounts",
AuthioConnectionRefused refused => $"Refused: {refused.Error} ({refused.Code})",
_ => "Unexpected"
};
}
}
Refusals throw AuthioProtocolException with Error (invalid_grant, ...), ErrorDescription, Authio's own
ErrorCode and the HTTP StatusCode; the connected-accounts exchange returns its refusals as cases instead.
Device flow
var device = await tokens.RequestDeviceAuthorizationAsync(["openid", "offline_access"]);
Console.WriteLine($"Open {device.VerificationUri} and enter {device.UserCode}");
var tokensForDevice = await tokens.PollDeviceTokenAsync(device); // honours interval, slow_down (+5 s) and expiry
Introspection, revocation, userinfo
var result = await introspection.IntrospectAsync(accessToken);
if (!result.Active) { /* revoked, expired, session ended, subject suspended, delegation withdrawn */ }
await revocation.RevokeAsync(refreshToken, AuthioTokenTypes.RefreshToken);
var me = await userInfo.GetAsync(accessToken); // needs the openid scope
var orgId = me.AdditionalClaims?["org_id"].GetString();
Roles and permissions
Authio puts roles in JSON-valued claims (user_resource_access, realm_resource_access, client_resource_access,
plan_resource_access, consumer_resource_access, secret_resource_access, system_resource_access) shaped
{"role": {"resource": ["method"]}}, only when the token was granted the roles scope.
var access = AuthioResourceAccessParser.Parse(principal.Claims.Select(claim => (claim.Type, claim.Value)));
access.Roles; // ["billing-admin", "viewer"]
access.Grants("invoice", "write"); // Authio's rule: any role, any source, case-insensitive
access.Permissions["invoice"]; // {"read", "write"}
access.FromSource(AuthioResourceAccessSource.Consumer);
A malformed claim contributes nothing, exactly as in Authio's own check.
Webhooks
app.MapPost("/webhooks/authio", async (HttpRequest request) =>
{
using var reader = new StreamReader(request.Body);
var body = await reader.ReadToEndAsync();
var verification = AuthioWebhookVerifier.Verify(
request.Headers[AuthioWebhookHeaders.Signature], body, webhookSecret); // whsec_... with the prefix
if (!verification.IsValid)
return Results.Unauthorized();
var webhookEvent = AuthioWebhookEventParser.Parse(body);
switch (webhookEvent.Type)
{
case AuthioWebhookEventTypes.UserCreated:
var user = webhookEvent.AsUser()!;
break;
case AuthioWebhookEventTypes.LoginFailed:
var login = webhookEvent.AsLogin()!;
break;
}
return Results.Ok();
});
Verification is constant-time, accepts any matching v1 among several, and refuses timestamps more than five minutes
from now by default (pass tolerance to change it). Deduplicate on webhookEvent.Id.
Admin API
var page = await admin.ListUsersAsync("acme", new AuthioAdminUserQuery { SearchTerm = "alice", PageSize = 50 });
var user = await admin.CreateUserAsync("acme", new AuthioAdminCreateUserRequest
{
UserName = "bob", Password = "S3cure!pass", Email = "bob@example.com", Roles = ["viewer"]
});
await admin.UpdateUserAsync("acme", user.ToUpdateRequest() with { FirstName = "Robert" });
await admin.SetUserStatusAsync("acme", user.Id, AuthioUserStatus.Inactive);
await admin.AssignUserRolesAsync("acme", user.Id, ["editor"]);
var sessions = await admin.ListSessionsAsync("acme");
await admin.RevokeSessionsAsync("acme", sessions.Select(session => session.Id));
await admin.AddGroupMemberAsync("acme", "web-app", groupId, user.Id);
The Admin API accepts the account secret only: as x-api-key (default) or, with
AdminAuthentication = PlatformToken, exchanged for a platform token at the authio realm. A realm's own
client-credentials token is refused. What a call may do depends on the secret's roles. Failures throw
AuthioAdminApiException with the HTTP status, the API's top-level ErrorCode, RequestId and Notifications.
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.Caching.Memory (>= 10.0.10)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Http (>= 10.0.10)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.10)
-
net8.0
- Microsoft.Extensions.Caching.Memory (>= 8.0.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Http (>= 8.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 8.0.0)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Authio.Client:
| Package | Downloads |
|---|---|
|
Authio.AspNetCore
Protect an ASP.NET Core API with Authio: JWT bearer validation from the realm's discovery document, optional introspection (active check or opaque tokens), flattened Authio roles and permissions, scope / role / permission / organization / step-up / client policies with attributes and minimal API helpers, a typed principal, a signed webhook receiver, and token forwarding and on-behalf-of for downstream calls. |
|
|
Authio.AspNetCore.Bff
Backend-for-Frontend for Authio: lets a browser SPA (React, Angular, Vue...) sign in with Authio without ever holding a token. Cookie + OpenID Connect (code flow, PKCE, RFC 9207 iss check), server-side sessions, refresh-token rotation with single-flight refresh, session liveness by introspection, login/logout/user endpoints, CSRF-protected local APIs and a YARP-based remote API proxy that attaches the user's token. |
GitHub repositories
This package is not used by any popular GitHub repositories.