Authio.Client 0.4.0

There is a newer version of this package available.
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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Authio.Client" Version="0.4.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Authio.Client" Version="0.4.0" />
                    
Directory.Packages.props
<PackageReference Include="Authio.Client" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Authio.Client --version 0.4.0
                    
#r "nuget: Authio.Client, 0.4.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Authio.Client@0.4.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Authio.Client&version=0.4.0
                    
Install as a Cake Addin
#tool nuget:?package=Authio.Client&version=0.4.0
                    
Install as a Cake Tool

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 HttpClient handler that adds the token, renews it before expiry and retries once on 401.
  • Authio roles: a parser for the *_resource_access claims 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.

Version Downloads Last Updated
0.6.0 52 10/9/2026
0.5.1 66 10/8/2026
0.5.0 71 10/8/2026
0.4.0 102 10/6/2026
0.3.0 125 10/5/2026
0.2.0 123 10/4/2026
0.1.0 124 10/4/2026