Core.Portal.SignIn 0.3.79
Core.Portal.SignIn
Core.Portal.SignIn contains product-agnostic helpers for broker-backed portal sign-in.
For the complete product, marketing, development, trust-boundary, and cross-surface context, read the authentication and authorization capability guide and cross-surface parity plan.
The package calls the Core auth API through Core.Api.Clients.AuthApiClient, validates the returned JWT, optionally enforces transitional configured roles, and returns a cookie-ready ClaimsPrincipal. Product portals still own login pages, branding, antiforgery, redirects, cookie scheme names, SmartScheme routing, named authorization policies, permission vocabulary, and tenant-specific configuration.
Email-first portal flows use ICorePortalAuthDiscoveryService.DiscoverAsync(...) before asking for a password. The service calls POST /api/core/auth/discover with email, app/surface/principal context, and optional tenant hints. Core resolves a tenant from explicit hints, API-key context, a known identity user, or configured SSO email domains, then returns one of:
redirect: start the returnedNextProviderKeythrough/api/core/auth/start.choice: show the returned interactive providers.password: reveal the product-owned password form.needs_tenant_hint: use a direct tenant link or another tenant-safe hint; do not show a tenant picker by default.
Password recovery uses ICorePortalPasswordResetService. The default broker-backed implementation calls the canonical Core JSON auth endpoints:
POST /api/core/auth/password/request-resetPOST /api/core/auth/password/complete-reset
It carries configured tenant context plus optional per-request product code, reset URL base, and return URL. Product portals still own the Razor pages, validation text, reset email copy, and local proof-mode behavior. A portal that is not yet backed by Core identity may register its own ICorePortalPasswordResetService implementation while keeping the same page flow and result contract.
This package must not reference persistence, API controllers, workers, or product repos.
Example
builder.Services.AddCorePortalSignIn(builder.Configuration, options =>
{
options.CoreApiBaseUrl = "https://api.example.com/";
options.TenantApiKey = builder.Configuration["Portal:TenantApiKey"];
options.ProductCode = "product-code";
options.AppId = "product-portal";
options.IncludeAccessTokenClaim = true;
options.AuthorizationContextRefreshInterval = TimeSpan.FromMinutes(5);
options.CookieAuthenticationScheme = "ProductPortal.Cookies";
});
The product login page or controller calls ICorePortalSignInService.BeginPasswordSignInAsync(...).
It either receives a cookie-ready principal or a second-factor challenge. The portal keeps the
transaction token in encrypted server-side/HTTP-only state, posts the entered code through
CompleteSecondFactorAsync(...), and issues its own cookie only after Core returns a principal.
TryCreatePrincipalAsync(...) remains a compatibility wrapper and cannot complete a challenged
sign-in.
Use ICorePortalSecondFactorHandoffStore for the password-to-code-page handoff. It places the broker
transaction in a short-lived, data-protected, HTTP-only cookie, preserves it across invalid-code
retries, and clears malformed or expired state. Configure a distinct
SecondFactorHandoffCookieName if multiple portal applications share one host name.
Use ICorePortalSessionPolicy for every successful cookie sign-in instead of constructing
AuthenticationProperties in the product. The default is a 30-day persistent, sliding ticket;
products can explicitly request a browser-session-only ticket. AddCorePortalSignIn also
post-configures the named cookie with the same lifetime and secure HTTP-only defaults.
var properties = sessionPolicy.CreateSignInProperties(
isPersistent: null, // use the Core default
redirectUri: returnUrl);
await HttpContext.SignInAsync(cookieScheme, principal, properties);
The broker issues an opaque rotating refresh token alongside its short-lived access token. The
refresh token is stored only inside the encrypted portal authentication ticket, is hashed in the
Core database, and is rotated when ICorePortalAuthorizationContextValidator receives an expired
access-token response. This keeps a valid portal session alive across browser restarts without
turning the access JWT into a long-lived bearer credential. A revoked, expired, replayed, or
otherwise invalid refresh token fails closed.
Product logout handlers call ICorePortalSignInService.RevokeCurrentSessionAsync(User) before
deleting their cookie. This revokes the broker refresh session as well as the browser ticket;
deleting only the product cookie is not a complete broker logout.
ICorePortalSessionRefreshCoordinator coalesces simultaneous refresh attempts from the same
browser ticket and keeps a 30-second in-process replay grace so parallel page requests do not race
one another into a sign-out. Multi-replica portals should register a distributed implementation of
that interface or use request affinity for the grace window.
Product authorization uses the broker-issued permissions claim together with product/application
context. Role names are administered bundles and should not be checked by product routes or
navigation. RequiredRoles remains only as a migration compatibility check.
AddCorePortalSignIn post-configures the named cookie scheme to call
ICorePortalAuthorizationContextValidator.ValidateAsync(...) for principals carrying the Core
broker-session marker. Product-owned local proof cookies are left alone. Invalid broker results are
rejected; refreshed principals replace and renew the ticket. The validator calls GET /api/core/auth/me, recomputes
the current normalized role/permission context, and fails closed when the broker session is revoked,
the user is inactive, the access token is missing, or refresh fails. IncludeAccessTokenClaim must
therefore be enabled for broker-backed cookies that use this validator.
When passkeys are enabled by the broker, provider discovery returns a provider whose Kind is
passkey. Product login UI must use
CorePortalAuthenticationPresentation.ShouldOfferPasskeySignIn(...); the shared rule returns true
only while the current principal is signed out. Passkey enrollment is a separate authenticated
account-security action and must never be offered as anonymous registration.
See Passkeys and durable portal sessions for broker configuration, deployment boundaries, and rollout requirements.
See Multi-factor authentication for the challenge handoff, assurance labels, recovery requirements, and deployment gates.
For shared signed-out UI, pair this package with Core.Portal.Chrome auth shell assets. The chrome package owns the Barlow typeface stack, html[data-theme] dark-mode behavior, and standard language selector/preference handoff; this sign-in package stays focused on broker-backed authentication.
No packages depend on Core.Portal.SignIn.
.NET 10.0
- Core.Api.Clients (>= 0.3.79)
- Core.Contracts (>= 0.3.79)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Http (>= 10.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Options (>= 10.0.0)
- System.IdentityModel.Tokens.Jwt (>= 8.15.0)
| Version | Downloads | Last updated |
|---|---|---|
| 0.3.79 | 14 | 08/11/2026 |
| 0.3.78 | 33 | 07/24/2026 |
| 0.3.77 | 112 | 07/20/2026 |
| 0.3.76 | 5 | 07/19/2026 |
| 0.3.75 | 10 | 07/19/2026 |
| 0.3.74 | 5 | 07/19/2026 |
| 0.3.73 | 7 | 07/18/2026 |
| 0.3.72 | 4 | 07/17/2026 |
| 0.3.71 | 4 | 07/17/2026 |
| 0.3.70 | 4 | 07/17/2026 |
| 0.3.69 | 4 | 07/17/2026 |
| 0.3.68 | 4 | 07/17/2026 |
| 0.3.67 | 4 | 07/17/2026 |
| 0.3.66 | 4 | 07/16/2026 |
| 0.3.65 | 4 | 07/16/2026 |
| 0.3.64 | 4 | 07/15/2026 |
| 0.3.63 | 4 | 07/15/2026 |
| 0.3.62 | 4 | 07/15/2026 |
| 0.3.61 | 4 | 07/15/2026 |
| 0.3.60 | 7 | 07/14/2026 |
| 0.3.59 | 6 | 07/14/2026 |
| 0.3.58 | 5 | 07/14/2026 |
| 0.3.57 | 4 | 07/14/2026 |
| 0.3.56 | 4 | 07/14/2026 |
| 0.3.55 | 4 | 07/14/2026 |
| 0.3.50 | 5 | 07/13/2026 |
| 0.3.49 | 3 | 07/13/2026 |
| 0.3.48 | 3 | 07/13/2026 |
| 0.3.27 | 5 | 07/05/2026 |
| 0.3.26 | 6 | 07/05/2026 |
| 0.3.24 | 4 | 07/03/2026 |
| 0.3.23 | 16 | 06/23/2026 |
| 0.3.22 | 5 | 06/22/2026 |
| 0.3.21 | 5 | 06/21/2026 |
| 0.3.20 | 5 | 06/19/2026 |
| 0.3.19 | 5 | 06/19/2026 |
| 0.3.18 | 5 | 06/19/2026 |
| 0.3.17 | 5 | 06/19/2026 |
| 0.3.16 | 4 | 06/19/2026 |
| 0.3.15 | 5 | 06/19/2026 |
| 0.3.14 | 5 | 06/19/2026 |
| 0.3.13 | 4 | 06/19/2026 |
| 0.3.12 | 4 | 06/19/2026 |
| 0.3.11 | 5 | 06/19/2026 |
| 0.3.10 | 6 | 06/19/2026 |
| 0.3.9 | 5 | 06/19/2026 |
| 0.3.8 | 5 | 06/19/2026 |
| 0.3.7 | 5 | 06/19/2026 |
| 0.3.6 | 5 | 06/18/2026 |
| 0.3.5 | 5 | 06/18/2026 |
| 0.3.4 | 7 | 06/10/2026 |
| 0.3.0 | 7 | 06/02/2026 |
| 0.2.0 | 5 | 06/02/2026 |
| 0.1.3-local.1 | 4 | 06/10/2026 |
| 0.1.2 | 6 | 05/31/2026 |
| 0.1.2-local.1 | 4 | 06/10/2026 |
| 0.1.1 | 19 | 05/28/2026 |