.NET SDK

Integrate Signward authentication into ASP.NET Core apps with the Signward.IdServer.Client NuGet package.

The official .NET SDK wraps the OIDC discovery flow, JWT validation, and token forwarding into a single AddIdServerAuth() extension. Works with ASP.NET Core 8, 9, and 10.

Install

Install via the .NET CLI:

dotnet add package Signward.IdServer.Client

Or PackageReference in your .csproj:

<PackageReference Include="Signward.IdServer.Client" Version="1.0.4" />

Current version: 1.0.4. Upgrade from 1.0.x is a drop-in package bump — no API changes, and the new options are all opt-in with defaults matching the previous behaviour.

For a server-rendered MVC or Razor Pages app that signs users in:

using IdServer.Client.Middleware;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddIdServerAuth(o =>
{
    o.Authority = "https://api.signward.com";
    o.ClientId = "YOUR_CLIENT_ID";
    o.ClientSecret = "YOUR_CLIENT_SECRET";
    o.Scopes = "openid profile email roles";
    o.RemoteFailurePath = "/Home/LoginError";   // see "Handle an interrupted sign-in" below
});

var app = builder.Build();
app.UseIdServerAuth();
app.MapRazorPages();
app.Run();

The middleware wires up:

  • A cookie authentication scheme (default)
  • OpenID Connect challenge scheme (redirects to Signward on 401)
  • /Account/Login and /Account/Logout handler routes
  • Silent access-token refresh (see RefreshThreshold below)

Handle an interrupted sign-in

A login can fail on the return leg for reasons that are entirely routine: the authorization code was already redeemed, the correlation cookie expired, or the user re-opened the callback URL from browser history. Without a handler ASP.NET Core turns these into an unhandled AuthenticationFailureException — a 500 error page on what is really just "start over".

Set RemoteFailurePath (1.0.4+) to a page of your own:

o.RemoteFailurePath = "/Home/LoginError";

Two requirements for that page, both easy to get wrong:

  • It must be reachable without authentication ([AllowAnonymous]). If your app has a global fallback authorization policy, an authenticated-only error page gets challenged and the user bounces back into the login that just failed.
  • It must not start a new login automatically. Offer a manual "try again" link instead — otherwise a persistent cookie problem becomes a redirect loop.

The failure is logged (IdServer.Client.Auth) with the underlying exception, so the cause stays visible in your own logs.

Sign out

Point your sign-out link at /Account/Logout. It does three things, and all three matter:

  1. Denies the current access token server-side, so a copy of it cannot be replayed before it expires
  2. Clears the local cookie
  3. Signs out of the OpenID Connect scheme — the Signward session itself ends, not just your app's

Without the third step the user clicks "sign out", then the next login walks straight back in without being asked for credentials, because the identity provider still considers them signed in. Where they land afterwards is PostLogoutRedirectUri (default /).

Keep the session alive

The access token is deliberately short-lived (15 minutes by default) so it can be revoked quickly. To avoid signing users out that often, the SDK refreshes it silently: when a request arrives and the token expires within RefreshThreshold (default 2 minutes), it exchanges the stored refresh token in the background and re-stores the new pair in the cookie.

o.RefreshThreshold = TimeSpan.FromMinutes(2);   // TimeSpan.Zero disables it

The session therefore lasts as long as the refresh token (7 days server-side), while the access token stays short. Requires SaveTokens = true (the default) and cookie auth.

Configure — API (JWT bearer)

For a REST API that only validates incoming bearer tokens (no cookies, no redirect):

builder.Services.AddIdServerAuth(o =>
{
    o.Authority = "https://api.signward.com";
    o.Audience = "your-api-audience";
    o.UseCookieAuth = false;
});

JWT validation is automatic: issuer, audience, signature (JWKS), and expiration are all enforced.

Protect an endpoint

Use the standard [Authorize] attribute, or the SDK-provided role-aware shortcut:

[ApiController]
[Route("api/reports")]
public class ReportsController : ControllerBase
{
    [HttpGet]
    [IdServerAuthorize("admin", "owner")]
    public IActionResult List() => Ok(new { reports = new[] { "Q1", "Q2" } });
}

IdServerAuthorize accepts both built-in roles (admin, owner, user) and custom roles defined per-tenant in the Portal.

Read the current user

The SDK exposes IdServerUser extensions on ClaimsPrincipal:

app.MapGet("/me", (ClaimsPrincipal user) => new
{
    userId = user.GetUserId(),
    email = user.GetEmail(),
    tenantId = user.GetTenantId(),
    isAdmin = user.HasRole("admin")
});

Forward tokens to downstream APIs

Inject IdServerTokenHandler into any named HttpClient and the user's bearer token is forwarded automatically:

builder.Services.AddHttpClient("reports-api", c => c.BaseAddress = new Uri("https://reports.myapp.com"))
    .AddHttpMessageHandler<IdServerTokenHandler>();

Next steps