Elvia.Elvid.TokenClient
2.3.0
dotnet add package Elvia.Elvid.TokenClient --version 2.3.0
NuGet\Install-Package Elvia.Elvid.TokenClient -Version 2.3.0
<PackageReference Include="Elvia.Elvid.TokenClient" Version="2.3.0" />
<PackageVersion Include="Elvia.Elvid.TokenClient" Version="2.3.0" />
<PackageReference Include="Elvia.Elvid.TokenClient" />
paket add Elvia.Elvid.TokenClient --version 2.3.0
#r "nuget: Elvia.Elvid.TokenClient, 2.3.0"
#:package Elvia.Elvid.TokenClient@2.3.0
#addin nuget:?package=Elvia.Elvid.TokenClient&version=2.3.0
#tool nuget:?package=Elvia.Elvid.TokenClient&version=2.3.0
Elvia.Elvid.TokenClient
OAuth2 Client Credentials bibliotek for Elvid med automatisk token-håndtering og caching.
Erstatter IdentityModel avhengigheter og duplikat ElvidAccessTokenService kode.
Kom i gang
1. Installer
dotnet add package Elvia.Elvid.TokenClient
2. Konfigurer
// Forutsetter eksisterende Vault-konfigurasjon (AddHashiVaultSecrets)
builder.Services.AddElvidTokenProvider(options =>
{
options.TokenEndpoint = builder.Configuration.EnsureHasValue("system:kv:elvid:tokenendpoint");
options.ClientId = builder.Configuration.EnsureHasValue("system:kv:elvid:clientid");
options.ClientSecret = builder.Configuration.EnsureHasValue("system:kv:elvid:clientsecret");
});
3. Bruk
HttpClient (anbefalt):
// Default provider
builder.Services.AddHttpClient("ExternalApi", client =>
{
client.BaseAddress = new Uri("https://api.elvia.no/");
})
.AddElvidAuthHandler(); // Automatisk Bearer token fra default provider
// Named provider (for multiple clients)
builder.Services.AddHttpClient("KumaApi", client =>
{
client.BaseAddress = new Uri("https://kuma.elvia.io/");
})
.AddElvidAuthHandler("kuma"); // Bruker navngitt "kuma" provider
// I service:
var client = _httpClientFactory.CreateClient("ExternalApi");
var response = await client.GetAsync("endpoint"); // Token legges til automatisk
Direkte (Client Credentials):
public class MyService
{
private readonly IElvidTokenProvider _tokenProvider;
public async Task<string> GetTokenAsync()
{
return await _tokenProvider.GetAccessTokenAsync([]);
}
}
Password Grant (for testing/smoketests):
public class MySmokeTestService
{
private readonly IElvidTokenProvider _tokenProvider;
public async Task<string> GetUserTokenAsync()
{
return await _tokenProvider.GetAccessTokenAsync("testuser", "testpass", []);
}
}
Standalone bruk (uten Dependency Injection):
// Perfect for smoketests og enkle skript
var tokenProvider = ElvidTokenClientFactory.Create(
"https://elvid.test-elvia.io/connect/token",
"your-client-id",
"your-client-secret");
// Client credentials
var token = await tokenProvider.GetAccessTokenAsync([]);
// Password grant
var userToken = await tokenProvider.GetAccessTokenAsync("testuser", "testpass", []);
Multiple API clients (anbefalt for større systemer):
// Registrer flere navngitte clients
builder.Services.AddElvidTokenProvider("convey", options =>
{
options.TokenEndpoint = builder.Configuration.EnsureHasValue("elvid:kv:elvid:elvid-convey:tokenendpoint");
options.ClientId = builder.Configuration.EnsureHasValue("elvid:kv:elvid:elvid-convey:clientid");
options.ClientSecret = builder.Configuration.EnsureHasValue("elvid:kv:elvid:elvid-convey:clientsecret");
});
builder.Services.AddElvidTokenProvider("kuma", options =>
{
options.TokenEndpoint = builder.Configuration.EnsureHasValue("elvid:kv:elvid:elvid-kuma:tokenendpoint");
options.ClientId = builder.Configuration.EnsureHasValue("elvid:kv:elvid:elvid-kuma:clientid");
options.ClientSecret = builder.Configuration.EnsureHasValue("elvid:kv:elvid:elvid-kuma:clientsecret");
});
// I service - bruk factory til å få riktig client
public class MyService
{
private readonly IElvidTokenProviderFactory _tokenProviderFactory;
public async Task CallConveyAsync()
{
var conveyProvider = _tokenProviderFactory.GetProvider("convey");
var token = await conveyProvider.GetAccessTokenAsync([]);
// Use token for Convey API calls
}
public async Task CallKumaAsync()
{
var kumaProvider = _tokenProviderFactory.GetProvider("kuma");
var token = await kumaProvider.GetAccessTokenAsync([]);
// Use token for Kuma API calls
}
}
JWT Bearer Authentication
For applikasjoner som skal validere ElvID tokens (ikke bare hente dem), tilbyr TokenClient en standardisert JWT authentication setup:
Enkel oppsett:
using Elvia.Elvid.TokenClient.DependencyInjection;
// I Program.cs
var authority = builder.Configuration.EnsureHasValue("elvid:kv:shared:elvidauthority");
builder.Services.AddElvidJwtAuthentication(authority);
// Det er alt! Standardoppsett for ElvID tokens er konfigurert:
// ✅ MapInboundClaims = false (bevarer originale claim types)
// ✅ ValidateAudience = false (ElvID tokens krever ikke audience validering)
// ✅ ValidateIssuer = false (stoler på authority vi konfigurerte)
Før (manuelt oppsett):
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = authority;
options.MapInboundClaims = false;
options.TokenValidationParameters.ValidateAudience = false;
options.TokenValidationParameters.ValidateIssuer = false;
});
Etter (TokenClient helper):
builder.Services.AddElvidJwtAuthentication(authority);
Multiple authentication schemes:
// Legg til flere schemes ved behov
builder.Services
.AddElvidJwtAuthentication("https://elvid.elvia.io") // Default scheme
.AddElvidJwtBearer("https://other-authority.io", "OtherScheme");
Bruk i controllers:
[Authorize] // Krever gyldig ElvID token
public class MyController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
var userId = User.FindFirst("sub")?.Value;
var email = User.FindFirst("email")?.Value;
return Ok(new { userId, email });
}
}
Microsoft Graph API Integration
TokenClient inkluderer innebygd støtte for Microsoft Graph API med automatisk token-håndtering og caching. Perfekt for AD-gruppeoppslag, brukeroppslag, og authorization handlers.
Erstatter: ElvID sin GraphApiService og manuelle Graph API kode i andre systemer.
Konfigurer
using Elvia.Elvid.TokenClient.DependencyInjection;
// I Program.cs
builder.Services.AddElvidGraphClient(options =>
{
options.TenantId = builder.Configuration.EnsureHasValue("elviaad:tenantid");
options.ClientId = builder.Configuration.EnsureHasValue("elviaad:clientid");
options.ClientSecret = builder.Configuration.EnsureHasValue("elviaad:clientsecret");
});
Brukeroppslag
public class UserService
{
private readonly IElvidGraphClient _graphClient;
public async Task<GraphUser?> GetUserInfoAsync(string userPrincipalName)
{
var user = await _graphClient.GetUserAsync(userPrincipalName);
if (user != null)
{
// ElvID Graph API returnerer kun essensielle felt
Console.WriteLine($"ID: {user.Id}");
Console.WriteLine($"Name: {user.DisplayName}");
Console.WriteLine($"Email: {user.Mail}");
Console.WriteLine($"UPN: {user.UserPrincipalName}");
// MERK: Sensitive felt er filtrert bort (telefon, jobbtittel, kontor)
}
return user;
}
}
AD Gruppeoppslag
public class GroupService
{
private readonly IElvidGraphClient _graphClient;
// Hent alle gruppe-IDer (rask, cached i 5 min)
public async Task<string[]> GetUserGroupsAsync(string userPrincipalName)
{
return await _graphClient.GetUserGroupIdsAsync(userPrincipalName);
}
// Hent alle grupper med detaljer (navn, beskrivelse, etc.)
public async Task<GraphGroup[]> GetUserGroupDetailsAsync(string userPrincipalName)
{
return await _graphClient.GetUserGroupsAsync(userPrincipalName);
}
// Sjekk om bruker er medlem av spesifikk gruppe
public async Task<bool> IsAdminAsync(string userPrincipalName)
{
return await _graphClient.IsUserMemberOfGroupAsync(
userPrincipalName,
"admin-group-id");
}
// Sjekk om bruker er medlem av noen av gruppene
public async Task<bool> HasAccessAsync(string userPrincipalName)
{
var allowedGroups = new[] { "group-1-id", "group-2-id", "group-3-id" };
return await _graphClient.IsUserMemberOfAnyGroupAsync(
userPrincipalName,
allowedGroups);
}
}
Authorization Handler (ASP.NET Core)
public class AdGroupRequirement : IAuthorizationRequirement
{
public string[] AllowedGroupIds { get; }
public AdGroupRequirement(params string[] allowedGroupIds)
{
AllowedGroupIds = allowedGroupIds;
}
}
public class AdGroupHandler : AuthorizationHandler<AdGroupRequirement>
{
private readonly IElvidGraphClient _graphClient;
public AdGroupHandler(IElvidGraphClient graphClient)
{
_graphClient = graphClient;
}
protected override async Task HandleRequirementAsync(
AuthorizationHandlerContext context,
AdGroupRequirement requirement)
{
var userPrincipalName = context.User.FindFirst("upn")?.Value
?? context.User.FindFirst("preferred_username")?.Value;
if (userPrincipalName == null) return;
// IsUserMemberOfAnyGroupAsync bruker cached data (5 min)
var isMember = await _graphClient.IsUserMemberOfAnyGroupAsync(
userPrincipalName,
requirement.AllowedGroupIds);
if (isMember)
{
context.Succeed(requirement);
}
}
}
// I Program.cs
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
policy.Requirements.Add(new AdGroupRequirement("admin-group-id")));
});
builder.Services.AddSingleton<IAuthorizationHandler, AdGroupHandler>();
Gruppedetaljer og medlemmer
public class GroupManagementService
{
private readonly IElvidGraphClient _graphClient;
// Hent info om en gruppe
public async Task<GraphGroup?> GetGroupInfoAsync(string groupId)
{
return await _graphClient.GetGroupAsync(groupId);
}
// Hent alle medlemmer i en gruppe (kun IDs - rask)
public async Task<string[]> GetGroupMemberIdsAsync(string groupId)
{
return await _graphClient.GetGroupMemberIdsAsync(groupId);
}
// Hent alle medlemmer med grunnleggende brukerinfo (for visning i UI)
public async Task<GraphUser[]> GetGroupMembersAsync(string groupId)
{
var members = await _graphClient.GetGroupMembersAsync(groupId);
// Hver bruker har kun essensielle felt (personvern):
// - Id, UserPrincipalName, DisplayName, Mail
//
// MERK: Sensitive felt er filtrert bort av ElvID Graph API:
// - Telefonnummer (MobilePhone, BusinessPhones)
// - Jobbtittel (JobTitle)
// - Kontorplassering (OfficeLocation)
// - Navn-komponenter (GivenName, Surname)
return members;
}
// Hent alle eiere av en gruppe (administratorer)
public async Task<GraphUser[]> GetGroupOwnersAsync(string groupId)
{
// Returnerer samme filtrerte brukerinfo som GetGroupMembersAsync()
return await _graphClient.GetGroupOwnersAsync(groupId);
}
// Hent grunnleggende brukerinfo
public async Task<GraphUser?> GetUserAsync(string userIdentifier)
{
// Kan bruke UPN (user@elvia.no) eller bruker-ID
// Returnerer samme filtrerte brukerinfo som gruppemedlemmene
return await _graphClient.GetUserAsync(userIdentifier);
}
}
Use Case: Meldingsportalen
Viser hvordan Meldingsportalen (eller lignende systemer) kan bruke Graph API:
public class MessagePortalService
{
private readonly IElvidGraphClient _graphClient;
// Vise hvem som har tilgang til en melding (basert på gruppe)
public async Task<List<UserInfo>> GetMessageRecipientsAsync(string groupId)
{
// Hent alle medlemmer i gruppen med full info
var members = await _graphClient.GetGroupMembersAsync(groupId);
return members.Select(m => new UserInfo
{
EntraId = m.Id,
UserName = m.DisplayName ?? "Unknown",
Email = m.Mail ?? m.UserPrincipalName
}).ToList();
}
// Identifisere administratorer/ansvarlige
public async Task<List<UserInfo>> GetGroupAdministratorsAsync(string groupId)
{
var owners = await _graphClient.GetGroupOwnersAsync(groupId);
return owners.Select(o => new UserInfo
{
EntraId = o.Id,
UserName = o.DisplayName ?? "Unknown",
Email = o.Mail ?? o.UserPrincipalName
}).ToList();
}
// Display navn i UI/logger (optimalisert)
public async Task<string> GetDisplayNameForLogAsync(string userId)
{
// Kun display navn - raskere enn full user lookup
var displayName = await _graphClient.GetUserDisplayNameAsync(userId);
return displayName ?? "Unknown User";
}
}
Fordeler med Graph Integration
- ✅ Automatisk token-håndtering - Ingen manuell håndtering av Graph API tokens
- ✅ Innebygd caching - Gruppeoppslag caches i 5 minutter (konfigurerbart)
- ✅ Thread-safe - Sikker for concurrent requests
- ✅ Retry-logikk - Automatisk retry ved midlertidige feil
- ✅ Personvern innebygd - ElvID Graph API filtrerer sensitive felt automatisk
- ✅ Erstatter GraphApiService - Drop-in replacement for ElvID sin GraphApiService
- ✅ Ingen IdentityModel - Ingen avhengighet til deprecated IdentityModel pakker
- ✅ Type-safe - Sterkt typede modeller for alle operasjoner
Migration fra IdentityModel
Problem: KubernetesClient vulnerability krever oppgradering av Elvia.Configuration, som fjerner IdentityModel avhengigheter.
Løsning: Erstatt IdentityModel med TokenClient.
SetBearerToken Extension Method
TokenClient inkluderer SetBearerToken extension method for enkel migrering:
using Elvia.Elvid.TokenClient.Http;
// Fungerer akkurat som IdentityModel sin SetBearerToken
var token = await _tokenProvider.GetAccessTokenAsync([]);
httpClient.SetBearerToken(token);
Før (IdentityModel - fungerer ikke lenger):
// Krever IdentityModel pakke som ikke lenger er inkludert
var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = "https://elvid.test-elvia.io/connect/token",
ClientId = "client-id",
ClientSecret = "secret"
});
var token = tokenResponse.AccessToken;
Etter (TokenClient - fungerer uten eksterne avhengigheter):
Med Dependency Injection:
// I Program.cs - kun én gang
builder.Services.AddElvidTokenProvider(options =>
{
options.TokenEndpoint = builder.Configuration.EnsureHasValue("system:kv:elvid:tokenendpoint");
options.ClientId = builder.Configuration.EnsureHasValue("system:kv:elvid:clientid");
options.ClientSecret = builder.Configuration.EnsureHasValue("system:kv:elvid:clientsecret");
});
// Client credentials - caches automatisk
var token = await _tokenProvider.GetAccessTokenAsync([]);
// Password grant (for smoketests)
var userToken = await _tokenProvider.GetAccessTokenAsync("testuser", "testpass", []);
Uten Dependency Injection (smoketests):
// Direkte opprettelse - perfect for smoketests
var tokenProvider = ElvidTokenClientFactory.Create(
"https://elvid.test-elvia.io/connect/token",
"client-id",
"client-secret");
var token = await tokenProvider.GetAccessTokenAsync("testuser", "testpass", []);
Fordeler
- ✅ Løser IdentityModel-problem - Ingen avhengighet til IdentityModel eller Duende pakker
- ✅ Client Credentials + Password Grant - Støtter både machine-to-machine og user authentication
- ✅ JWT Bearer Authentication - Standardisert oppsett for validering av ElvID tokens
- ✅ Microsoft Graph API - Innebygd støtte for AD-gruppeoppslag og brukerinfo
- ✅ Automatisk caching - Thread-safe med per-scope, per-user og Graph API caching
- ✅ Multiple named clients - Factory pattern for separate API konfigurasjoner (Convey, Kuma etc.)
- ✅ HttpClient-integrasjon - Automatisk Bearer token påføring (også uten scopes)
- ✅ Retry-logikk - Automatisk retry ved 5xx feil
- ✅ Ingen scopes påkrevd - Client credentials flow uten scopes
- ✅ Fjerner duplikat kode - Erstatter ElvidAccessTokenService, GraphApiService og standard authentication setup
- ✅ Perfect for smoketests - Password grant støtte og standalone factory
- ✅ Standalone support -
ElvidTokenClientFactory.Create()for bruk uten DI
Publisering
Tag en versjon for å publisere til NuGet:
git tag tokenclient-v1.0.0
git push origin tokenclient-v1.0.0
| 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
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 10.0.0)
- Microsoft.AspNetCore.Http (>= 2.3.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Http (>= 10.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Options (>= 10.0.0)
-
net8.0
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 8.0.11)
- Microsoft.AspNetCore.Http (>= 2.3.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 8.0.2)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Http (>= 8.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.3)
- Microsoft.Extensions.Options (>= 8.0.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
v2.3.0:
- FIX: Default scope for ElvidGraphClient changed from "elvid.api" to "elvid.graph.read"
- Graph API now works out of the box without explicitly setting Scopes
- Updated documentation to reflect correct scope requirements
v2.2.0:
- FIX: IHttpClientFactory is now automatically registered by AddElvidTokenProvider extensions
- Fixes "Unable to resolve service for type 'System.Net.Http.IHttpClientFactory'" error
- No code changes required for consumers - just update the package
v2.1.0:
- Multi-target support: Now compatible with both .NET 8 and .NET 10
- .NET 8 consumers can use this package again (was .NET 10 only in v2.0.0)
- .NET 10 consumers continue to get optimized .NET 10 assemblies
- No API changes from v2.0.0
v2.0.0:
- BREAKING: Upgraded to .NET 10 LTS
- Updated all Microsoft.Extensions.* packages to 10.0.0
- Updated Microsoft.AspNetCore.Authentication.JwtBearer to 10.0.0
v1.6.0:
- NEW: Microsoft Graph API integration with IElvidGraphClient
- NEW: AD group membership queries with automatic caching (GetUserGroupIdsAsync, GetUserGroupsAsync)
- NEW: User lookup operations (GetUserAsync) - privacy-filtered, essential fields only
- NEW: Group membership checks (IsUserMemberOfGroupAsync, IsUserMemberOfAnyGroupAsync)
- NEW: Group details and member queries (GetGroupAsync, GetGroupMemberIdsAsync)
- NEW: GetGroupMembersAsync() - Get group members with privacy-filtered user info
- NEW: GetGroupOwnersAsync() - Get group owners with privacy-filtered user info
- NEW: AddElvidGraphClient() extension method for easy setup
- PRIVACY: ElvID Graph API filters sensitive fields (phone, job title, office location) automatically
- PRIVACY: All user endpoints return only essential fields (Id, UPN, DisplayName, Mail)
- Enhanced documentation for JWT Bearer authentication setup
- Replaces ElvID's GraphApiService and removes IdentityModel dependency for Graph API
- All Graph API results cached for 5 minutes by default
- Thread-safe Graph API operations with automatic token management
- Comprehensive examples for authorization handlers using AD groups
- Real-world examples: Meldingsportalen integration patterns
- Updated package description and tags to reflect Graph API support
v1.5.1:
- CRITICAL FIX: Domain detection now works correctly with cached discovery endpoints
- Fixed bug where cached token endpoint was not adjusted for current request domain
- Ensures domain translation (.no vs .io) applies on every request, not just first discovery call
v1.5.0:
- Added automatic domain detection and translation (.no vs .io) for ElvID token endpoints
- TokenClient now detects if application runs on elvia.no or elvia.io domain and adjusts token endpoint accordingly
- Similar to AtlasUrl logic, but domain-aware based on incoming HTTP requests
- Fixes issues where applications on .no domain tried to call ElvID on .io (and vice versa)
- Uses IHttpContextAccessor to detect current host domain
- Changed default Create() method to use TokenEndpoint (recommended approach)
- Added CreateWithAuthority() for backwards compatibility with Authority-based discovery
- Updated README to recommend TokenEndpoint as primary configuration method
- TokenEndpoint is simpler, faster, and more robust than Authority-based discovery
- Breaking change: Existing ElvidTokenClientFactory.Create(authority, ...) calls will now interpret first parameter as TokenEndpoint. Use CreateWithAuthority() for Authority.
- Fully backward compatible - works seamlessly for both single-domain and multi-domain deployments
v1.4.0:
- Added TokenEndpoint option to bypass discovery (CRITICAL FIX for internal ElvID calls)
- Added AddElvidJwtAuthentication() extension method for standard JWT Bearer setup
- Added AddElvidJwtBearer() for multiple authentication schemes
- Fixes deployment issues when discovery endpoint not accessible from internal services
- Simplifies common JWT authentication configuration across applications consuming ElvID tokens
- Fully backward compatible
v1.3.0:
- Enhanced AddElvidAuthHandler to support named token providers
- Smart parameter detection: single string = provider name, multiple = scopes
- Enables proper HttpClient integration with multiple named providers
- Fixes integration with IElvidTokenProviderFactory
v1.2.0:
- Added SetBearerToken extension method for easier migration from IdentityModel
- Improved documentation for HttpClient usage patterns
v1.1.0:
- Added Password Grant support for smoketests and user authentication
- Added standalone factory (ElvidTokenClientFactory.Create) for non-DI scenarios
- Added multiple named client support via IElvidTokenProviderFactory
- Removed scope requirement from HttpClient extensions
- Thread-safe per-user token caching for Password Grant
- Enhanced error handling and logging
v1.0.x: Initial OAuth2 Client Credentials support