Elvia.Elvid.TokenClient
2.4.0
dotnet add package Elvia.Elvid.TokenClient --version 2.4.0
NuGet\Install-Package Elvia.Elvid.TokenClient -Version 2.4.0
<PackageReference Include="Elvia.Elvid.TokenClient" Version="2.4.0" />
<PackageVersion Include="Elvia.Elvid.TokenClient" Version="2.4.0" />
<PackageReference Include="Elvia.Elvid.TokenClient" />
paket add Elvia.Elvid.TokenClient --version 2.4.0
#r "nuget: Elvia.Elvid.TokenClient, 2.4.0"
#:package Elvia.Elvid.TokenClient@2.4.0
#addin nuget:?package=Elvia.Elvid.TokenClient&version=2.4.0
#tool nuget:?package=Elvia.Elvid.TokenClient&version=2.4.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", []);
}
}
Maskinporten Token (via ElvID):
public class MyMaskinportenService
{
private readonly IElvidTokenProvider _tokenProvider;
public async Task<string> GetKrrTokenAsync()
{
// Henter ElvID-token og veksler det mot Maskinporten-token automatisk
return await _tokenProvider.GetMaskinportenTokenAsync(
"maskinporten.krr.global-kontaktinformasjon.read");
}
}
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
}
}
Maskinporten Token Support
TokenClient støtter henting av Maskinporten-tokens via ElvID. Dette forenkler integrasjon mot eksterne tjenester som krever Maskinporten-autentisering.
Bruk
public class ExternalApiService
{
private readonly IElvidTokenProvider _tokenProvider;
public async Task CallExternalApiAsync()
{
// Henter Maskinporten-token i ett kall
// Scopet må starte med "maskinporten." og være konfigurert i ElvID
var maskinportenToken = await _tokenProvider.GetMaskinportenTokenAsync(
"maskinporten.your-scope-here");
// Bruk tokenet mot ekstern API
using var client = new HttpClient();
client.SetBearerToken(maskinportenToken);
var response = await client.GetAsync("https://external-api.no/...");
}
}
Konfigurasjon
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");
// Valgfritt: Override Maskinporten endpoint. Uten denne utledes endepunktet fra samme
// ElvID-host som tokenet hentes fra, så både Authority og TokenEndpoint holder.
// options.MaskinportenTokenEndpoint = "https://elvid.elvia.io/api/maskinporten/token";
// Valgfritt: Justere cache refresh skew (default: 30 sekunder)
// options.MaskinportenRefreshSkewSeconds = 30;
});
Hvordan det fungerer
- Klienten kaller
GetMaskinportenTokenAsync("maskinporten.xxx") - TokenClient henter først et ElvID-token med det angitte scopet
- TokenClient kaller ElvID sitt
/api/maskinporten/tokenendepunkt med ElvID-tokenet som Bearer, og sender?scope=slik at ElvID vet hvilken nedstrøms-API tokenet skal gjelde for - ElvID veksler tokenet hos Maskinporten med Elvias virksomhetssertifikat og returnerer resultatet
Merk: Maskinporten-tokens har kort levetid (119 sekunder). TokenClient cacher tokenet per scope og refresher automatisk før det utløper.
Feilhåndtering: ElvidTokenException.Reason forteller hva som gikk galt, og hva du kan gjøre med det:
| Reason | Betyr | Gjør |
|---|---|---|
RequestRejected |
ElvID avviste forespørselen — som regel har klienten ikke scopet (invalid_scope), eller scopet finnes ikke i dette miljøet |
Fiks konfigurasjonen; retry hjelper ikke |
Unauthorized |
Feil client id/secret | Sjekk hemmeligheten |
UpstreamUnavailable |
ElvID svarte 502/504: Maskinporten (Digdir) svarte ikke | Prøv igjen senere |
HttpError |
Annen 5xx/transportfeil | Retryes automatisk én gang |
Bare HttpError retryes. De andre er deterministiske for forespørselen slik den ble sendt, så en
ny identisk runde kan ikke gi et annet svar — en 504 har dessuten allerede kostet ElvIDs fulle
timeout mot Maskinporten.
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
- ✅ Maskinporten-støtte - Enkel henting av Maskinporten-tokens via ElvID
- ✅ 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, Maskinporten 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 - Én automatisk retry ved transportfeil/5xx; aldri ved avviste forespørsler (4xx) eller når Maskinporten er nede (502/504)
- ✅ 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.11)
- Microsoft.AspNetCore.Http (>= 2.3.12)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.11)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.11)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.11)
- Microsoft.Extensions.Http (>= 10.0.11)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.11)
- Microsoft.Extensions.Options (>= 10.0.11)
-
net8.0
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 8.0.30)
- Microsoft.AspNetCore.Http (>= 2.3.12)
- 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.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.4.0 | 1,884 | 9/22/2026 |
| 2.3.2 | 7,092 | 8/17/2026 |
| 2.3.1 | 2,232 | 8/6/2026 |
| 2.3.0 | 31,245 | 1/8/2026 |
| 2.2.0 | 351 | 1/6/2026 |
| 2.1.0 | 3,147 | 12/1/2025 |
| 2.0.0 | 552 | 12/1/2025 |
| 1.6.0 | 4,928 | 10/28/2025 |
| 1.5.1 | 1,644 | 10/10/2025 |
| 1.5.0 | 770 | 10/8/2025 |
| 1.4.0 | 662 | 10/7/2025 |
| 1.3.0 | 1,943 | 9/30/2025 |
| 1.2.0 | 414 | 9/29/2025 |
| 1.1.0 | 357 | 9/26/2025 |
| 1.0.1 | 222 | 9/26/2025 |
v2.4.0:
- NEW: IElvidTokenProvider.GetMaskinportenTokenAsync() - fetch a Maskinporten token via ElvID in one call
- Replaces the manual two-step dance (ElvID token, then GET /api/maskinporten/token)
- Maskinporten tokens are cached per scope and refreshed ahead of their ~119 second lifetime
- Works with either Authority or TokenEndpoint configuration, and follows .no/.io domain detection
- NEW: ElvidTokenErrorReason.UpstreamUnavailable - Maskinporten did not answer (ElvID 502/504); retry later
- NEW: ElvidTokenErrorReason.RequestRejected - ElvID rejected the request (invalid_scope, invalid_grant,
scope not granted). Previously surfaced as HttpError and retried once; now not retried.
- NOTE: hand-written implementations of IElvidTokenProvider must add the new member (Moq mocks are unaffected)
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