Pacem.Authentication.OpenIdConnect
0.10.19
See the version list below for details.
dotnet add package Pacem.Authentication.OpenIdConnect --version 0.10.19
NuGet\Install-Package Pacem.Authentication.OpenIdConnect -Version 0.10.19
<PackageReference Include="Pacem.Authentication.OpenIdConnect" Version="0.10.19" />
<PackageVersion Include="Pacem.Authentication.OpenIdConnect" Version="0.10.19" />
<PackageReference Include="Pacem.Authentication.OpenIdConnect" />
paket add Pacem.Authentication.OpenIdConnect --version 0.10.19
#r "nuget: Pacem.Authentication.OpenIdConnect, 0.10.19"
#:package Pacem.Authentication.OpenIdConnect@0.10.19
#addin nuget:?package=Pacem.Authentication.OpenIdConnect&version=0.10.19
#tool nuget:?package=Pacem.Authentication.OpenIdConnect&version=0.10.19
Pacem.Authentication.OpenIdConnect
OpenID Connect sign-in for ASP.NET Core apps: a cookie session plus the OpenID Connect handler, wired for apps that serve several clients at once (one per website, resolved per request), with the session's tokens kept fresh and, together with Pacem.Net.Http, renewed on demand when a downstream API refuses the user's token.
- Clients resolved per request (
IOidcClientRetriever), each with its own id, secret, scopes, response type and callbacks. - Sign-in, sign-out and federated sign-out paths served by a middleware.
- Session refresh: shortly before the access token expires, the session's refresh token gets a new pair, written back into the cookie. One refresh per refresh token: concurrent requests of a session share it.
- Renewal on demand:
AddUserAccessToken()HTTP clients renew a refused user token through the session. - An opt-in session ping (
UsePacemOpenIdConnectAuthentication("/ping")) that keeps the session alive while a page stays open.
Replaces Pacem.Mvc.OpenIdConnect (see the CHANGELOG for what changed and how to move).
Install
dotnet add package Pacem.Authentication.OpenIdConnect
Targets net10.0. Talks to the identity provider through
Pacem.IdentityModel.
One client
"OpenIdConnect": {
"Authority": "https://id.example.com/acme",
"Client": {
"ClientId": "website",
"ClientSecret": "<secret>",
"Scope": "openid profile offline_access api",
"ResponseType": "code"
}
}
public static class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
IConfigurationSection oidc = builder.Configuration.GetSection("OpenIdConnect");
builder.Services.AddPacemOpenIdConnectAuthentication(oidc["Authority"]!, client => oidc.GetSection("Client").Bind(client));
var app = builder.Build();
app.UsePacemOpenIdConnectAuthentication();
app.MapGet("/", (ClaimsPrincipal user) => $"Hello {user.Identity?.Name ?? "anonymous"}");
app.Run();
}
}
/signin-oidc(LoginRedirectPath) starts the sign-in for an anonymous user and is where the provider comes back. The user lands on?RedirectUri=or the referer when they're local, the root otherwise./signout(LogoutPath) ends the local session and redirects toPostLogoutRedirectPath./signout-oidc(FederatedSignoutPath) ends the provider's session too.ResponseTypeis honoured per client:code(the default) uses PKCE; the hybridcode id_tokendoesn't.- Ask for
offline_access(or whatever your provider needs) to get a refresh token: without one the session can't be refreshed.
A client per website
Multi-tenant apps pick the client from the request:
builder.Services.AddPacemOpenIdConnectAuthentication<WebsiteClientRetriever>("https://id.example.com/acme");
public sealed class WebsiteClientRetriever(IWebsiteStore websites) : IOidcClientRetriever
{
public async Task<OidcClientOptions?> FindByContextAsync(HttpContext context)
{
var website = await websites.FindByHostAsync(context.Request.Host.Host);
if (website is null)
{
// no sign-in on this host
return null;
}
return new OidcClientOptions
{
ClientId = website.ClientId,
ClientSecret = website.ClientSecret,
Scope = "openid profile offline_access",
OnTokenValidatedAsync = context => website.ProvisionAsync(context.Principal!),
};
}
}
Lighter alternatives: AddPacemOpenIdConnectAuthentication(authority, context => …) (sync or async), or
AddPacemOpenIdConnectAuthentication(authority, factory, lifetime) for a retriever with a scoped lifetime. A derived
options type works too: AddPacemOpenIdConnectAuthentication<MyClientOptions>(authority, o => …).
Callbacks
Each client can hook the handler's events without replacing the package's own handling:
OnRedirectToIdentityProviderAsync, OnRedirectToIdentityProviderForSignOutAsync, OnTokenValidatedAsync (provision the
user), OnAuthorizationCodeReceivedAsync, OnUserInformationReceivedAsync, OnAccessDeniedAsync (or just
AccessDeniedPath), OnAuthenticationFailedAsync.
The session
- Cookie
.pacem.auth(schemePacemCookieAuthenticationDefaults.Scheme), 40 minutes sliding. - Tokens saved in the session (
SaveTokens); claims as issued (name,role,sub…, not mapped toClaimTypesuris), plus the UserInfo claims with the client'sClaimsIssuer(or the token's issuer). - Refresh: when the access token has less than 5 minutes left, the session's refresh token is redeemed at the provider's token endpoint (read from its discovery document, cached), and the new tokens go back into the cookie. Tokens the provider doesn't reissue (a non-rotating refresh token, the id_token) are kept. Requests of the same session arriving together, or shortly after with the old cookie, share one refresh instead of spending a rotated refresh token twice. A failed refresh is logged (never with the tokens) and the session goes on as it was.
The sharing is per process: instances of a web farm don't coordinate.
Keeping the session alive (opt-in)
A page left open for a while can ping the app, so the sliding cookie doesn't expire and the tokens stay fresh:
app.UsePacemOpenIdConnectAuthentication("/ping"); // GET /ping → { "timestamp": "…" }
// or: the user's access token too, for client-side code calling APIs directly (on /ping, or a path of yours)
app.UsePacemOpenIdConnectAuthentication(ping => ping.IncludeAccessToken = true); // { "timestamp": "…", "token": "…" }
app.UsePacemOpenIdConnectAuthentication("/session/ping", ping => ping.IncludeAccessToken = true);
- These overloads take a
WebApplication(an app that is also an endpoint route builder); the parameterless one maps no ping. - The ping refreshes the session with a 6-minute margin instead of 5: a page pinging every few minutes gets the new
tokens before its other requests need them. This needs routing to run before authentication, which a
WebApplicationdoes by itself (don't callUseRouting()explicitly afterUsePacemOpenIdConnectAuthentication(…)). - Responses are sent with
Cache-Control: no-store. Anonymous requests are allowed and answer the timestamp only. IncludeAccessTokenis off by default. With it on, any script running in the page (an XSS, a third-party library, a browser extension) can read the token and use it elsewhere until it expires. Keep it in a variable, not in the DOM or in browser storage, keep access tokens short-lived and their scopes narrow, and use a Content-Security-Policy.
Calling APIs on behalf of the user
With Pacem.Net.Http, forward the user's token, and let this package renew it when the API refuses it:
builder.Services.AddPacemOpenIdConnectAuthentication(oidc["Authority"]!, client => oidc.GetSection("Client").Bind(client));
builder.Services.AddRestClient("api", client => client.BaseAddress = new Uri("https://api.example.com/"))
.AddUserAccessToken();
app.MapGet("/orders", async (IRestClientFactory rest, CancellationToken cancellationToken) =>
{
var orders = await rest.CreateClient("api").GetAsync<List<OrderDto>>("orders", cancellationToken);
return Results.Ok(orders);
});
When the API answers 401, the session is refreshed (sharing the refresh with the cookie validation), the cookie is
rewritten and the call retried once with the new token; later calls of the same request use it too. This happens only
while the response hasn't started: afterwards the cookie can't be rewritten, and with rotating refresh tokens renewing
anyway would lose the new pair, so the 401 is returned as it is.
License
Apache-2.0.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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.OpenIdConnect (>= 10.0.14)
- Pacem.Core (>= 0.10.19)
- Pacem.IdentityModel (>= 0.10.19)
- Pacem.Net.Http (>= 0.10.19)
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-ricci | 0 | 10/28/2026 |
| 1.0.0-peano | 40 | 10/27/2026 |
| 1.0.0-maiorana | 38 | 10/27/2026 |
| 0.10.19 | 39 | 10/27/2026 |
| 0.10.19-galilei | 43 | 10/27/2026 |
| 0.10.19-fermi | 41 | 10/27/2026 |
| 0.10.19-degiorgi | 50 | 10/26/2026 |