Browse Source

Update the samples to stop using SignInManager.CreateUserPrincipalAsync() and use the Microsoft claims for cookie authentication

pull/1463/head
Kévin Chalet 4 years ago
parent
commit
1b9081778d
  1. 4
      sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs
  2. 3
      sandbox/OpenIddict.Sandbox.AspNet.Client/Views/Home/Index.cshtml
  3. 6
      sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthenticationController.cs
  4. 23
      sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs
  5. 3
      sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/ResourceController.cs
  6. 18
      sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs
  7. 4
      sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml
  8. 20
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthenticationController.cs
  9. 79
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthorizationController.cs
  10. 4
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/ResourceController.cs
  11. 2
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/UserinfoController.cs
  12. 11
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/Startup.cs

4
sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs

@ -114,7 +114,7 @@ namespace OpenIddict.Sandbox.AspNet.Client.Controllers
.Select(claim => claim switch
{
// Map the standard "sub" and custom "id" claims to ClaimTypes.NameIdentifier, which is
// the default claim type used by ASP.NET and is required by the antiforgery components.
// the default claim type used by .NET and is required by the antiforgery components.
{ Type: Claims.Subject } or
{ Type: "id", Issuer: "https://github.com/" or "https://twitter.com/" }
=> new Claim(ClaimTypes.NameIdentifier, claim.Value, claim.ValueType, claim.Issuer),
@ -137,7 +137,7 @@ namespace OpenIddict.Sandbox.AspNet.Client.Controllers
_ => false
}));
// The antiforgery components require both the nameidentifier and identityprovider claims
// The antiforgery components require both the ClaimTypes.NameIdentifier and identityprovider claims
// so the latter is manually added using the issuer identity resolved from the remote server.
claims.Add(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider",
result.Identity.GetClaim(Claims.AuthorizationServer)));

3
sandbox/OpenIddict.Sandbox.AspNet.Client/Views/Home/Index.cshtml

@ -1,5 +1,4 @@
@using System.Security.Claims
@using OpenIddict.Abstractions
@model string
<div class="jumbotron">
@ -20,7 +19,7 @@
}
if (User is ClaimsPrincipal principal &&
principal.FindFirst(OpenIddictConstants.Claims.Subject)?.Issuer is "https://localhost:44395/")
principal.FindFirst(ClaimTypes.NameIdentifier)?.Issuer is "https://localhost:44349/")
{
<form action="~/" method="post">
@Html.AntiForgeryToken()

6
sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthenticationController.cs

@ -67,7 +67,7 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers
.Select(claim => claim switch
{
// Map the standard "sub" and custom "id" claims to ClaimTypes.NameIdentifier, which is
// the default claim type used by ASP.NET and is required by the antiforgery components.
// the default claim type used by .NET and is required by the antiforgery components.
{ Type: Claims.Subject } or
{ Type: "id", Issuer: "https://github.com/" }
=> new Claim(ClaimTypes.NameIdentifier, claim.Value, claim.ValueType, claim.Issuer),
@ -90,11 +90,13 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers
_ => false
}));
// The antiforgery components require both the nameidentifier and identityprovider claims
// The antiforgery components require both the ClaimTypes.NameIdentifier and identityprovider claims
// so the latter is manually added using the issuer identity resolved from the remote server.
claims.Add(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider",
result.Identity.GetClaim(Claims.AuthorizationServer)));
// Note: when using external authentication providers with ASP.NET Identity,
// the user identity MUST be added to the external authentication cookie scheme.
var identity = new ClaimsIdentity(claims,
authenticationType: DefaultAuthenticationTypes.ExternalCookie,
nameType: ClaimTypes.Name,

23
sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs

@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
@ -140,11 +141,12 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers
case ConsentTypes.Implicit:
case ConsentTypes.External when authorizations.Any():
case ConsentTypes.Explicit when authorizations.Any() && !request.HasPrompt(Prompts.Consent):
var identity = new ClaimsIdentity(OpenIddictServerOwinDefaults.AuthenticationType);
identity.AddClaims((await context.Get<ApplicationSignInManager>().CreateUserIdentityAsync(user)).Claims);
identity.AddClaim(new Claim(Claims.Subject, identity.FindFirstValue(ClaimTypes.NameIdentifier)));
identity.AddClaim(new Claim(Claims.Name, identity.FindFirstValue(ClaimTypes.Name)));
// Create the claims-based identity that will be used by OpenIddict to generate tokens.
var identity = new ClaimsIdentity(OpenIddictServerOwinDefaults.AuthenticationType)
.AddClaim(Claims.Subject, user.Id)
.AddClaim(Claims.Email, user.Email)
.AddClaim(Claims.Name, user.UserName)
.AddClaims(Claims.Role, (await context.Get<ApplicationUserManager>().GetRolesAsync(user.Id)).ToImmutableArray());
// Note: in this sample, the granted scopes match the requested scope
// but you may want to allow the user to uncheck specific scopes.
@ -255,11 +257,12 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers
return new EmptyResult();
}
var identity = new ClaimsIdentity(OpenIddictServerOwinDefaults.AuthenticationType);
identity.AddClaims((await context.Get<ApplicationSignInManager>().CreateUserIdentityAsync(user)).Claims);
identity.AddClaim(new Claim(Claims.Subject, identity.FindFirstValue(ClaimTypes.NameIdentifier)));
identity.AddClaim(new Claim(Claims.Name, identity.FindFirstValue(ClaimTypes.Name)));
// Create the claims-based identity that will be used by OpenIddict to generate tokens.
var identity = new ClaimsIdentity(OpenIddictServerOwinDefaults.AuthenticationType)
.AddClaim(Claims.Subject, user.Id)
.AddClaim(Claims.Email, user.Email)
.AddClaim(Claims.Name, user.UserName)
.AddClaims(Claims.Role, (await context.Get<ApplicationUserManager>().GetRolesAsync(user.Id)).ToImmutableArray());
// Note: in this sample, the granted scopes match the requested scope
// but you may want to allow the user to uncheck specific scopes.

3
sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/ResourceController.cs

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using System.Web.Http;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
using OpenIddict.Abstractions;
using OpenIddict.Validation.Owin;
using static OpenIddict.Abstractions.OpenIddictConstants;
@ -22,7 +23,7 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers
// This demo action requires that the client application be granted the "demo_api" scope.
// If it was not granted, a detailed error is returned to the client application to inform it
// that the authorization process must be restarted with the specified scope to access this API.
if (!((ClaimsPrincipal) User).HasClaim(Claims.Private.Scope, "demo_api"))
if (User is not ClaimsPrincipal principal || !principal.HasScope("demo_api"))
{
context.Authentication.Challenge(
authenticationTypes: OpenIddictValidationOwinDefaults.AuthenticationType,

18
sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs

@ -100,16 +100,22 @@ public class AuthenticationController : Controller
var claims = new List<Claim>(result.Principal.Claims
.Select(claim => claim switch
{
// Applications can map non-standard claims issued by specific issuers to a standard equivalent.
// Map the standard "sub" and custom "id" claims to ClaimTypes.NameIdentifier, which is
// the default claim type used by .NET and is required by the antiforgery components.
{ Type: Claims.Subject } or
{ Type: "id", Issuer: "https://github.com/" or "https://twitter.com/" }
=> new Claim(Claims.Subject, claim.Value, claim.ValueType, claim.Issuer),
=> new Claim(ClaimTypes.NameIdentifier, claim.Value, claim.ValueType, claim.Issuer),
// Map the standard "name" claim to ClaimTypes.Name.
{ Type: Claims.Name }
=> new Claim(ClaimTypes.Name, claim.Value, claim.ValueType, claim.Issuer),
_ => claim
})
.Where(claim => claim switch
{
// Preserve the "name" and "sub" claims.
{ Type: Claims.Name or Claims.Subject } => true,
// Preserve the nameidentifier and name claims.
{ Type: ClaimTypes.NameIdentifier or ClaimTypes.Name } => true,
// Applications that use multiple client registrations can filter claims based on the issuer.
{ Type: "bio", Issuer: "https://github.com/" } => true,
@ -120,8 +126,8 @@ public class AuthenticationController : Controller
var identity = new ClaimsIdentity(claims,
authenticationType: CookieAuthenticationDefaults.AuthenticationScheme,
nameType: Claims.Name,
roleType: Claims.Role);
nameType: ClaimTypes.Name,
roleType: ClaimTypes.Role);
// Build the authentication properties based on the properties that were added when the challenge was triggered.
var properties = new AuthenticationProperties(result.Properties.Items);

4
sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml

@ -1,4 +1,4 @@
@using static OpenIddict.Abstractions.OpenIddictConstants
@using System.Security.Claims
@model string
<div class="jumbotron">
@ -18,7 +18,7 @@
<h3>Message received from the resource controller: @Model</h3>
}
if (User.FindFirst(Claims.Subject)?.Issuer is "https://localhost:44349/")
if (User.FindFirst(ClaimTypes.NameIdentifier)?.Issuer is "https://localhost:44395/")
{
<form action="/" method="post">
<button class="btn btn-lg btn-warning" type="submit">Query the resource controller</button>

20
sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthenticationController.cs

@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using OpenIddict.Client.AspNetCore;
using static OpenIddict.Abstractions.OpenIddictConstants;
namespace OpenIddict.Sandbox.AspNetCore.Server.Controllers;
@ -56,18 +57,22 @@ public class AuthenticationController : Controller
var claims = new List<Claim>(result.Principal.Claims
.Select(claim => claim switch
{
// Note: when using external authentication providers with ASP.NET Core Identity,
// the ClaimTypes.NameIdentifier claim - which is not configurable in Identity -
// MUST be used to store the user identifier.
{ Type: "id", Issuer: "https://github.com/" }
// Map the standard "sub" and custom "id" claims to ClaimTypes.NameIdentifier, which is
// the default claim type used by .NET and is required by the antiforgery components.
{ Type: Claims.Subject } or
{ Type: "id", Issuer: "https://github.com/" or "https://twitter.com/" }
=> new Claim(ClaimTypes.NameIdentifier, claim.Value, claim.ValueType, claim.Issuer),
// Map the standard "name" claim to ClaimTypes.Name.
{ Type: Claims.Name }
=> new Claim(ClaimTypes.Name, claim.Value, claim.ValueType, claim.Issuer),
_ => claim
})
.Where(claim => claim switch
{
// Preserve the ClaimTypes.NameIdentifier claim.
{ Type: ClaimTypes.NameIdentifier } => true,
// Preserve the nameidentifier and name claims.
{ Type: ClaimTypes.NameIdentifier or ClaimTypes.Name } => true,
// Applications that use multiple client registrations can filter claims based on the issuer.
{ Type: "bio", Issuer: "https://github.com/" } => true,
@ -77,8 +82,7 @@ public class AuthenticationController : Controller
}));
// Note: when using external authentication providers with ASP.NET Core Identity,
// the "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" claim
// - which is not configurable in Identity - MUST be used to store the user identifier.
// the user identity MUST be added to the external authentication cookie scheme.
var identity = new ClaimsIdentity(claims,
authenticationType: IdentityConstants.ExternalScheme,
nameType: ClaimTypes.NameIdentifier,

79
sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthorizationController.cs

@ -4,6 +4,7 @@
* the license and the contributors participating to this project.
*/
using System.Collections.Immutable;
using System.Security.Claims;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Authentication;
@ -170,13 +171,18 @@ public class AuthorizationController : Controller
case ConsentTypes.Implicit:
case ConsentTypes.External when authorizations.Any():
case ConsentTypes.Explicit when authorizations.Any() && !request.HasPrompt(Prompts.Consent):
var principal = await _signInManager.CreateUserPrincipalAsync(user);
// Create the claims-based identity that will be used by OpenIddict to generate tokens.
var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)
.AddClaim(Claims.Subject, await _userManager.GetUserIdAsync(user))
.AddClaim(Claims.Email, await _userManager.GetEmailAsync(user))
.AddClaim(Claims.Name, await _userManager.GetUserNameAsync(user))
.AddClaims(Claims.Role, (await _userManager.GetRolesAsync(user)).ToImmutableArray());
// Note: in this sample, the granted scopes match the requested scope
// but you may want to allow the user to uncheck specific scopes.
// For that, simply restrict the list of scopes before calling SetScopes.
principal.SetScopes(request.GetScopes());
principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync());
identity.SetScopes(request.GetScopes());
identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync());
// Automatically create a permanent authorization to avoid requiring explicit consent
// for future authorization or token requests containing the same scopes.
@ -184,17 +190,17 @@ public class AuthorizationController : Controller
if (authorization is null)
{
authorization = await _authorizationManager.CreateAsync(
principal: principal,
principal: new ClaimsPrincipal(identity),
subject : await _userManager.GetUserIdAsync(user),
client : await _applicationManager.GetIdAsync(application),
type : AuthorizationTypes.Permanent,
scopes : principal.GetScopes());
scopes : identity.GetScopes());
}
principal.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization));
principal.SetDestinations(GetDestinations);
identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization));
identity.SetDestinations(GetDestinations);
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
// At this point, no authorization was found in the database and an error must be returned
// if the client application specified prompt=none in the authorization request.
@ -256,13 +262,18 @@ public class AuthorizationController : Controller
}));
}
var principal = await _signInManager.CreateUserPrincipalAsync(user);
// Create the claims-based identity that will be used by OpenIddict to generate tokens.
var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)
.AddClaim(Claims.Subject, await _userManager.GetUserIdAsync(user))
.AddClaim(Claims.Email, await _userManager.GetEmailAsync(user))
.AddClaim(Claims.Name, await _userManager.GetUserNameAsync(user))
.AddClaims(Claims.Role, (await _userManager.GetRolesAsync(user)).ToImmutableArray());
// Note: in this sample, the granted scopes match the requested scope
// but you may want to allow the user to uncheck specific scopes.
// For that, simply restrict the list of scopes before calling SetScopes.
principal.SetScopes(request.GetScopes());
principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync());
identity.SetScopes(request.GetScopes());
identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync());
// Automatically create a permanent authorization to avoid requiring explicit consent
// for future authorization or token requests containing the same scopes.
@ -270,18 +281,18 @@ public class AuthorizationController : Controller
if (authorization is null)
{
authorization = await _authorizationManager.CreateAsync(
principal: principal,
principal: new ClaimsPrincipal(identity),
subject : await _userManager.GetUserIdAsync(user),
client : await _applicationManager.GetIdAsync(application),
type : AuthorizationTypes.Permanent,
scopes : principal.GetScopes());
scopes : identity.GetScopes());
}
principal.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization));
principal.SetDestinations(GetDestinations);
identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization));
identity.SetDestinations(GetDestinations);
// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
[Authorize, FormValueRequired("submit.Deny")]
@ -343,14 +354,19 @@ public class AuthorizationController : Controller
var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
if (result.Succeeded)
{
var principal = await _signInManager.CreateUserPrincipalAsync(user);
// Create the claims-based identity that will be used by OpenIddict to generate tokens.
var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)
.AddClaim(Claims.Subject, await _userManager.GetUserIdAsync(user))
.AddClaim(Claims.Email, await _userManager.GetEmailAsync(user))
.AddClaim(Claims.Name, await _userManager.GetUserNameAsync(user))
.AddClaims(Claims.Role, (await _userManager.GetRolesAsync(user)).ToImmutableArray());
// Note: in this sample, the granted scopes match the requested scope
// but you may want to allow the user to uncheck specific scopes.
// For that, simply restrict the list of scopes before calling SetScopes.
principal.SetScopes(result.Principal.GetScopes());
principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync());
principal.SetDestinations(GetDestinations);
identity.SetScopes(result.Principal.GetScopes());
identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync());
identity.SetDestinations(GetDestinations);
var properties = new AuthenticationProperties
{
@ -359,7 +375,7 @@ public class AuthorizationController : Controller
RedirectUri = "/"
};
return SignIn(principal, properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
return SignIn(new ClaimsPrincipal(identity), properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
// Redisplay the form when the user code is not valid.
@ -447,17 +463,22 @@ public class AuthorizationController : Controller
}));
}
var principal = await _signInManager.CreateUserPrincipalAsync(user);
// Create the claims-based identity that will be used by OpenIddict to generate tokens.
var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)
.AddClaim(Claims.Subject, await _userManager.GetUserIdAsync(user))
.AddClaim(Claims.Email, await _userManager.GetEmailAsync(user))
.AddClaim(Claims.Name, await _userManager.GetUserNameAsync(user))
.AddClaims(Claims.Role, (await _userManager.GetRolesAsync(user)).ToImmutableArray());
// Note: in this sample, the granted scopes match the requested scope
// but you may want to allow the user to uncheck specific scopes.
// For that, simply restrict the list of scopes before calling SetScopes.
principal.SetScopes(request.GetScopes());
principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync());
principal.SetDestinations(GetDestinations);
identity.SetScopes(request.GetScopes());
identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync());
identity.SetDestinations(GetDestinations);
// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
else if (request.IsAuthorizationCodeGrantType() || request.IsDeviceCodeGrantType() || request.IsRefreshTokenGrantType())
@ -466,11 +487,7 @@ public class AuthorizationController : Controller
var principal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal;
// Retrieve the user profile corresponding to the authorization code/refresh token.
// Note: if you want to automatically invalidate the authorization code/refresh token
// when the user password/roles change, use the following line instead:
//
// var user = await _signInManager.ValidateSecurityStampAsync(info.Principal);
var user = await _userManager.GetUserAsync(principal);
var user = await _userManager.FindByIdAsync(principal.GetClaim(Claims.Subject));
if (user is null)
{
return Forbid(

4
sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/ResourceController.cs

@ -2,8 +2,8 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using OpenIddict.Sandbox.AspNetCore.Server.Models;
using OpenIddict.Abstractions;
using OpenIddict.Sandbox.AspNetCore.Server.Models;
using OpenIddict.Validation.AspNetCore;
using static OpenIddict.Abstractions.OpenIddictConstants;
@ -37,7 +37,7 @@ public class ResourceController : Controller
}));
}
var user = await _userManager.GetUserAsync(User);
var user = await _userManager.FindByIdAsync(User.GetClaim(Claims.Subject));
if (user is null)
{
return Challenge(

2
sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/UserinfoController.cs

@ -21,7 +21,7 @@ public class UserinfoController : Controller
[IgnoreAntiforgeryToken, Produces("application/json")]
public async Task<IActionResult> Userinfo()
{
var user = await _userManager.GetUserAsync(User);
var user = await _userManager.FindByIdAsync(User.GetClaim(Claims.Subject));
if (user is null)
{
return Challenge(

11
sandbox/OpenIddict.Sandbox.AspNetCore.Server/Startup.cs

@ -34,17 +34,6 @@ public class Startup
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Configure Identity to use the same JWT claims as OpenIddict instead
// of the legacy WS-Federation claims it uses by default (ClaimTypes),
// which saves you from doing the mapping in your authorization controller.
services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserNameClaimType = Claims.Name;
options.ClaimsIdentity.UserIdClaimType = Claims.Subject;
options.ClaimsIdentity.RoleClaimType = Claims.Role;
options.ClaimsIdentity.EmailClaimType = Claims.Email;
});
// OpenIddict offers native integration with Quartz.NET to perform scheduled tasks
// (like pruning orphaned authorizations/tokens from the database) at regular intervals.
services.AddQuartz(options =>

Loading…
Cancel
Save