Skip to content

Commit 031507a

Browse files
committed
feat: DEVPLAN 1.13.10 add OpenAPI document generation via CoreIdent.OpenApi package
Create new CoreIdent.OpenApi project with .NET 10 Microsoft.AspNetCore.OpenApi integration; add service registration and endpoint mapping extensions; configure security schemes, request/response examples, and XML documentation; add OpenAPI validation gate to CI workflow; update Developer_Guide.md and README_Detailed.md with setup instructions and optional Scalar UI integration; complete DEVPLAN 1.13.10 checklist items.
1 parent 16d099d commit 031507a

18 files changed

Lines changed: 976 additions & 31 deletions

.github/workflows/dotnet.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ jobs:
4040
- name: Test (with coverage)
4141
run: dotnet test CoreIdent.sln -c Release --no-build --collect:"XPlat Code Coverage" --logger "console;verbosity=minimal"
4242

43+
- name: OpenAPI Validation Gate
44+
run: dotnet test tests/CoreIdent.Integration.Tests/CoreIdent.Integration.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~CoreIdent.Integration.Tests.Infrastructure.OpenApiFixtureTests" --logger "console;verbosity=minimal"
45+
4346
- name: Coverage Gate (CoreIdent.Core >= 90%)
4447
run: |
4548
python3 - <<'PY'

CoreIdent.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72
77
EndProject
88
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoreIdent.Core", "src\CoreIdent.Core\CoreIdent.Core.csproj", "{13763257-46A9-43D3-B447-01E9B886AB99}"
99
EndProject
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoreIdent.OpenApi", "src\CoreIdent.OpenApi\CoreIdent.OpenApi.csproj", "{7C1E8B5E-37B3-4E71-9A5F-8E2B8D9AD8B2}"
11+
EndProject
1012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoreIdent.Passkeys", "src\CoreIdent.Passkeys\CoreIdent.Passkeys.csproj", "{7DAD0A0D-9B1B-4C2B-A758-9E5D5C1C4B8A}"
1113
EndProject
1214
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoreIdent.Passkeys.AspNetIdentity", "src\CoreIdent.Passkeys.AspNetIdentity\CoreIdent.Passkeys.AspNetIdentity.csproj", "{A8E0F5F1-86B0-4E6E-9B8D-0C2C15FA1C9D}"
@@ -59,6 +61,18 @@ Global
5961
{13763257-46A9-43D3-B447-01E9B886AB99}.Release|x64.Build.0 = Release|Any CPU
6062
{13763257-46A9-43D3-B447-01E9B886AB99}.Release|x86.ActiveCfg = Release|Any CPU
6163
{13763257-46A9-43D3-B447-01E9B886AB99}.Release|x86.Build.0 = Release|Any CPU
64+
{7C1E8B5E-37B3-4E71-9A5F-8E2B8D9AD8B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
65+
{7C1E8B5E-37B3-4E71-9A5F-8E2B8D9AD8B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
66+
{7C1E8B5E-37B3-4E71-9A5F-8E2B8D9AD8B2}.Debug|x64.ActiveCfg = Debug|Any CPU
67+
{7C1E8B5E-37B3-4E71-9A5F-8E2B8D9AD8B2}.Debug|x64.Build.0 = Debug|Any CPU
68+
{7C1E8B5E-37B3-4E71-9A5F-8E2B8D9AD8B2}.Debug|x86.ActiveCfg = Debug|Any CPU
69+
{7C1E8B5E-37B3-4E71-9A5F-8E2B8D9AD8B2}.Debug|x86.Build.0 = Debug|Any CPU
70+
{7C1E8B5E-37B3-4E71-9A5F-8E2B8D9AD8B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
71+
{7C1E8B5E-37B3-4E71-9A5F-8E2B8D9AD8B2}.Release|Any CPU.Build.0 = Release|Any CPU
72+
{7C1E8B5E-37B3-4E71-9A5F-8E2B8D9AD8B2}.Release|x64.ActiveCfg = Release|Any CPU
73+
{7C1E8B5E-37B3-4E71-9A5F-8E2B8D9AD8B2}.Release|x64.Build.0 = Release|Any CPU
74+
{7C1E8B5E-37B3-4E71-9A5F-8E2B8D9AD8B2}.Release|x86.ActiveCfg = Release|Any CPU
75+
{7C1E8B5E-37B3-4E71-9A5F-8E2B8D9AD8B2}.Release|x86.Build.0 = Release|Any CPU
6276
{7DAD0A0D-9B1B-4C2B-A758-9E5D5C1C4B8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
6377
{7DAD0A0D-9B1B-4C2B-A758-9E5D5C1C4B8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
6478
{7DAD0A0D-9B1B-4C2B-A758-9E5D5C1C4B8A}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -247,5 +261,6 @@ Global
247261
{CE3F11D3-060F-4F4C-8A40-5A67AFDAD1D0} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
248262
{D5A2C0E8-2B2F-4A54-9EA1-6E5D3C59D6E7} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
249263
{A90B9D37-39B3-4AE4-8AE1-52E5A02CB200} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
264+
{7C1E8B5E-37B3-4E71-9A5F-8E2B8D9AD8B2} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
250265
EndGlobalSection
251266
EndGlobal

docs/DEVPLAN.md

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1362,10 +1362,10 @@ This document provides a detailed breakdown of tasks, components, test cases, an
13621362
---
13631363
13641364
* **Component:** OpenAPI Integration Package
1365-
- [ ] (L1) Create new project `CoreIdent.OpenApi` targeting `net10.0`
1366-
- [ ] (L1) Add dependency on `Microsoft.AspNetCore.OpenApi` for .NET 10 OpenAPI support
1367-
- [ ] (L1) Add dependency on `CoreIdent.Core` for access to endpoint models
1368-
- [ ] (L2) Design OpenAPI configuration options:
1365+
- [x] (L1) Create new project `CoreIdent.OpenApi` targeting `net10.0`
1366+
- [x] (L1) Add dependency on `Microsoft.AspNetCore.OpenApi` for .NET 10 OpenAPI support
1367+
- [x] (L1) Add dependency on `CoreIdent.Core` for access to endpoint models
1368+
- [x] (L2) Design OpenAPI configuration options:
13691369
```csharp
13701370
public class CoreIdentOpenApiOptions
13711371
{
@@ -1376,46 +1376,46 @@ This document provides a detailed breakdown of tasks, components, test cases, an
13761376
public bool IncludeSecurityDefinitions { get; set; } = true;
13771377
}
13781378
```
1379-
- [ ] (L2) Create extension method `AddCoreIdentOpenApi()` for `IServiceCollection`
1380-
- [ ] (L2) Create extension method `MapCoreIdentOpenApi()` for `IEndpointRouteBuilder`
1381-
- [ ] (L2) Configure OpenAPI document to include:
1379+
- [x] (L2) Create extension method `AddCoreIdentOpenApi()` for `IServiceCollection`
1380+
- [x] (L2) Create extension method `MapCoreIdentOpenApi()` for `IEndpointRouteBuilder`
1381+
- [x] (L2) Configure OpenAPI document to include:
13821382
- All OAuth 2.0 and OIDC endpoints (token, authorize, userinfo, etc.)
13831383
- Passwordless endpoints (magic links, OTPs, WebAuthn)
13841384
- Token management endpoints
13851385
- Discovery endpoints (.well-known)
13861386
- JWKS endpoint
13871387
- Passkey endpoints (if enabled)
1388-
- [ ] (L2) Add proper security schemes:
1388+
- [x] (L2) Add proper security schemes:
13891389
- `client_secret_basic` for client authentication
13901390
- `client_secret_post` for client authentication
13911391
- `authorization_code` flow with PKCE
13921392
- `refresh_token` flow
13931393
- `Bearer` token authentication
1394-
- [ ] (L2) Add request/response examples for key endpoints
1395-
- [ ] (L2) Add descriptions from XML documentation to OpenAPI schemas
1394+
- [x] (L2) Add request/response examples for key endpoints
1395+
- [x] (L2) Add descriptions from XML documentation to OpenAPI schemas
13961396
13971397
* **Component:** Optional API Reference UI (Scalar)
1398-
- [ ] (L2) Do not ship a UI in CoreIdent packages (no Swashbuckle / no Swagger UI)
1399-
- [ ] (L2) Ensure the generated OpenAPI document is compatible with Scalar
1400-
- [ ] (L2) Document how a host app can add Scalar to serve the OpenAPI JSON (host-managed)
1398+
- [x] (L2) Do not ship a UI in CoreIdent packages (no Swashbuckle / no Swagger UI)
1399+
- [x] (L2) Ensure the generated OpenAPI document is compatible with Scalar
1400+
- [x] (L2) Document how a host app can add Scalar to serve the OpenAPI JSON (host-managed)
14011401
14021402
* **Documentation Updates:**
1403-
- [ ] (L2) Update `Developer_Guide.md` with OpenAPI setup instructions
1404-
- [ ] (L2) Add OpenAPI configuration examples to README_Detailed.md
1405-
- [ ] (L2) Document security scheme usage in API documentation
1406-
- [ ] (L2) Document optional Scalar integration (no UI implementation in CoreIdent)
1403+
- [x] (L2) Update `Developer_Guide.md` with OpenAPI setup instructions
1404+
- [x] (L2) Add OpenAPI configuration examples to README_Detailed.md
1405+
- [x] (L2) Document security scheme usage in API documentation
1406+
- [x] (L2) Document optional Scalar integration (no UI implementation in CoreIdent)
14071407
14081408
* **Test Cases:**
1409-
- [ ] (L2) OpenAPI document builds without warnings
1410-
- [ ] (L2) All public endpoints are included in OpenAPI document
1411-
- [ ] (L2) Security schemes are properly defined and usable
1412-
- [ ] (L2) Smoke test: `GET /openapi/v1.json` returns 200 with valid OpenAPI document
1409+
- [x] (L2) OpenAPI document builds without warnings
1410+
- [x] (L2) All public endpoints are included in OpenAPI document
1411+
- [x] (L2) Security schemes are properly defined and usable
1412+
- [x] (L2) Smoke test: `GET /openapi/v1.json` returns 200 with valid OpenAPI document
14131413
14141414
* **Quality Gates:**
1415-
- [ ] (L2) OpenAPI document passes validation (no schema errors)
1416-
- [ ] (L2) All examples in documentation are valid
1417-
- [ ] (L2) Security definitions match actual endpoint requirements
1418-
- [ ] (L2) CI build includes OpenAPI validation step
1415+
- [x] (L2) OpenAPI document passes validation (no schema errors)
1416+
- [x] (L2) All examples in documentation are valid
1417+
- [x] (L2) Security definitions match actual endpoint requirements
1418+
- [x] (L2) CI build includes OpenAPI validation step
14191419
14201420
---
14211421

docs/Developer_Guide.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,55 @@ app.MapCoreIdentEndpoints();
218218
app.Run();
219219
```
220220

221+
## 1.2 OpenAPI documentation (Feature 1.13.10)
222+
223+
CoreIdent supports OpenAPI document generation via the `CoreIdent.OpenApi` package (built on .NET 10's `Microsoft.AspNetCore.OpenApi`).
224+
225+
CoreIdent does **not** ship a built-in UI (no Swashbuckle / Swagger UI). Instead, the host app can optionally add a UI such as Scalar.
226+
227+
### 1.2.1 Add OpenAPI services
228+
229+
Add the package reference:
230+
231+
```xml
232+
<PackageReference Include="CoreIdent.OpenApi" Version="1.0.0" />
233+
```
234+
235+
Register OpenAPI services:
236+
237+
```csharp
238+
using CoreIdent.OpenApi.Extensions;
239+
240+
builder.Services.AddCoreIdentOpenApi(options =>
241+
{
242+
options.DocumentTitle = "My API";
243+
options.DocumentVersion = "v1";
244+
options.OpenApiRoute = "/openapi/v1.json";
245+
});
246+
```
247+
248+
### 1.2.2 Map the OpenAPI endpoint
249+
250+
```csharp
251+
using CoreIdent.OpenApi.Extensions;
252+
253+
app.MapCoreIdentOpenApi();
254+
```
255+
256+
This maps an endpoint equivalent to `MapOpenApi(...)` so that `GET /openapi/v1.json` returns the OpenAPI JSON document.
257+
258+
### 1.2.3 Optional UI: Scalar (host-managed)
259+
260+
To serve an interactive API reference UI, install `Scalar.AspNetCore` in your host app and map it alongside the OpenAPI JSON:
261+
262+
```csharp
263+
using Scalar.AspNetCore;
264+
265+
app.MapCoreIdentOpenApi();
266+
app.MapScalarApiReference();
267+
```
268+
269+
221270
### What you get
222271

223272
With defaults, CoreIdent maps:

docs/README_Detailed.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ This section is a placeholder for DEVPLAN 1.13.6.
7373
- **Aspire integration** — Health checks, distributed tracing, service defaults
7474
- **OpenTelemetry metrics**`System.Diagnostics.Metrics` instrumentation
7575
- **Templates**`coreident-api`, `coreident-server`, `coreident-api-fsharp`
76+
- **OpenAPI** — OpenAPI JSON document generation via `CoreIdent.OpenApi` (no built-in UI)
7677

7778
### Coming Next
7879

@@ -91,6 +92,51 @@ CoreIdent includes passwordless flows:
9192

9293
For the SMS OTP endpoint and configuration reference, see the Developer Guide section [4.8 Passwordless SMS OTP](Developer_Guide.md#48-passwordless-sms-otp-feature-13).
9394

95+
---
96+
97+
## OpenAPI documentation (Feature 1.13.10)
98+
99+
CoreIdent can generate an OpenAPI JSON document for all mapped endpoints.
100+
101+
- **Document generation**: `CoreIdent.OpenApi` (built on .NET 10 `Microsoft.AspNetCore.OpenApi`)
102+
- **UI**: host-managed (CoreIdent does not ship Swashbuckle / Swagger UI)
103+
104+
### Minimal setup
105+
106+
```csharp
107+
using CoreIdent.OpenApi.Extensions;
108+
109+
builder.Services.AddCoreIdentOpenApi(options =>
110+
{
111+
options.DocumentTitle = "CoreIdent API";
112+
options.DocumentVersion = "v1";
113+
options.OpenApiRoute = "/openapi/v1.json";
114+
});
115+
116+
var app = builder.Build();
117+
118+
app.MapCoreIdentEndpoints();
119+
app.MapCoreIdentOpenApi();
120+
```
121+
122+
### Optional UI (Scalar)
123+
124+
In the host app:
125+
126+
```bash
127+
dotnet add package Scalar.AspNetCore
128+
```
129+
130+
Then:
131+
132+
```csharp
133+
using Scalar.AspNetCore;
134+
135+
app.MapCoreIdentOpenApi();
136+
app.MapScalarApiReference();
137+
```
138+
139+
94140
## Documentation
95141

96142
All planning and technical documentation is in the [`docs/`](./) folder:

src/CoreIdent.Core/Endpoints/TokenEndpointExtensions.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ public static IEndpointRouteBuilder MapCoreIdentTokenEndpoint(this IEndpointRout
5050
ArgumentNullException.ThrowIfNull(endpoints);
5151
ArgumentException.ThrowIfNullOrWhiteSpace(tokenPath);
5252

53-
endpoints.MapPost(tokenPath, HandleTokenRequest);
53+
endpoints
54+
.MapPost(tokenPath, HandleTokenRequest)
55+
.Produces<TokenResponse>(StatusCodes.Status200OK, "application/json")
56+
.Produces<TokenErrorResponse>(StatusCodes.Status400BadRequest, "application/json")
57+
.Produces<TokenErrorResponse>(StatusCodes.Status401Unauthorized, "application/json");
5458

5559
return endpoints;
5660
}

src/CoreIdent.Core/Endpoints/TokenManagementEndpointsExtensions.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ public static IEndpointRouteBuilder MapCoreIdentTokenManagementEndpoints(this IE
6060

6161
ArgumentException.ThrowIfNullOrWhiteSpace(introspectPath);
6262

63-
endpoints.MapPost(revokePath, async (
63+
endpoints
64+
.MapPost(revokePath, async (
6465
HttpRequest request,
6566
ISigningKeyProvider signingKeyProvider,
6667
ITokenRevocationStore tokenRevocationStore,
@@ -171,10 +172,15 @@ public static IEndpointRouteBuilder MapCoreIdentTokenManagementEndpoints(this IE
171172
logger.LogError(ex, "Error processing token revocation request.");
172173
return Results.Json(new { error = "server_error", error_description = "An error occurred processing the request." }, statusCode: StatusCodes.Status500InternalServerError);
173174
}
174-
});
175+
})
176+
.Produces(StatusCodes.Status200OK)
177+
.Produces<TokenErrorResponse>(StatusCodes.Status400BadRequest, "application/json")
178+
.Produces<TokenErrorResponse>(StatusCodes.Status401Unauthorized, "application/json")
179+
.Produces<TokenErrorResponse>(StatusCodes.Status500InternalServerError, "application/json");
175180

176181
// Introspection endpoint (RFC 7662)
177-
endpoints.MapPost(introspectPath, async (
182+
endpoints
183+
.MapPost(introspectPath, async (
178184
HttpRequest request,
179185
ISigningKeyProvider signingKeyProvider,
180186
ITokenRevocationStore tokenRevocationStore,
@@ -270,7 +276,11 @@ public static IEndpointRouteBuilder MapCoreIdentTokenManagementEndpoints(this IE
270276

271277
// Unknown token
272278
return Results.Json(new TokenIntrospectionResponse { Active = false });
273-
});
279+
})
280+
.Produces<TokenIntrospectionResponse>(StatusCodes.Status200OK, "application/json")
281+
.Produces<TokenErrorResponse>(StatusCodes.Status400BadRequest, "application/json")
282+
.Produces<TokenErrorResponse>(StatusCodes.Status401Unauthorized, "application/json")
283+
.Produces<TokenErrorResponse>(StatusCodes.Status500InternalServerError, "application/json");
274284

275285
return endpoints;
276286
}

src/CoreIdent.Core/Endpoints/UserInfoEndpointExtensions.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ public static IEndpointRouteBuilder MapCoreIdentUserInfoEndpoint(this IEndpointR
4646
ArgumentNullException.ThrowIfNull(endpoints);
4747
ArgumentException.ThrowIfNullOrWhiteSpace(userInfoPath);
4848

49-
endpoints.MapGet(userInfoPath, HandleUserInfoAsync);
49+
endpoints
50+
.MapGet(userInfoPath, HandleUserInfoAsync)
51+
.Produces<UserInfoResponse>(StatusCodes.Status200OK, "application/json")
52+
.Produces(StatusCodes.Status401Unauthorized)
53+
.Produces(StatusCodes.Status403Forbidden);
5054

5155
return endpoints;
5256
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
namespace CoreIdent.OpenApi.Configuration;
2+
3+
/// <summary>
4+
/// Options for configuring CoreIdent OpenAPI document generation.
5+
/// </summary>
6+
public sealed class CoreIdentOpenApiOptions
7+
{
8+
/// <summary>
9+
/// The OpenAPI document title.
10+
/// </summary>
11+
public string DocumentTitle { get; set; } = "CoreIdent API";
12+
13+
/// <summary>
14+
/// The OpenAPI document version.
15+
/// </summary>
16+
public string DocumentVersion { get; set; } = "v1";
17+
18+
/// <summary>
19+
/// The HTTP route where the OpenAPI JSON document is served.
20+
/// </summary>
21+
public string OpenApiRoute { get; set; } = "/openapi/v1.json";
22+
23+
/// <summary>
24+
/// When <see langword="true"/>, include XML documentation (when available) in the generated OpenAPI document.
25+
/// </summary>
26+
public bool IncludeXmlComments { get; set; } = true;
27+
28+
/// <summary>
29+
/// When <see langword="true"/>, include OpenAPI security scheme definitions in the document.
30+
/// </summary>
31+
public bool IncludeSecurityDefinitions { get; set; } = true;
32+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<LangVersion>14</LangVersion>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.1" />
16+
</ItemGroup>
17+
18+
<ItemGroup>
19+
<ProjectReference Include="..\CoreIdent.Core\CoreIdent.Core.csproj" />
20+
</ItemGroup>
21+
22+
</Project>

0 commit comments

Comments
 (0)