Authentication against an external identity provider: validating bearer tokens it issued, and driving an interactive sign-in through OpenID Connect. For tokens this application issues itself, see JWT Authentication.
Validating tokens something else issued — Entra ID, Auth0, Keycloak, Duende, Okta.
AddJwtAuthentication cannot do this: it requires a Secret and always derives a symmetric key, while an external
authority signs with rotating asymmetric keys published at a JWKS endpoint. AddBearerAuthentication registers no
ITokenHelper — it reads tokens, it does not mint them.
var services = new ServiceCollection();
IConfiguration configuration = new ConfigurationManager();
// Any OpenID Connect provider
services.AddBearerAuthentication(o =>
{
o.Authority = "https://your-tenant.eu.auth0.com/";
o.Audience = "https://api.example.com";
});
// Entra ID, from the app registration
services.AddEntraIdBearer(o =>
{
o.TenantId = configuration["Authentication:EntraId:TenantId"]!;
o.ClientId = configuration["Authentication:EntraId:ClientId"]!;
});
| Property | Type | Default | Description |
|---|---|---|---|
AuthenticationScheme |
string |
"Bearer" |
|
Authority |
string? |
null |
Issuer base URL; signing keys discovered and refreshed from it |
MetadataAddress |
string? |
null |
Overrides the derived metadata URL |
Secret |
string? |
null |
Shared symmetric key, for an HMAC-signing issuer |
Audience / Audiences |
null |
Audiences wins when both are set |
|
ValidIssuers |
ICollection<string>? |
null |
Null ⇒ the discovery document’s issuer |
RequireHttpsMetadata |
bool |
true |
|
SaveToken |
bool |
false |
Keep the raw token for a downstream call |
ValidateLifetime |
bool |
true |
|
ClockSkew |
TimeSpan |
Zero |
|
NameClaimType / RoleClaimType |
string |
"name" / "role" |
|
Claims |
ClaimNormalizationOptions |
(defaults) | |
Configure |
Action<JwtBearerOptions>? |
null |
Applied before the normalization hook is chained on |
Exactly one source of signing keys is required — Authority/MetadataAddress or Secret. Both, or neither,
throws at registration.
| Property | Type | Default | Description |
|---|---|---|---|
TenantId |
string |
(required) | Directory id, or organizations / common |
ClientId |
string |
(required) | This API’s application id |
Instance |
string |
https://login.microsoftonline.com |
Sovereign clouds differ |
UseV2Endpoint |
bool |
true |
|
Audiences |
ICollection<string>? |
null |
Defaults to both api://{ClientId} and {ClientId} |
AuthenticationScheme |
string |
"Bearer" |
|
SaveToken |
bool |
false |
|
Claims |
ClaimNormalizationOptions |
(defaults) | |
Configure |
Action<JwtBearerOptions>? |
null |
roles, plural. role singular matches nothing, so [Authorize(Roles = "Admin")]
answers 403 against a token that visibly contains the role. The preset handles it and normalization adds a role
copy, so both spellings work.oid is the stable user id, sub is not — Entra’s sub is pairwise per application, so two apps see
different values for the same person.accessTokenAcceptedVersion: null issues v1 tokens from
https://sts.windows.net/{tid}/; the mismatch surfaces as IDX10205. Both spellings are accepted for a single
tenant.organizations / common is multi-tenant, so there is no fixed issuer — it is validated against the token’s
own tid. Any tenant can then sign in; deciding whether that tenant is entitled to anything is the
application’s job.groups is object GUIDs, and past the token-size limit it is dropped in favour of _claim_names — so a
groups-based model breaks for the users in the most groups. Resolving it needs a Graph call.No Microsoft.Identity.Web, no MSAL. They protect an API and sign users in, and stop there — no on-behalf-of flow,
no downstream calls as the user, no MSAL token cache, no incremental consent, no B2C user flows. Take
Microsoft.Identity.Web directly if you need those.
Signing users in through a browser: authorization code + PKCE, landing in a cookie session. Always a pair of
schemes — the OIDC handler runs the challenge and code exchange, a cookie holds the session — and
AddOidcAuthentication registers both.
var services = new ServiceCollection();
IConfiguration configuration = new ConfigurationManager();
// Entra ID
services.AddEntraIdSignIn(o =>
{
o.TenantId = configuration["Authentication:EntraId:TenantId"]!;
o.ClientId = configuration["Authentication:EntraId:ClientId"]!;
o.ClientSecret = configuration["Authentication:EntraId:ClientSecret"]!;
});
// Any OpenID Connect provider
services.AddOidcAuthentication(o =>
{
o.Authority = "https://your-tenant.eu.auth0.com/";
o.ClientId = "…";
o.ClientSecret = "…";
});
| Default | Scheme |
|---|---|
DefaultScheme / DefaultAuthenticateScheme / DefaultSignInScheme |
the cookie |
DefaultChallengeScheme / DefaultSignOutScheme |
the OIDC scheme |
Backwards, an [Authorize] endpoint either tries to validate an id_token it does not have, or redirects to the
provider on every request.
| Property | Type | Default | Description |
|---|---|---|---|
AuthenticationScheme |
string |
"OpenIdConnect" |
|
SignInScheme |
string? |
null |
Defaults to Cookie’s scheme |
Authority / ClientId |
string |
(required) | |
ClientSecret |
string? |
null |
Required for the confidential-client code exchange |
ResponseType |
string |
"code" |
|
Scopes |
ICollection<string> |
openid profile email |
Replaces the handler’s defaults |
CallbackPath |
string |
/signin-oidc |
Must match a registered redirect URI exactly |
SignedOutCallbackPath |
string |
/signout-callback-oidc |
|
SignedOutRedirectUri |
string? |
null |
|
UsePkce |
bool |
true |
|
SaveTokens |
bool |
false |
Keeps the tokens in the cookie — and enlarges it |
GetClaimsFromUserInfoEndpoint |
bool |
true |
A lean id_token usually omits email |
RequireHttpsMetadata |
bool |
true |
|
NameClaimType / RoleClaimType |
string |
"name" / "role" |
|
ValidIssuers |
ICollection<string>? |
null |
Null ⇒ the discovery document’s issuer |
Cookie |
CookieAuthOptions |
(defaults) | The session half |
Claims |
ClaimNormalizationOptions |
(defaults) | |
Configure |
Action<OpenIdConnectOptions>? |
null |
Applied before the normalization hook is chained on |
EntraIdSignInOptions is the preset — TenantId, ClientId, ClientSecret, Instance, UseV2Endpoint,
Scopes, the callback paths, SaveTokens, and a Configure reaching the full OidcAuthOptions.
redirect_uri
from the internal plain-HTTP request; the provider rejects it, or returns the browser to the wrong origin where
the correlation cookie is not sent back. Configure UseForwardedHeaders ahead of the authentication middleware.CallbackPath must match a registered redirect URI exactly, scheme, host and port included.SaveTokens = true is required for a later downstream call and makes the cookie considerably larger.Configure delegate so customization cannot drop it.The code exchange itself needs a live provider — verify the full round trip against a real tenant.
Apache License 2.0 — this package contains no license validation and no runtime limits. See LICENSE. A few companion packages are commercially licensed with a free tier; see the licensing overview.