Browse Source
Minimal ASP.NET Core server for testing CIMD support. Seeds a pre-registered public client (test-client) and test user, supports authorization code + PKCE and password grant flows. This serves as the baseline to verify token issuance before adding CIMD handling.pull/2416/head
9 changed files with 519 additions and 0 deletions
@ -0,0 +1,104 @@ |
|||
# CLAUDE.md |
|||
|
|||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
|||
|
|||
## Project Overview |
|||
|
|||
OpenIddict is an open-source framework for building OAuth 2.0/OpenID Connect servers and clients in .NET. It provides a modular architecture with pluggable stores (EF Core, EF 6.x, MongoDB) and host integrations (ASP.NET Core, OWIN). |
|||
|
|||
## Build Commands |
|||
|
|||
The project uses Microsoft.DotNet.Arcade.Sdk with .NET SDK 10.0. |
|||
|
|||
```bash |
|||
# Build (Windows) |
|||
Build.cmd |
|||
|
|||
# Build (Unix/macOS) |
|||
./build.sh |
|||
|
|||
# Full CI build with tests |
|||
# Windows: |
|||
eng\common\Build.cmd -restore -build -test |
|||
# Unix: |
|||
./eng/common/build.sh -restore -build -test |
|||
|
|||
# Build specific project |
|||
dotnet build src/OpenIddict.Core/OpenIddict.Core.csproj |
|||
|
|||
# Run all tests |
|||
dotnet test |
|||
|
|||
# Run a single test project |
|||
dotnet test test/OpenIddict.Core.Tests/OpenIddict.Core.Tests.csproj |
|||
|
|||
# Run a single test by name |
|||
dotnet test test/OpenIddict.Core.Tests/OpenIddict.Core.Tests.csproj --filter "FullyQualifiedName~MyTestMethod" |
|||
|
|||
# Full CI build (restore, build, test, sign, pack) |
|||
eng\common\Build.cmd -configuration Release -ci -prepareMachine -restore -build -test -sign -pack |
|||
``` |
|||
|
|||
## Multi-Targeting |
|||
|
|||
Projects target many frameworks simultaneously: net462, net472, net48, netstandard2.0, netstandard2.1, net8.0, net9.0, net10.0, plus mobile platforms (Android, iOS, macOS, Mac Catalyst, Windows) when workloads are available. |
|||
|
|||
Conditional compilation uses `SUPPORTS_*` defines (e.g., `SUPPORTS_KEY_DERIVATION_WITH_SPECIFIED_HASH_ALGORITHM`, `SUPPORTS_CERTIFICATE_LOADER`) set in `Directory.Build.targets`. Use `#if` directives for platform-specific code paths. |
|||
|
|||
## Architecture |
|||
|
|||
### Core Abstraction Layers |
|||
|
|||
1. **OpenIddict.Abstractions** — Interfaces, constants, descriptors, store contracts, and primitive types. Everything depends on this. |
|||
2. **OpenIddict.Core** — Generic managers (`OpenIddictApplicationManager<T>`, `OpenIddictAuthorizationManager<T>`, `OpenIddictScopeManager<T>`, `OpenIddictTokenManager<T>`) and caches. Managers coordinate stores, caches, validation, and business logic. |
|||
3. **OpenIddict.Server / .Client / .Validation** — Protocol implementations using an event-driven handler/filter/dispatcher pattern. |
|||
|
|||
### Store Pattern |
|||
|
|||
Store interfaces (`IOpenIddictApplicationStore<T>`, etc.) abstract persistence. Implementations: |
|||
- `OpenIddict.EntityFrameworkCore` — EF Core stores |
|||
- `OpenIddict.EntityFramework` — EF 6.x stores |
|||
- `OpenIddict.MongoDb` — MongoDB stores |
|||
- Corresponding `.Models` projects hold entity classes |
|||
|
|||
### Host Integration Pattern |
|||
|
|||
Each protocol component (Server, Client, Validation) has host-specific projects: |
|||
- `.AspNetCore` — ASP.NET Core middleware integration |
|||
- `.Owin` — OWIN/Katana integration for ASP.NET 4.x |
|||
- `.DataProtection` — ASP.NET Core Data Protection token formats |
|||
|
|||
### Handler/Filter Pattern |
|||
|
|||
Request processing uses event handlers guarded by filters. Handlers are registered via `IOpenIddictServerHandlers`, and filters (e.g., `RequireAccessTokenGenerated`, `RequireClientIdParameter`) control execution flow. |
|||
|
|||
### DI Registration |
|||
|
|||
Builder pattern via `OpenIddictBuilder` with extension methods in `Microsoft.Extensions.DependencyInjection` namespace. Uses `TryAddScoped`/`TryAddSingleton` for idempotent registration. |
|||
|
|||
## Code Conventions |
|||
|
|||
- C# 14, nullable reference types enabled, implicit usings enabled (`Directory.Build.props`) |
|||
- `TreatWarningsAsErrors: true` |
|||
- File-scoped namespaces throughout |
|||
- `ValueTask` for async manager methods; `CancellationToken` on all async APIs |
|||
- `SR.GetResourceString()` for localized exception messages (resource strings in Abstractions) |
|||
- Strong-name signed assemblies (key at `eng/key.snk`) |
|||
- CRLF line endings, 4-space indentation (enforced via `.editorconfig`) |
|||
|
|||
## Testing |
|||
|
|||
- xUnit with Moq |
|||
- Test projects mirror source projects under `/test/` (e.g., `OpenIddict.Core.Tests`) |
|||
- Integration tests in `*.IntegrationTests` projects |
|||
- `[Fact]` for deterministic tests, `[Theory]` + `[InlineData]` for parameterized tests |
|||
- CI runs tests on Windows, Ubuntu, and macOS |
|||
|
|||
## Key Directories |
|||
|
|||
- `/eng/` — Arcade build infrastructure, signing key |
|||
- `/gen/` — Code generators (Client WebIntegration provider generator) |
|||
- `/src/` — Source libraries (~30 projects) |
|||
- `/test/` — Test projects (~18 projects) |
|||
- `/sandbox/` — Sample/reference applications |
|||
- `/shared/OpenIddict.Extensions/` — Shared helper utilities and polyfills |
|||
@ -0,0 +1,219 @@ |
|||
using System.Security.Claims; |
|||
using Microsoft.AspNetCore; |
|||
using Microsoft.AspNetCore.Authentication; |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.IdentityModel.Tokens; |
|||
using OpenIddict.Abstractions; |
|||
using OpenIddict.Sandbox.AspNetCore.CimdServer.Models; |
|||
using OpenIddict.Server.AspNetCore; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
|
|||
namespace OpenIddict.Sandbox.AspNetCore.CimdServer; |
|||
|
|||
public class AuthorizationController : Controller |
|||
{ |
|||
private readonly IOpenIddictApplicationManager _applicationManager; |
|||
private readonly IOpenIddictAuthorizationManager _authorizationManager; |
|||
private readonly IOpenIddictScopeManager _scopeManager; |
|||
private readonly SignInManager<ApplicationUser> _signInManager; |
|||
private readonly UserManager<ApplicationUser> _userManager; |
|||
|
|||
public AuthorizationController( |
|||
IOpenIddictApplicationManager applicationManager, |
|||
IOpenIddictAuthorizationManager authorizationManager, |
|||
IOpenIddictScopeManager scopeManager, |
|||
SignInManager<ApplicationUser> signInManager, |
|||
UserManager<ApplicationUser> userManager) |
|||
{ |
|||
_applicationManager = applicationManager; |
|||
_authorizationManager = authorizationManager; |
|||
_scopeManager = scopeManager; |
|||
_signInManager = signInManager; |
|||
_userManager = userManager; |
|||
} |
|||
|
|||
[HttpGet("~/connect/authorize")] |
|||
[HttpPost("~/connect/authorize")] |
|||
[IgnoreAntiforgeryToken] |
|||
public async Task<IActionResult> Authorize() |
|||
{ |
|||
var request = HttpContext.GetOpenIddictServerRequest() ?? |
|||
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); |
|||
|
|||
// Try to retrieve the user principal stored in the authentication cookie.
|
|||
var result = await HttpContext.AuthenticateAsync(); |
|||
if (result is not { Succeeded: true }) |
|||
{ |
|||
// If the user is not logged in, redirect to login.
|
|||
// For this demo, we auto-sign in the test user to simplify testing.
|
|||
var user = await _userManager.FindByNameAsync("testuser"); |
|||
if (user is not null) |
|||
{ |
|||
await _signInManager.SignInAsync(user, isPersistent: false); |
|||
return Challenge(); |
|||
} |
|||
|
|||
return Forbid( |
|||
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, |
|||
properties: new AuthenticationProperties(new Dictionary<string, string?> |
|||
{ |
|||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired, |
|||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is not logged in." |
|||
})); |
|||
} |
|||
|
|||
var userEntity = await _userManager.GetUserAsync(result.Principal) ?? |
|||
throw new InvalidOperationException("The user details cannot be retrieved."); |
|||
|
|||
var application = await _applicationManager.FindByClientIdAsync(request.ClientId!) ?? |
|||
throw new InvalidOperationException("Details concerning the calling client application cannot be found."); |
|||
|
|||
// Auto-approve consent for this demonstrator.
|
|||
var identity = new ClaimsIdentity( |
|||
authenticationType: TokenValidationParameters.DefaultAuthenticationType, |
|||
nameType: Claims.Name, |
|||
roleType: Claims.Role); |
|||
|
|||
identity.SetClaim(Claims.Subject, await _userManager.GetUserIdAsync(userEntity)) |
|||
.SetClaim(Claims.Email, await _userManager.GetEmailAsync(userEntity)) |
|||
.SetClaim(Claims.Name, await _userManager.GetUserNameAsync(userEntity)); |
|||
|
|||
identity.SetScopes(request.GetScopes()); |
|||
identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync()); |
|||
|
|||
var authorization = await _authorizationManager.CreateAsync( |
|||
identity: identity, |
|||
subject: await _userManager.GetUserIdAsync(userEntity), |
|||
client: (await _applicationManager.GetIdAsync(application))!, |
|||
type: AuthorizationTypes.Permanent, |
|||
scopes: identity.GetScopes()); |
|||
|
|||
identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); |
|||
identity.SetDestinations(GetDestinations); |
|||
|
|||
return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
|
|||
[HttpPost("~/connect/token")] |
|||
[IgnoreAntiforgeryToken] |
|||
[Produces("application/json")] |
|||
public async Task<IActionResult> Exchange() |
|||
{ |
|||
var request = HttpContext.GetOpenIddictServerRequest() ?? |
|||
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); |
|||
|
|||
if (request.IsPasswordGrantType()) |
|||
{ |
|||
var user = await _userManager.FindByNameAsync(request.Username!); |
|||
if (user is null) |
|||
{ |
|||
return Forbid( |
|||
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, |
|||
properties: new AuthenticationProperties(new Dictionary<string, string?> |
|||
{ |
|||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant, |
|||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The username/password couple is invalid." |
|||
})); |
|||
} |
|||
|
|||
var passwordResult = await _signInManager.CheckPasswordSignInAsync(user, request.Password!, lockoutOnFailure: false); |
|||
if (!passwordResult.Succeeded) |
|||
{ |
|||
return Forbid( |
|||
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, |
|||
properties: new AuthenticationProperties(new Dictionary<string, string?> |
|||
{ |
|||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant, |
|||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The username/password couple is invalid." |
|||
})); |
|||
} |
|||
|
|||
var identity = new ClaimsIdentity( |
|||
authenticationType: TokenValidationParameters.DefaultAuthenticationType, |
|||
nameType: Claims.Name, |
|||
roleType: Claims.Role); |
|||
|
|||
identity.SetClaim(Claims.Subject, await _userManager.GetUserIdAsync(user)) |
|||
.SetClaim(Claims.Email, await _userManager.GetEmailAsync(user)) |
|||
.SetClaim(Claims.Name, await _userManager.GetUserNameAsync(user)); |
|||
|
|||
identity.SetScopes(request.GetScopes()); |
|||
identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync()); |
|||
identity.SetDestinations(GetDestinations); |
|||
|
|||
return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
|
|||
if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType()) |
|||
{ |
|||
var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
|
|||
var user = await _userManager.FindByIdAsync(result.Principal!.GetClaim(Claims.Subject)!); |
|||
if (user is null) |
|||
{ |
|||
return Forbid( |
|||
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, |
|||
properties: new AuthenticationProperties(new Dictionary<string, string?> |
|||
{ |
|||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant, |
|||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The token is no longer valid." |
|||
})); |
|||
} |
|||
|
|||
if (!await _signInManager.CanSignInAsync(user)) |
|||
{ |
|||
return Forbid( |
|||
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, |
|||
properties: new AuthenticationProperties(new Dictionary<string, string?> |
|||
{ |
|||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant, |
|||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in." |
|||
})); |
|||
} |
|||
|
|||
var identity = new ClaimsIdentity(result.Principal!.Claims, |
|||
authenticationType: TokenValidationParameters.DefaultAuthenticationType, |
|||
nameType: Claims.Name, |
|||
roleType: Claims.Role); |
|||
|
|||
identity.SetClaim(Claims.Subject, await _userManager.GetUserIdAsync(user)) |
|||
.SetClaim(Claims.Email, await _userManager.GetEmailAsync(user)) |
|||
.SetClaim(Claims.Name, await _userManager.GetUserNameAsync(user)); |
|||
|
|||
identity.SetDestinations(GetDestinations); |
|||
|
|||
return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
|
|||
throw new InvalidOperationException("The specified grant type is not supported."); |
|||
} |
|||
|
|||
private static IEnumerable<string> GetDestinations(Claim claim) |
|||
{ |
|||
switch (claim.Type) |
|||
{ |
|||
case Claims.Name: |
|||
yield return Destinations.AccessToken; |
|||
|
|||
if (claim.Subject!.HasScope(Scopes.Profile)) |
|||
yield return Destinations.IdentityToken; |
|||
|
|||
yield break; |
|||
|
|||
case Claims.Email: |
|||
yield return Destinations.AccessToken; |
|||
|
|||
if (claim.Subject!.HasScope(Scopes.Email)) |
|||
yield return Destinations.IdentityToken; |
|||
|
|||
yield break; |
|||
|
|||
case "AspNet.Identity.SecurityStamp": yield break; |
|||
|
|||
default: |
|||
yield return Destinations.AccessToken; |
|||
yield break; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; |
|||
using Microsoft.EntityFrameworkCore; |
|||
|
|||
namespace OpenIddict.Sandbox.AspNetCore.CimdServer.Models; |
|||
|
|||
public class ApplicationDbContext : IdentityDbContext<ApplicationUser> |
|||
{ |
|||
public ApplicationDbContext(DbContextOptions options) |
|||
: base(options) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
using Microsoft.AspNetCore.Identity; |
|||
|
|||
namespace OpenIddict.Sandbox.AspNetCore.CimdServer.Models; |
|||
|
|||
public class ApplicationUser : IdentityUser { } |
|||
@ -0,0 +1,22 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Web"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFrameworks>net10.0</TargetFrameworks> |
|||
<TypeScriptEnabled>false</TypeScriptEnabled> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\OpenIddict.Core\OpenIddict.Core.csproj" /> |
|||
<ProjectReference Include="..\..\src\OpenIddict.Server.AspNetCore\OpenIddict.Server.AspNetCore.csproj" /> |
|||
<ProjectReference Include="..\..\src\OpenIddict.Server.DataProtection\OpenIddict.Server.DataProtection.csproj" /> |
|||
<ProjectReference Include="..\..\src\OpenIddict.Validation.AspNetCore\OpenIddict.Validation.AspNetCore.csproj" /> |
|||
<ProjectReference Include="..\..\src\OpenIddict.Validation.ServerIntegration\OpenIddict.Validation.ServerIntegration.csproj" /> |
|||
<ProjectReference Include="..\..\src\OpenIddict.EntityFrameworkCore\OpenIddict.EntityFrameworkCore.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" /> |
|||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,64 @@ |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using OpenIddict.Sandbox.AspNetCore.CimdServer; |
|||
using OpenIddict.Sandbox.AspNetCore.CimdServer.Models; |
|||
|
|||
var builder = WebApplication.CreateBuilder(args); |
|||
|
|||
builder.WebHost.UseUrls("https://localhost:7295"); |
|||
|
|||
builder.Services.AddControllers(); |
|||
|
|||
builder.Services.AddDbContext<ApplicationDbContext>(options => |
|||
{ |
|||
options.UseSqlite($"Filename={Path.Combine(Path.GetTempPath(), "openiddict-sandbox-aspnetcore-cimdserver.sqlite3")}"); |
|||
options.UseOpenIddict(); |
|||
}); |
|||
|
|||
builder.Services.AddIdentity<ApplicationUser, IdentityRole>() |
|||
.AddEntityFrameworkStores<ApplicationDbContext>() |
|||
.AddDefaultTokenProviders(); |
|||
|
|||
builder.Services.AddOpenIddict() |
|||
.AddCore(options => |
|||
{ |
|||
options.UseEntityFrameworkCore() |
|||
.UseDbContext<ApplicationDbContext>(); |
|||
}) |
|||
.AddServer(options => |
|||
{ |
|||
options.SetAuthorizationEndpointUris("connect/authorize") |
|||
.SetTokenEndpointUris("connect/token"); |
|||
|
|||
options.AllowAuthorizationCodeFlow() |
|||
.AllowPasswordFlow() |
|||
.AllowRefreshTokenFlow(); |
|||
|
|||
options.RequireProofKeyForCodeExchange(); |
|||
|
|||
options.RegisterScopes("openid", "profile", "email"); |
|||
|
|||
options.AddDevelopmentEncryptionCertificate() |
|||
.AddDevelopmentSigningCertificate(); |
|||
|
|||
options.UseAspNetCore() |
|||
.EnableAuthorizationEndpointPassthrough() |
|||
.EnableTokenEndpointPassthrough(); |
|||
}) |
|||
.AddValidation(options => |
|||
{ |
|||
options.UseLocalServer(); |
|||
options.UseAspNetCore(); |
|||
}); |
|||
|
|||
builder.Services.AddHostedService<Worker>(); |
|||
|
|||
var app = builder.Build(); |
|||
|
|||
app.UseDeveloperExceptionPage(); |
|||
app.UseRouting(); |
|||
app.UseAuthentication(); |
|||
app.UseAuthorization(); |
|||
app.MapControllers(); |
|||
|
|||
app.Run(); |
|||
@ -0,0 +1,12 @@ |
|||
{ |
|||
"profiles": { |
|||
"OpenIddict.Sandbox.AspNetCore.CimdServer": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "https://localhost:53769;http://localhost:53770" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
using Microsoft.AspNetCore.Identity; |
|||
using OpenIddict.Abstractions; |
|||
using OpenIddict.Sandbox.AspNetCore.CimdServer.Models; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
|
|||
namespace OpenIddict.Sandbox.AspNetCore.CimdServer; |
|||
|
|||
public class Worker : IHostedService |
|||
{ |
|||
private readonly IServiceProvider _provider; |
|||
|
|||
public Worker(IServiceProvider provider) |
|||
=> _provider = provider; |
|||
|
|||
public async Task StartAsync(CancellationToken cancellationToken) |
|||
{ |
|||
await using var scope = _provider.CreateAsyncScope(); |
|||
|
|||
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); |
|||
await context.Database.EnsureCreatedAsync(cancellationToken); |
|||
|
|||
await SeedUsersAsync(scope.ServiceProvider); |
|||
await SeedClientsAsync(scope.ServiceProvider); |
|||
} |
|||
|
|||
private static async Task SeedUsersAsync(IServiceProvider provider) |
|||
{ |
|||
var userManager = provider.GetRequiredService<UserManager<ApplicationUser>>(); |
|||
|
|||
if (await userManager.FindByNameAsync("testuser") is null) |
|||
{ |
|||
var user = new ApplicationUser |
|||
{ |
|||
UserName = "testuser", |
|||
Email = "testuser@example.com" |
|||
}; |
|||
|
|||
await userManager.CreateAsync(user, "Pass123$"); |
|||
} |
|||
} |
|||
|
|||
private static async Task SeedClientsAsync(IServiceProvider provider) |
|||
{ |
|||
var manager = provider.GetRequiredService<IOpenIddictApplicationManager>(); |
|||
|
|||
// Pre-registered test client for baseline verification.
|
|||
if (await manager.FindByClientIdAsync("test-client") is null) |
|||
{ |
|||
await manager.CreateAsync(new OpenIddictApplicationDescriptor |
|||
{ |
|||
ApplicationType = ApplicationTypes.Native, |
|||
ClientId = "test-client", |
|||
ClientType = ClientTypes.Public, |
|||
ConsentType = ConsentTypes.Systematic, |
|||
DisplayName = "Test client (pre-registered)", |
|||
RedirectUris = |
|||
{ |
|||
new Uri("http://localhost/callback") |
|||
}, |
|||
Permissions = |
|||
{ |
|||
Permissions.Endpoints.Authorization, |
|||
Permissions.Endpoints.Token, |
|||
Permissions.GrantTypes.AuthorizationCode, |
|||
Permissions.GrantTypes.Password, |
|||
Permissions.GrantTypes.RefreshToken, |
|||
Permissions.ResponseTypes.Code, |
|||
Permissions.Scopes.Email, |
|||
Permissions.Scopes.Profile |
|||
}, |
|||
Requirements = |
|||
{ |
|||
Requirements.Features.ProofKeyForCodeExchange |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; |
|||
} |
|||
Loading…
Reference in new issue