![]() |
VOOZH | about |
dotnet add package Udap.Server --version 0.8.5
NuGet\Install-Package Udap.Server -Version 0.8.5
<PackageReference Include="Udap.Server" Version="0.8.5" />
<PackageVersion Include="Udap.Server" Version="0.8.5" />Directory.Packages.props
<PackageReference Include="Udap.Server" />Project file
paket add Udap.Server --version 0.8.5
#r "nuget: Udap.Server, 0.8.5"
#:package Udap.Server@0.8.5
#addin nuget:?package=Udap.Server&version=0.8.5Install as a Cake Addin
#tool nuget:?package=Udap.Server&version=0.8.5Install as a Cake Tool
This package adds UDAP Dynamic Client Registration (DCR) and metadata capabilities to authorization servers built on Duende IdentityServer. It provides the .well-known/udap metadata endpoint and the /connect/register DCR endpoint as extensions to the IdentityServer pipeline.
Note: Duende IdentityServer requires a license for production use above $1M annual revenue.
.well-known/udap)IUdapAuthorizationExtensionValidatorudap_community access-token claim (see Community Claim)For SSRAA or TEFCA community-specific validation rules, add the corresponding packages:
Udap.Ssraa.Server — HL7 v3 PurposeOfUse enforcementUdap.Tefca.Server — TEFCA Exchange Purpose (XP) code validation, SAN matchingUdap.Tefca.Model — TEFCA extension models (tefca-ias, XP constants)The example below shows a typical setup. See also the example project.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddIdentityServer()
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = b => b.UseSqlite(connectionString,
dbOpts => dbOpts.MigrationsAssembly(migrationsAssembly));
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = b => b.UseSqlite(connectionString,
dbOpts => dbOpts.MigrationsAssembly(migrationsAssembly));
})
.AddResourceStore<ResourceStore>()
.AddClientStore<ClientStore>()
.AddTestUsers(TestUsers.Users)
.AddUdapServer(
options =>
{
var udapServerOptions = builder.Configuration.GetOption<ServerSettings>("ServerSettings");
options.DefaultSystemScopes = udapServerOptions.DefaultSystemScopes;
options.DefaultUserScopes = udapServerOptions.DefaultUserScopes;
options.ForceStateParamOnAuthorizationCode = udapServerOptions
.ForceStateParamOnAuthorizationCode;
},
options =>
options.UdapDbContext = b =>
b.UseSqlite(connectionString,
dbOpts =>
dbOpts.MigrationsAssembly(typeof(Program).Assembly.FullName)),
baseUrl: "https://localhost:5002/connect/register"
);
var app = builder.Build();
app.UseStaticFiles();
app.UseRouting();
app.UseUdapServer();
app.UseIdentityServer();
app.UseAuthorization();
app.MapRazorPages().RequireAuthorization();
app.Run();
UDAP supports multiple trust communities, each with its own validation rules for token requests and client registration. The validation pipeline is pluggable via ICommunityTokenValidator and ICommunityRegistrationValidator.
Two profile packages are available:
| Package | Communities | POU codes | Max POU | Registration checks |
|---|---|---|---|---|
Udap.Ssraa.Server |
SSRAA / standard UDAP | 62 HL7 v3 codes | unlimited | none |
Udap.Tefca.Server |
TEFCA | 12 XP codes | 1 | SAN URI XP code validation |
Install the profile packages and map communities to their validation pipelines:
// SSRAA rules for standard UDAP communities
builder.Services.AddUdapSsraaValidation(options =>
{
options.Communities.Add("udap://fhirlabs.net");
});
// TEFCA rules (register model extensions first)
builder.Services.AddUdapTefcaExtensions();
builder.Services.AddUdapTefcaValidation(options =>
{
options.Communities.Add("tefca://test-community");
});
hl7-b2b with purpose_of_use)DefaultUdapAuthorizationExtensionValidator resolves the client's community from the registration storeICommunityTokenValidator implementations until one matches via AppliesToCommunity()CommunityValidationRules specifying required extensions, allowed POU codes, and max POU countValidateAsync() for any domain-specific checksImplement ICommunityTokenValidator for custom rules:
public class MyValidator : ICommunityTokenValidator
{
public bool AppliesToCommunity(string communityName)
=> communityName == "udap://my-community";
public CommunityValidationRules? GetValidationRules(string? grantType)
=> new CommunityValidationRules
{
RequiredExtensions = grantType == "client_credentials"
? new HashSet<string> { "hl7-b2b" } : null,
AllowedPurposeOfUse = new HashSet<string> { /* your codes */ },
MaxPurposeOfUseCount = 1
};
public Task<AuthorizationExtensionValidationResult> ValidateAsync(
UdapAuthorizationExtensionValidationContext context)
=> Task.FromResult(AuthorizationExtensionValidationResult.Success());
}
// Register it
builder.Services.AddSingleton<ICommunityTokenValidator, MyValidator>();
See the and READMEs for detailed documentation on each profile.
When a client registers via UDAP Dynamic Client Registration, the server creates a Duende IdentityServer Client entity with UDAP-specific secrets and properties. Knowing what is stored (and when it is updated) helps with admin tooling and certificate lifecycle management.
| Storage Type | Duende Type | Key / Type Field | Value | Expiration |
|---|---|---|---|---|
| Client Secret | ClientSecret |
UDAP_SAN_URI_ISS_NAME |
The URI Subject Alternative Name (SAN) from the client's X.509 certificate, used as the issuer identity | Certificate NotAfter |
| Client Secret | ClientSecret |
UDAP_COMMUNITY |
The community ID (integer as string) the client registered under | Certificate NotAfter |
| Client Secret | ClientSecret |
X509CertificateBase64 (UDAP_X509_CERTIFICATE) |
Base64 DER-encoded public certificate from the client's x5c chain — stored for admin visibility (expiration monitoring, revocation checking) | Certificate NotAfter |
| Client Property | ClientProperty |
org |
Organization identifier — the query parameter name on the registration endpoint (see Organization / Data Holder scoping) | — |
| Client Property | ClientProperty |
data_holder |
Data holder identifier — the query parameter value on the registration endpoint (see Organization / Data Holder scoping) | — |
| Client Property | ClientProperty |
community |
The community name (URI) the client registered under — written only when ServerSettings.IncludeCommunityClaim is enabled (see Community Claim) |
— |
Other standard Duende Client fields are also populated: ClientId (generated), ClientName, AllowedGrantTypes, AllowedScopes, RedirectUris, LogoUri, RequirePkce, RequireDPoP, and Created.
A client is uniquely identified by the combination of four values: SAN URI (UDAP_SAN_URI_ISS_NAME), community (UDAP_COMMUNITY), organization (org), and data holder (data_holder). When a registration request matches an existing client on all four, the server performs an upsert — updating scopes, grant types, redirect URIs, and the stored certificate rather than creating a new client. When any of the four differ, a new client (new client_id) is created.
The org and data_holder properties are how a deployer controls whether multiple
registrations collapse into one client_id or stay separate. They come from a single
query parameter on the registration endpoint, using an unusual encoding:
The query parameter name becomes
org; its value becomesdata_holder.
https://as.example.com/connect/register?SurescriptsDirectory=BobsClinic
└──── org ────┘ └ data_holder ┘
→ org = "SurescriptsDirectory", data_holder = "BobsClinic"
Where the value comes from. The server reads this query string first from the
registration software statement's aud claim, then falls back to the actual POST URL
(UdapDynamicClientRegistrationValidator.ResolveOrgAndDataHolder). Because a conformant
client sets aud equal to the registration_endpoint it discovered in your metadata,
whatever query string you publish in registration_endpoint is what gets stored as
org/data_holder.
Default. If no query parameter is present, both org and data_holder default to
empty (DefaultOrgMap), so all such clients share the same org/data-holder pair.
Scope. This query parameter is read only at /connect/register. It is ignored at
/connect/token and /connect/authorize, where the client is identified by its issued
client_id and authenticated by the signed private_key_jwt client assertion.
client_id vs. manyBecause org + data_holder are part of the identity 4-tuple,
they are the lever for sharing or splitting registrations across endpoints (e.g. a client
that discovers two FHIR base URLs served by the same authorization server and community):
One client_id, one set of scopes (per org name). If a client should resolve to a
single registration across endpoints that belong to the same organization, publish the
identical org (=data_holder) query string in the registration_endpoint of every
one of those endpoints' .well-known/udap documents (or omit it everywhere, so all
default to empty). The four values then match, the server upserts, and the original
client_id is returned — so the client ends up with one registration and one
AllowedScopes set keyed to that org name, no matter how many endpoints it discovered.
Different scopes → register again under a different key. If an endpoint needs a
distinct scope set (or any distinct registration), publish a different org=data_holder
query string for it. The differing key produces a separate client_id with its own
AllowedScopes, independent of the first.
In short: same org=data_holder key ⇒ one shared client_id and one scope set; a
different key ⇒ a separate client_id you can scope independently. If clients are
registering more times than you expect, diff the registration_endpoint query strings
across your metadata documents — a mismatch (including "present at one endpoint, absent at
another") is the usual cause.
UDAP allows certificate rotation without re-registration. When a client authenticates at the token endpoint with a new certificate (different from the one used at registration), UdapJwtSecretValidator invokes RolloverClientSecrets. This updates:
Expiration on the UDAP_SAN_URI_ISS_NAME and UDAP_COMMUNITY secrets to match the new certificate's NotAfterValue and Expiration on the X509CertificateBase64 secret to reflect the new certificateRollover only occurs if the new certificate is currently valid (NotBefore < now < NotAfter). Existing PKI chain validation against community trust anchors is unchanged; rollover is purely a metadata update.
UDAP clients register under a specific trust community, but by default nothing surfaces that
community to a resource server. Enabling the IncludeCommunityClaim setting on ServerSettings
turns this on, with two effects:
community
property (see the storage table above) for admin visibility.udap_community claim is added to issued access tokens for UDAP
clients, on both the client_credentials and authorization_code flows.The claim value is resolved from the client's stored community id at token time rather than from the registration-time property, so if a community is later renamed the claim automatically reflects the new name without re-registering the client.
builder.Services.AddUdapServer(
options =>
{
var udapServerOptions = builder.Configuration.GetOption<ServerSettings>("ServerSettings");
options.DefaultSystemScopes = udapServerOptions.DefaultSystemScopes;
options.DefaultUserScopes = udapServerOptions.DefaultUserScopes;
options.IncludeCommunityClaim = udapServerOptions.IncludeCommunityClaim; // default false
},
/* ... */);
Or via configuration:
{
"ServerSettings": {
"IncludeCommunityClaim": true
}
}
The setting defaults to false, so existing tokens are unchanged unless it is explicitly enabled.
The emitted claim is unprefixed (udap_community, not client_udap_community).
EF Core migration projects are available for both database providers:
These projects create all UDAP and Duende IdentityServer tables and seed data required to run local tests. See SeedData.cs for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 is compatible. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
Showing the top 3 NuGet packages that depend on Udap.Server:
| Package | Downloads |
|---|---|
|
Udap.UI
Package is a part of the UDAP reference implementation for .NET. |
|
|
Udap.Tefca.Server
TEFCA community-specific validators for UDAP registration and token issuance. |
|
|
Udap.Ssraa.Server
SSRAA community-specific validators for UDAP token issuance with HL7 v3 PurposeOfUse enforcement. |
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.8.5 | 85 | 6/16/2026 |
| 0.8.4 | 161 | 5/30/2026 |
| 0.8.3 | 176 | 5/29/2026 |
| 0.8.2 | 164 | 5/22/2026 |
| 0.8.1 | 161 | 5/20/2026 |
| 0.8.0 | 177 | 5/15/2026 |
| 0.7.13 | 156 | 5/15/2026 |
| 0.7.12 | 371 | 4/1/2026 |
| 0.7.11 | 171 | 3/31/2026 |
| 0.7.10 | 161 | 3/31/2026 |
| 0.7.9 | 176 | 3/31/2026 |
| 0.7.8 | 169 | 3/30/2026 |
| 0.7.7 | 160 | 3/30/2026 |
| 0.7.6 | 164 | 3/30/2026 |
| 0.7.5 | 171 | 3/30/2026 |
| 0.7.4 | 152 | 3/29/2026 |
| 0.7.3 | 150 | 3/29/2026 |
| 0.7.2 | 159 | 3/28/2026 |
| 0.7.1 | 130 | 3/23/2026 |
| 0.7.0 | 127 | 3/22/2026 |