AIntelA.Auth
1.7.0
dotnet add package AIntelA.Auth --version 1.7.0
NuGet\Install-Package AIntelA.Auth -Version 1.7.0
<PackageReference Include="AIntelA.Auth" Version="1.7.0" />
<PackageVersion Include="AIntelA.Auth" Version="1.7.0" />
<PackageReference Include="AIntelA.Auth" />
paket add AIntelA.Auth --version 1.7.0
#r "nuget: AIntelA.Auth, 1.7.0"
#:package AIntelA.Auth@1.7.0
#addin nuget:?package=AIntelA.Auth&version=1.7.0
#tool nuget:?package=AIntelA.Auth&version=1.7.0
AIA.Auth
Client SDK for the AIA identity provider (idp.tripapi.com.br). Turns any Envision .NET service into an
AIA resource server in one line, with optional service-side token refresh and M2M (client-credentials) support.
Self-contained — no dependency on AIA's server-side projects.
Install
From GitHub Packages (cironolaenvision). Add a nuget.config next to your solution:
<configuration>
<packageSources>
<add key="github-aia" value="https://nuget.pkg.github.com/cironolaenvision/index.json" />
</packageSources>
<packageSourceCredentials>
<github-aia>
<add key="Username" value="%GITHUB_ACTOR%" />
<add key="ClearTextPassword" value="%GITHUB_TOKEN%" />
</github-aia>
</packageSourceCredentials>
</configuration>
GITHUB_TOKEN is a PAT with read:packages (GitHub Packages requires auth even to restore). Then:
dotnet add package AIA.Auth
Use
// Program.cs — resource server
builder.Services.AddAiaAuthentication(builder.Configuration); // JWKS validation from the "Aia" config section
// builder.Services.AddAiaServiceToken(); // optional: M2M client_credentials (needs Aia:ClientId/Secret)
app.UseAuthentication();
app.UseAiaTokenRefresh(); // optional: service-side silent refresh -> X-Refreshed-Access-Token (expose it via CORS)
app.UseAuthorization();
appsettings.json:
{
"Aia": {
"Authority": "https://idp.tripapi.com.br",
"Issuer": "aia-idp",
"Audience": "aia-clients"
}
}
Read identity off the validated principal with the typed helpers:
var userId = User.UserId(); // sub
var roles = User.Roles(); // roles claim
if (User.IsService()) { ... } // token_use == "service"
Typed IdP client — IAuthApi
IAuthApi is a Refit interface covering the IdP's full HTTP surface
(auth/session, the client_credentials token endpoint, anonymous guest tokens, messaging channels,
onboarding/provisioning, admin tenants/users/apps, directory imports/sync, SCIM 2.0, discovery) with
self-contained request/response DTOs — the one blessed call-shape, the same way TripApi exposes
ICompositeApi. Register it with the shared service-client helper:
builder.Services.AddAiaServiceClient<IAuthApi>("https://idp.tripapi.com.br");
Pass the matching bearer per call via the authorization parameter (full "Bearer <token>") — a user token,
the master admin token, a service token, or a per-tenant SCIM token. Public endpoints (discovery, login,
/auth/anonymous, the token endpoint, HRD) take no auth. Example — mint a guest token and call as that user:
var auth = RestService.For<IAuthApi>("https://idp.tripapi.com.br");
var guest = await auth.AnonymousAsync(new AnonymousTokenRequest("aia_<clientId>"));
var me = await auth.MeAsync($"Bearer {guest.AccessToken}");
Messaging channels — reaching a user over WhatsApp
A messaging adapter has a phone number and needs a user. Two calls, both needing a service token with the
channel:link role and nothing else:
try
{
var who = await auth.ResolveChannelAsync("whatsapp", from, $"Bearer {serviceToken}");
var user = await auth.GetChannelTokenAsync(new ChannelTokenRequestForm
{
ClientId = clientId, ClientSecret = clientSecret, Channel = "whatsapp", Address = from,
});
// …call the rest of the platform as that person, with user.AccessToken
}
catch (ApiException e) when (e.StatusCode == HttpStatusCode.NotFound)
{
// Not linked yet — ask for a corporate email, then StartChannelLinkAsync with the parked message.
}
404 is the first-time-user path, not an error. And note what the grant cannot do: there is no user
parameter anywhere in it. The adapter presents an address the messaging platform authenticated and the IdP
resolves the binding, so a leaked adapter secret reaches exactly the addresses already bound rather than
becoming arbitrary impersonation. That asymmetry is why a channel credential can exist in a service that
otherwise has no impersonation path at all — refusals are uniformly invalid_grant for the same reason.
The other direction — where do I reach these people?
Notifications ask the opposite question: given recipients, which of them are reachable on a channel. That is a
different role, channel:lookup, precisely because it is the shape the paragraph above rules out — user ids
in, phone numbers out. The messaging adapter is not granted it, so a leaked adapter secret still cannot
enumerate numbers.
var reachable = await auth.LookupChannelIdentitiesAsync(new ChannelLookupRequest
{
Channel = "whatsapp", TenantId = tenantId, UserIds = recipients, // the WHOLE notification's recipients
}, $"Bearer {serviceToken}");
foreach (var r in recipients)
{
var binding = reachable.Bindings.FirstOrDefault(b => b.UserId == r);
if (binding is null) continue; // not reachable here — deliver on the other channels, say nothing
Send(binding.Address, …);
}
Three things to hold on to:
- Ask once, for everyone. This sits on the send path, and a notification routinely has several recipients — four passengers on a ticket, three approvers on a step. Loop over the response, not over the calls.
- A short list is the normal answer, and there is no
404. A user with no usable binding is absent rather than present-with-a-null-address, and that is deliberately indistinguishable from a user id that does not exist: the linking flow was built not to be a corporate-directory oracle and this must not become one. - "Usable" excludes a binding past its re-verification horizon — the same rule
ResolveChannelAsyncapplies inbound. Brazilian carriers recycle mobile numbers, and a number the IdP has stopped trusting to speak for someone must not be sent their itinerary either.
What's inside
IAuthApi(+ its DTOs inClients/) — the typed Refit client for every IdP endpoint.AddAiaAuthentication(config)— JwtBearer + JWKS validation (issuer/audience/metadata fromAia:*), WS?access_token=hook, IdentityModel pinned to 8.18.0.UseAiaTokenRefresh()— refreshes a near-expiry user token via AIA and returns it inX-Refreshed-Access-Token.AiaServiceTokenClient— caches/renews this service's own token viaclient_credentials.AiaClaimsExtensions/AiaClaimNames— typed claim access + the wire-contract constants.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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.AspNetCore.Authentication.JwtBearer (>= 10.0.8)
- Microsoft.IdentityModel.JsonWebTokens (>= 8.18.0)
- Microsoft.IdentityModel.Protocols.OpenIdConnect (>= 8.18.0)
- Refit (>= 8.0.0)
- System.IdentityModel.Tokens.Jwt (>= 8.18.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|