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