Paysky.Identity.Keycloak
1.0.0
dotnet add package Paysky.Identity.Keycloak --version 1.0.0
NuGet\Install-Package Paysky.Identity.Keycloak -Version 1.0.0
<PackageReference Include="Paysky.Identity.Keycloak" Version="1.0.0" />
<PackageVersion Include="Paysky.Identity.Keycloak" Version="1.0.0" />
<PackageReference Include="Paysky.Identity.Keycloak" />
paket add Paysky.Identity.Keycloak --version 1.0.0
#r "nuget: Paysky.Identity.Keycloak, 1.0.0"
#:package Paysky.Identity.Keycloak@1.0.0
#addin nuget:?package=Paysky.Identity.Keycloak&version=1.0.0
#tool nuget:?package=Paysky.Identity.Keycloak&version=1.0.0
Paysky.Identity.Keycloak
One internal library for Keycloak identity across the PaySky .NET estate. It replaces the two hand-rolled integrations (QRSwitch, APM) and is the standard onboarding path for new services (Super-POS first).
Build status (local, verified): 6 packages × net8.0 + net10.0 = 12 assemblies, 0 warnings, 0 errors. Unit tests: 27 passing.
Packages
Reference the meta-package for everything, or a single package for a narrower surface:
| Package | Use it for | ASP.NET dependency |
|---|---|---|
| Paysky.Identity.Keycloak | Meta-package — pulls the four below | (transitive) |
| Paysky.Identity.Keycloak.Authentication | Validate Keycloak JWTs (resource server), single- or multi-realm | Yes |
| Paysky.Identity.Keycloak.Authorization | [Permission("…")] policies mapped to Keycloak roles |
Yes |
| Paysky.Identity.Keycloak.Admin | Manage users/roles/realms via the Admin REST API | No (usable from workers) |
| Paysky.Identity.Keycloak.Login | Broker end-user login/refresh/logout/userinfo | No |
| Paysky.Identity.Keycloak.Abstractions | Contracts, options, results (referenced by all) | No |
All packages multi-target net8.0;net10.0.
Quick start
1. Resource server (validate tokens)
builder.Services.AddPayskyKeycloakAuthentication(builder.Configuration);
// ...
app.UseAuthentication();
app.UseAuthorization();
- One realm in config → single JwtBearer scheme.
- Two or more realms → an issuer-based policy scheme routes each token to the right realm's validator automatically (replaces APM's hand-copied
AuthExtensions). - Realm roles (
realm_access.roles) are mapped toClaimTypes.Roleby default; client roles (resource_access.<clientId>.roles) are opt-in per realm. - The tenant id is normalized from any of
tenantId/tenant_id/tid/TenantIdinto oneX-Tenantclaim.
2. Permission authorization
builder.Services.AddPayskyPermissionAuthorization(o =>
o.ForbiddenMessageResolver = ctx => Localize(ctx, "Forbidden")); // optional
[Permission("Users.Create")]
public IActionResult CreateUser(...) { ... }
3. Admin client (users / roles / provisioning)
builder.Services.AddPayskyKeycloakAdmin(builder.Configuration);
public sealed class UserService(IKeycloakUserAdmin users)
{
public Task<KeycloakResult<string>> Create(CreateKeycloakUserRequest req) => users.CreateUserAsync(req);
}
4. Login broker (direct user login)
builder.Services.AddPayskyKeycloakLogin(builder.Configuration);
public sealed class AuthController(IKeycloakLoginBroker broker)
{
public async Task<IActionResult> Login(LoginDto dto)
{
var result = await broker.LoginAsync(dto.Username, dto.Password);
return result.Success ? Ok(result.Data) : Unauthorized(result.ErrorMessage);
}
}
App-specific rules (e.g. QRSwitch's tenant-active check, UPDATE_PASSWORD handling) stay in the app: call the broker, then apply your policy on the returned token. They are deliberately not baked into the shared library.
Configuration
Full canonical schema: templates/appsettings.Keycloak.sample.json. Minimal resource-server example:
"Keycloak": {
"BaseUrl": "https://idp.paysky.internal",
"RequireHttpsMetadata": true,
"Realms": [
{ "Name": "qrswitch", "ClientId": "qrswitch-api", "MapRealmRoles": true }
]
}
Security defaults (all overridable, but safe by default)
RequireHttpsMetadata= true.- Audience validation = on, expecting a single audience (
ClientId). The generic Keycloakaccountaudience is not accepted unless you list it inAdditionalAudiences— this closes the weakness in the legacy QRSwitch setup. - Issuer + lifetime validation = on, explicit.
- Admin token grant =
client_credentials(service account), not ROPC. - Every
Add…method fails fast with a clear exception when required config or secrets are missing.
Realm-naming convention
A realm name always begins with the lowercase project slug; append an audience suffix only when a project needs more than one realm.
- Single-realm:
qrswitch,super-pos - Multi-realm:
apm-admin,apm-merchant,apm-tenant
Lowercase kebab-case only — realm names live in issuer URLs and the multi-realm selector matches on them.
Standard client template
templates/keycloak-service-client.template.json — a confidential, service-account-enabled client with direct-access-grants off and a tenant-id protocol mapper. Replace the REPLACE_* placeholders and import via the Keycloak Admin console or REST API.
Build & test
dotnet build Paysky.Identity.Keycloak.slnx
dotnet test tests/Paysky.Identity.Keycloak.UnitTests
Unit tests cover the behavioural core: role mapping (realm + client + malformed + de-dup), tenant normalization, audience/authority resolution, result factories, Keycloak error parsing, and admin-token caching + grant selection.
Remaining phase (not yet done)
Integration tests against a live Keycloak (Testcontainers) are the one planned item still outstanding — they need Docker and a running Keycloak, so they are a separate, environment-dependent phase from this compile-and-unit-verified core. Scope: provision a realm/client from the template, then exercise login → validate → admin CRUD → refresh → logout end-to-end.
Adoption order
- Super-POS — greenfield (no Keycloak today); proves the library on a clean target.
- APM — replace per-service validation, collapse the 3 duplicated
AuthExtensions, swap the hand-built admin client. Renames realms toapm-admin/apm-merchant/apm-tenant. - QRSwitch — collapse the two internal
KeycloakBaseServiceimplementations ontoIKeycloakTokenProvider; tighten the audience check with a staged token re-issue so in-flightaud=accounttokens are not locked out mid-deploy.
See docs/keycloak-library-build-plan.md for the full design rationale and drift analysis.
| 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
- Paysky.Identity.Keycloak.Admin (>= 1.0.0)
- Paysky.Identity.Keycloak.Authentication (>= 1.0.0)
- Paysky.Identity.Keycloak.Authorization (>= 1.0.0)
- Paysky.Identity.Keycloak.Login (>= 1.0.0)
-
net8.0
- Paysky.Identity.Keycloak.Admin (>= 1.0.0)
- Paysky.Identity.Keycloak.Authentication (>= 1.0.0)
- Paysky.Identity.Keycloak.Authorization (>= 1.0.0)
- Paysky.Identity.Keycloak.Login (>= 1.0.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 |
|---|---|---|
| 1.0.0 | 126 | 7/20/2026 |