Apricot Framework

Usage

Obtaining tokens without ASP.NET Core, the two calls on the authenticator, how a token is cached, and the failure reasons.

The core package obtains tokens on its own. It reads no configuration, so everything it needs is passed in — which is what lets it run in a console or worker host.

using var httpClient = new HttpClient();

var authenticator = new ClientCredentialsAuthenticator(httpClient, cache);

var context = await authenticator.AuthenticateAsync(new ClientAuthenticationParameters
{
    Authority = "https://idp.example.com",
    ClientId = "orders-service",
    ClientSecret = secret,
    Scopes = ["billing.read"],
});

request.Headers.Authorization = new AuthenticationHeaderValue(context.TokenType, context.Token);

Build the header from TokenType rather than hard-coding Bearer: a provider is entitled to answer with a different case, and some servers compare the scheme case-sensitively.

The two calls

AuthenticateAsync returns a token. DoAuthenticatedAsync runs an operation with one, which is the better shape when the token exists only to make a call:

var order = await authenticator.DoAuthenticatedAsync(
    (token, ct) => this.billing.GetAsync(id, token, ct),
    cancellationToken: cancellationToken);

Anything the operation itself throws is left alone. Only the failure to obtain a token becomes a ClientAuthenticationException.

Parameters

Every member of ClientAuthenticationParameters is optional. An authenticator built over configuration fills what is left unset, so a caller states only what differs — usually the scopes or resources one downstream call needs.

MemberNotes
AuthorityThe provider's base address, not its token endpoint.
ClientId, ClientSecretPresented as HTTP Basic by default; see ClientCredentialStyle.
ScopesSent as one space-delimited scope parameter.
ResourcesSent as one resource parameter each, per RFC 8707.

An empty list means "none", where an absent one means "use the configured default". The distinction matters when a caller wants a token with no scopes at all.

Caching

IClientAuthenticationCache holds tokens and discovered endpoints. Two entries share a key exactly when they deserve the same token, so build keys with ClientAuthenticationKeys rather than inventing a scheme — a key two different parameter sets can share serves one caller's token to another.

The secret is deliberately not part of the key. Including it would discard every valid token the moment a secret rotated, and would write a credential into whatever backs the cache.

Failure reasons

ClientAuthenticationException.Reason says what a caller can do about it.

ReasonMeaningRetryable
UnavailableUnreachable, timed out, or the provider reported a fault.Yes
InvalidCredentialsThe client identifier or secret was rejected.No
InvalidConfigurationThe authority, or the metadata it published, cannot be used.No
InvalidScopeThe provider refused a requested scope.No
UnknownThe provider answered in a way this library could not read.No

Cancellation is not one of them: a cancelled request throws OperationCanceledException, because abandoning a call is not the same as failing at it.

Warning

No exception message is safe to return to a caller. They name the authority and the client, and a provider's own error description routinely quotes the request that was refused — which for a token request contains the secret. The error definitions package reports a coarse reason instead.

On this page