Pacem.Authentication.OpenIdConnect 0.10.19

There is a newer prerelease version of this package available.
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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Pacem.Authentication.OpenIdConnect" Version="0.10.19" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Pacem.Authentication.OpenIdConnect" Version="0.10.19" />
                    
Directory.Packages.props
<PackageReference Include="Pacem.Authentication.OpenIdConnect" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Pacem.Authentication.OpenIdConnect --version 0.10.19
                    
#r "nuget: Pacem.Authentication.OpenIdConnect, 0.10.19"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Pacem.Authentication.OpenIdConnect@0.10.19
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Pacem.Authentication.OpenIdConnect&version=0.10.19
                    
Install as a Cake Addin
#tool nuget:?package=Pacem.Authentication.OpenIdConnect&version=0.10.19
                    
Install as a Cake Tool

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 to PostLogoutRedirectPath.
  • /signout-oidc (FederatedSignoutPath) ends the provider's session too.
  • ResponseType is honoured per client: code (the default) uses PKCE; the hybrid code id_token doesn'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 (scheme PacemCookieAuthenticationDefaults.Scheme), 40 minutes sliding.
  • Tokens saved in the session (SaveTokens); claims as issued (name, role, sub…, not mapped to ClaimTypes uris), plus the UserInfo claims with the client's ClaimsIssuer (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 WebApplication does by itself (don't call UseRouting() explicitly after UsePacemOpenIdConnectAuthentication(…)).
  • Responses are sent with Cache-Control: no-store. Anonymous requests are allowed and answer the timestamp only.
  • IncludeAccessToken is 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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