This document provides a detailed breakdown of tasks, components, test cases, and technical guidance for CoreIdent. It aligns with the vision in Project_Overview.md and technical specifications in Technical_Plan.md.
Key priorities:
- Phase 0 (Foundation) is now first priority — asymmetric keys, revocation, introspection
- Passwordless authentication is Phase 1
- Test infrastructure overhaul is a dedicated effort
- Removed: Web3, LNURL, AI integrations
- Added: DPoP, RAR, SPIFFE/SPIRE (later phases)
Note: References to "creating" components mean implementing the feature within the current architecture.
Checklist Legend:
[x]— Complete[ ]— Not started[~]— Partial / needs revisit after prior feature is implemented
LX Levels (LLM capability required):
L1: Low stakes, low accuracy requirements — if wrong, easy to fix, doesn't break anything importantL2: Moderate stakes — should be correct, errors catchable in review/testingL3: High stakes, high accuracy requirements — must be correct, worth spending money to succeed
| Protocol / Feature | Phase | Feature | Status |
|---|---|---|---|
| .NET 10 Migration | 0 | 0.1 | ✅ Complete |
| Asymmetric Keys (RS256/ES256) | 0 | 0.2 | ✅ Complete |
| Client Store & Model | 0 | 0.3 | ✅ Complete |
| Scope & Core Models | 0 | 0.4 | ✅ Complete |
| Core Registration & Routing | 0 | 0.4.1 | ✅ Complete |
| OIDC Discovery Metadata | 0 | 0.4.2 | ✅ Complete |
| User Model & Stores | 0 | 0.4.3 | ✅ Complete |
| Token Issuance Endpoint | 0 | 0.5 | ✅ Complete |
| Token Revocation (RFC 7009) | 0 | 0.6 | ✅ Complete |
| Token Introspection (RFC 7662) | 0 | 0.7 | ✅ Complete |
| Test Infrastructure | 0 | 0.8 | ✅ Complete |
| OpenTelemetry Metrics | 0 | 0.9 | ✅ Complete |
| CLI Tool | 0 | 0.10 | ✅ Complete |
| Dev Container | 0 | 0.11 | ✅ Complete |
| Email Magic Link | 1 | 1.1 | ✅ Complete |
| Passkey/WebAuthn | 1 | 1.2 | ✅ Complete |
| SMS OTP | 1 | 1.3 | ✅ Complete |
| F# Compatibility | 1 | 1.4 | ✅ Complete |
dotnet new Templates |
1 | 1.5 | ✅ Complete |
| Aspire Integration | 1 | 1.6 | ✅ Complete |
| Authorization Code + PKCE | 1 | 1.7 | ✅ Complete |
| Consent & Grants | 1 | 1.8 | ✅ Complete |
| Delegated User Store | 1 | 1.9 | ✅ Complete |
| OIDC UserInfo Endpoint | 1 | 1.10 | ✅ Complete |
| Resource Owner Endpoints (Register/Login/Profile) | 1 | 1.11 | ✅ Complete |
| Password Grant (ROPC) | 1 | 1.12 | ✅ Complete |
| Follow-Up Cleanup | 1 | 1.13 | ✅ Complete |
| Google Provider | 2 | 2.2 | 🔲 Planned |
| Microsoft Provider | 2 | 2.3 | 🔲 Planned |
| GitHub Provider | 2 | 2.4 | 🔲 Planned |
| Key Rotation | 3 | 3.1 | 🔲 Planned |
| OIDC Logout | 3 | 3.2 | 🔲 Planned |
| Dynamic Client Registration | 3 | 3.3 | 🔲 Planned |
| Device Authorization Flow | 3 | 3.4 | 🔲 Planned |
| PAR (RFC 9126) | 3 | 3.5 | 🔲 Planned |
| DPoP (RFC 9449) | 3 | 3.6 | 🔲 Planned |
| RAR (RFC 9396) | 3 | 3.7 | 🔲 Planned |
| UI Package | 4 | 4.1 | 🔲 Planned |
| Admin API | 4 | 4.3 | 🔲 Planned |
| MFA Framework | 5 | 5.1 | 🔲 Planned |
| SCIM | 5 | 5.4 | 🔲 Planned |
| SPIFFE/SPIRE | 5 | 5.5 | 🔲 Planned |
| Verifiable Credentials | 5 | 5.10 | 🔲 Planned |
Goal: Establish production-ready cryptographic foundation, essential token lifecycle endpoints, and robust test infrastructure.
Estimated Duration: 3-4 weeks
Prerequisites: .NET 10 SDK installed
- Milestone 0A — Foundation & Crypto: Features 0.1–0.2 (project setup, asymmetric keys)
- Milestone 0B — Core Models & Stores: Features 0.3–0.4 (client, scope, user, refresh token infrastructure)
- Milestone 0C — Token Lifecycle Endpoints: Features 0.5–0.7 (token issuance, revocation, introspection)
- Milestone 0D — Quality & DevEx: Features 0.8–0.11 (testing, metrics, CLI, dev container)
- Component: Solution & Project Setup
- (L1) Create
CoreIdent.slnsolution file - (L1) Create
CoreIdent.Core.csprojtargetingnet10.0 - (L1) Create
CoreIdent.Storage.EntityFrameworkCore.csprojtargetingnet10.0 - (L1) Create
CoreIdent.Adapters.DelegatedUserStore.csprojtargetingnet10.0 - (L1) Create test projects targeting
net10.0 - (L2) Configure NuGet package references for .NET 10
Microsoft.AspNetCore.Authentication.JwtBearer→ 10.xMicrosoft.Extensions.Identity.Core→ 10.xMicrosoft.EntityFrameworkCore→ 10.xMicrosoft.IdentityModel.Tokens→ latest stable
- (L1) Create
- Component: C# 14 Features
- (L1) Enable C# 14 in all projects (
<LangVersion>14</LangVersion>) - (L2) Add
ClaimsPrincipalExtensionsusing extension members syntax
- (L1) Enable C# 14 in all projects (
- Test Case:
- (L1) Solution builds without warnings on .NET 10
- Documentation:
- (L1) Update README.md with .NET 10 requirement
- Component:
ISigningKeyProviderInterface- (L1) Create
CoreIdent.Core/Services/ISigningKeyProvider.cspublic interface ISigningKeyProvider { Task<SigningCredentials> GetSigningCredentialsAsync(CancellationToken ct = default); Task<IEnumerable<SecurityKeyInfo>> GetValidationKeysAsync(CancellationToken ct = default); string Algorithm { get; } } public record SecurityKeyInfo(string KeyId, SecurityKey Key, DateTime? ExpiresAt);
- (L1) Create
- Component:
CoreIdentKeyOptionsConfiguration- (L1) Create
CoreIdent.Core/Configuration/CoreIdentKeyOptions.cspublic class CoreIdentKeyOptions { public KeyType Type { get; set; } = KeyType.RSA; public int RsaKeySize { get; set; } = 2048; public string? PrivateKeyPem { get; set; } public string? PrivateKeyPath { get; set; } public string? CertificatePath { get; set; } public string? CertificatePassword { get; set; } } public enum KeyType { RSA, ECDSA, Symmetric }
- (L1) Create
- Component:
RsaSigningKeyProviderImplementation- (L3) Create
CoreIdent.Core/Services/RsaSigningKeyProvider.cs- Guidance: Load RSA key from PEM string, PEM file, or X509 certificate
- Guidance: Generate key on startup if none configured (dev mode only, log warning)
- Guidance: Support
kid(key ID) generation based on key thumbprint
- (L3) Create
- Component:
EcdsaSigningKeyProviderImplementation- (L3) Create
CoreIdent.Core/Services/EcdsaSigningKeyProvider.cs- Guidance: Support ES256 (P-256 curve)
- Guidance: Similar loading patterns as RSA
- (L3) Create
- Component:
SymmetricSigningKeyProviderImplementation (Legacy/Dev)- (L2) Create
CoreIdent.Core/Services/SymmetricSigningKeyProvider.cs- Guidance: Implement HS256 logic (for dev/testing only)
- Guidance: Log deprecation warning when used
- (L2) Create
- Component:
JwtTokenService- (L2) Create
JwtTokenServiceusingISigningKeyProvider - (L2) Use
SigningCredentialsfrom provider for all token generation - (L2) Include
kidclaim in JWT header
- (L2) Create
- Component: JWKS Endpoint
- (L2) Create
DiscoveryEndpointsExtensions.cswith JWKS endpoint usingISigningKeyProvider.GetValidationKeysAsync() - (L3) Return proper RSA key format (
kty: "RSA",n,e,kid,use: "sig",alg) - (L2) Support multiple keys in JWKS (for rotation)
- (L2) Create
- Component: DI Registration
- (L2) Add
AddSigningKey()extension method with overloads:.AddSigningKey(options => options.UseRsa(keyPath)) .AddSigningKey(options => options.UseRsaPem(pemString)) .AddSigningKey(options => options.UseEcdsa(keyPath)) .AddSigningKey(options => options.UseSymmetric(secret)) // Dev only
- (L2) Add
- Test Case (Unit):
- (L2)
RsaSigningKeyProviderloads key from PEM file correctly - (L2)
RsaSigningKeyProviderloads key from PEM string correctly - (L2)
RsaSigningKeyProvidergenerates key when none configured - (L2)
EcdsaSigningKeyProviderloads ES256 key correctly - (L1) Generated tokens include
kidin header - (L2) JWKS endpoint returns valid RSA public key structure
- (L2)
- Test Case (Integration):
- (L3) Token signed with RSA can be validated using JWKS public key
- (L3) Token signed with ECDSA can be validated using JWKS public key
- (L2) External JWT library can validate tokens using published JWKS
- Documentation:
- (L1) Update README.md with asymmetric key configuration examples
- (L2) Add security guidance for key management
- Component:
CoreIdentClientModel- (L1) Create
CoreIdent.Core/Models/CoreIdentClient.cspublic class CoreIdentClient { public string ClientId { get; set; } = string.Empty; public string? ClientSecret { get; set; } // Hashed for confidential clients public string ClientName { get; set; } = string.Empty; public ClientType ClientType { get; set; } = ClientType.Confidential; public ICollection<string> RedirectUris { get; set; } = []; public ICollection<string> PostLogoutRedirectUris { get; set; } = []; public ICollection<string> AllowedScopes { get; set; } = []; public ICollection<string> AllowedGrantTypes { get; set; } = []; public int AccessTokenLifetimeSeconds { get; set; } = 3600; public int RefreshTokenLifetimeSeconds { get; set; } = 86400; public bool RequirePkce { get; set; } = true; public bool AllowOfflineAccess { get; set; } = false; public bool Enabled { get; set; } = true; public DateTime CreatedAt { get; set; } public DateTime? UpdatedAt { get; set; } } public enum ClientType { Public, Confidential }
- (L1) Create
- Component:
IClientStoreInterface- (L1) Create
CoreIdent.Core/Stores/IClientStore.cspublic interface IClientStore { Task<CoreIdentClient?> FindByClientIdAsync(string clientId, CancellationToken ct = default); Task<bool> ValidateClientSecretAsync(string clientId, string clientSecret, CancellationToken ct = default); Task CreateAsync(CoreIdentClient client, CancellationToken ct = default); Task UpdateAsync(CoreIdentClient client, CancellationToken ct = default); Task DeleteAsync(string clientId, CancellationToken ct = default); }
- (L1) Create
- Component:
InMemoryClientStore- (L2) Create in-memory implementation using
ConcurrentDictionary - (L2) Support seeding clients at startup
- (L2) Create in-memory implementation using
- Component:
EfClientStore- (L2) Create EF Core implementation in
CoreIdent.Storage.EntityFrameworkCore - (L1) Add
ClientEntityentity configuration toCoreIdentDbContext
- (L2) Create EF Core implementation in
- Component: Client Secret Hashing
- (L2) Create
IClientSecretHasherinterface andDefaultClientSecretHasherimplementation - (L2) Use secure hashing (PBKDF2 with SHA256) for client secrets
- (L2) Create
- Component: DI Registration
- (L1) Add
AddInMemoryClientStore()extension method - (L1) Add
AddInMemoryClients(IEnumerable<CoreIdentClient>)extension - (L1) Add
AddEntityFrameworkCoreClientStore()extension method
- (L1) Add
- Test Case (Unit):
- (L1)
InMemoryClientStoreCRUD operations work correctly - (L1) Client secret validation works correctly
- (L1)
EfClientStoreCRUD operations work correctly
- (L1)
- Component:
CoreIdentScopeModel- (L1) Create
CoreIdent.Core/Models/CoreIdentScope.cspublic class CoreIdentScope { public string Name { get; set; } = string.Empty; public string? DisplayName { get; set; } public string? Description { get; set; } public bool Required { get; set; } = false; public bool Emphasize { get; set; } = false; public bool ShowInDiscoveryDocument { get; set; } = true; public ICollection<string> UserClaims { get; set; } = []; }
- (L1) Create
- Component:
IScopeStoreInterface- (L1) Create
CoreIdent.Core/Stores/IScopeStore.cspublic interface IScopeStore { Task<CoreIdentScope?> FindByNameAsync(string name, CancellationToken ct = default); Task<IEnumerable<CoreIdentScope>> FindByScopesAsync(IEnumerable<string> scopeNames, CancellationToken ct = default); Task<IEnumerable<CoreIdentScope>> GetAllAsync(CancellationToken ct = default); }
- (L1) Create
- Component:
InMemoryScopeStore- (L2) Create in-memory implementation
- (L2) Pre-seed standard OIDC scopes (openid, profile, email, address, phone, offline_access)
- Component:
EfScopeStore- (L2) Create EF Core implementation
- (L1) Add entity + DbContext schema configuration (migrations are app-owned)
- Component:
CoreIdentRefreshTokenModel- (L1) Create
CoreIdent.Core/Models/CoreIdentRefreshToken.cspublic class CoreIdentRefreshToken { public string Handle { get; set; } = string.Empty; public string SubjectId { get; set; } = string.Empty; public string ClientId { get; set; } = string.Empty; public string? FamilyId { get; set; } // For rotation tracking public ICollection<string> Scopes { get; set; } = []; public DateTime CreatedAt { get; set; } public DateTime ExpiresAt { get; set; } public DateTime? ConsumedAt { get; set; } public bool IsRevoked { get; set; } = false; }
- (L1) Create
- Component:
IRefreshTokenStoreInterface (Full)- (L1) Expand
CoreIdent.Core/Stores/IRefreshTokenStore.cspublic interface IRefreshTokenStore { Task<string> StoreAsync(CoreIdentRefreshToken token, CancellationToken ct = default); Task<CoreIdentRefreshToken?> GetAsync(string handle, CancellationToken ct = default); Task<bool> RevokeAsync(string handle, CancellationToken ct = default); Task RevokeFamilyAsync(string familyId, CancellationToken ct = default); Task<bool> ConsumeAsync(string handle, CancellationToken ct = default); Task CleanupExpiredAsync(CancellationToken ct = default); }
- (L1) Expand
- Component:
InMemoryRefreshTokenStore- (L2) Create in-memory implementation with
ConcurrentDictionary
- (L2) Create in-memory implementation with
- Component:
EfRefreshTokenStore- (L2) Create EF Core implementation
- (L1) Add entity + DbContext schema configuration (migrations are app-owned)
- (L2) Inject
TimeProviderfor testability (consistent with in-memory store)
- Component: Standard Scope Helpers
- (L1) Create
StandardScopesstatic class with predefined OIDC scopes
- (L1) Create
- Component: DI Registration
- (L1) Add
AddScopeStore()andAddRefreshTokenStore()extension methods - (L1) Add
AddInMemoryScopes(IEnumerable<CoreIdentScope>)extension
- (L1) Add
- Test Case (Unit):
- (L1) Scope store operations work correctly
- (L1) Refresh token store CRUD and family revocation work correctly
- Documentation:
- (L1) Document scope configuration
- Component:
CoreIdentOptionsConfiguration- (L1) Create
CoreIdentOptionswith required issuer/audience settings and safe defaults- Guidance: Include at minimum:
Issuer,Audience,AccessTokenLifetime,RefreshTokenLifetime - Guidance: Options should provide sane defaults where possible, but still allow fail-fast validation for required values (e.g., issuer/audience).
- Guidance: Keep
ITokenServiceas a low-level primitive (caller provides issuer/audience/expires). Higher-level endpoints/features should read fromIOptions<CoreIdentOptions>and pass values intoITokenService.
- Guidance: Include at minimum:
- (L1) Add startup validation (fail fast)
- Guidance: Validate required fields (issuer/audience), validate lifetimes are positive
- (L1) Create
- Component:
CoreIdentRouteOptions- (L1) Create route options to remove all ambiguity around endpoint mapping
- Guidance: Include
BasePath(default/auth),TokenPath(defaulttoken) - Guidance: Include root-relative
DiscoveryPath(default/.well-known/openid-configuration) - Guidance: Include root-relative
JwksPath(default/.well-known/jwks.json) - Guidance: Include
ConsentPath(defaultconsent, relative toBasePath) for future consent UI - Guidance: Include
UserInfoPath(defaultuserinfo, relative toBasePath) for OIDC userinfo - Guidance: Include
UserProfilePath(default/me, root-relative) for host-friendly "who am I" endpoint - Guidance: Routes must have hardcoded defaults that can be overridden via configuration/DI (convention over configuration).
- Guidance: Root-relative OIDC endpoints must remain root-relative even when
BasePathchanges. - Guidance: Any non-root-relative route should be composed as
BasePath + "/" + <RelativePath>(normalized for leading/trailing slashes). - Guidance: Route option values may be stored either with or without leading/trailing slashes, but endpoint mapping must normalize to valid ASP.NET route templates (single leading slash, no double slashes).
- Guidance: Include
- (L1) Create route options to remove all ambiguity around endpoint mapping
- Component: DI Registration (
AddCoreIdent)- (L2) Create
AddCoreIdent()extension method that registers:CoreIdentOptions+ validationCoreIdentRouteOptionsITokenServiceand related core services- Default stores when not overridden (in-memory)
- Guidance: Provide parameterless and parameterized overloads (e.g.
AddCoreIdent()andAddCoreIdent(Action<CoreIdentOptions> configure, Action<CoreIdentRouteOptions>? configureRoutes = null)), where the parameterless version uses defaults. - Guidance: Parameterless
AddCoreIdent()still requires issuer/audience to be configured (e.g., viaappsettings.jsonbinding toCoreIdentOptions). Validation will fail at startup if required values are missing.
- (L1) Document registration order for EF Core
- Guidance:
AddCoreIdent()->AddDbContext(...)->AddEntityFrameworkCore*Store()
- Guidance:
- (L2) Create
- Component: Endpoint Mapping (
MapCoreIdentEndpoints)- (L2) Create
MapCoreIdentEndpoints()extension method that maps all CoreIdent endpoints- Guidance: Map endpoints under
BasePathunless explicitly root-relative - Guidance: Ensure discovery and JWKS endpoints are always root-relative (per OIDC spec)
- Guidance: Provide parameterless and parameterized overloads. Parameterless should use configured options from DI; parameterized overload(s) should accept an options instance or configuration delegate and then cascade those settings down to the granular mappers.
- Guidance: Granular endpoint mappers remain public and convention-based;
MapCoreIdentEndpoints()is the authoritative aggregation.
- Guidance: Map endpoints under
- (L2) Create
- Test Case (Integration):
- (L2) App can boot with
AddCoreIdent()+MapCoreIdentEndpoints()and responds on required routes- Guidance: Test should only rely on defaults + minimal required configuration (issuer/audience, signing key) and validate that:
- Root-relative
/.well-known/*endpoints respond (ignoringBasePath) - Base-path endpoints respond under the configured default
BasePath
- Root-relative
- Guidance: Test should only rely on defaults + minimal required configuration (issuer/audience, signing key) and validate that:
- (L2) App can boot with
- Component: Discovery Document Endpoint
- (L2) Add
/.well-known/openid-configurationendpoint- Guidance: Always root-relative, ignore
BasePath - Guidance:
issuermust exactly match configuredCoreIdentOptions.Issuer - Guidance: Advertise endpoints using
CoreIdentRouteOptions(token, revocation, introspection, JWKS) - Guidance: Include supported
grant_types_supportedbased on implemented features - Guidance: Include
scopes_supportedbased onIScopeStore.GetAllAsync()(filterShowInDiscoveryDocument) - Guidance: Include signing algs from
ISigningKeyProvider.Algorithm
- Guidance: Always root-relative, ignore
- (L2) Add
- Component: Discovery Document Model
- (L1) Create a response model (record/class) for discovery document serialization
- Test Case (Integration):
- (L2) Discovery endpoint returns valid JSON with correct issuer and endpoint URLs
- Component:
CoreIdentUserModel- (L1) Create
CoreIdent.Core/Models/CoreIdentUser.cs- Guidance: Include at minimum:
Id,UserName,NormalizedUserName,CreatedAt,UpdatedAt
- Guidance: Include at minimum:
- (L1) Create
- Component:
IUserStoreInterface- (L1) Create
CoreIdent.Core/Stores/IUserStore.cs- Guidance: Include at minimum:
FindByIdAsync,FindByUsernameAsync,CreateAsync,UpdateAsync,DeleteAsync - Guidance: Include claims support to power token issuance:
GetClaimsAsync(subjectId)
- Guidance: Include at minimum:
- (L1) Create
- Component: In-Memory User Store
- (L2) Create
InMemoryUserStoreusingConcurrentDictionary- Guidance: Normalize usernames consistently
- (L2) Create
- Component: EF Core User Store
- (L2) Create
EfUserStoreinCoreIdent.Storage.EntityFrameworkCore - (L1) Add
UserEntity+ DbContext configuration
- (L2) Create
- Component: Password Hashing
- (L1) Create
IPasswordHasherinterface and default implementation using ASP.NET Core Identity hasher- Guidance: Password support is optional in Phase 1 flows, but needed for password-based auth where enabled
- (L1) Create
- Component: DI Registration
- (L1) Add
AddInMemoryUserStore()extension method - (L1) Add
AddEntityFrameworkCoreUserStore()extension method
- (L1) Add
- Test Case (Unit):
- (L1)
InMemoryUserStoreCRUD operations work correctly - (L1)
EfUserStoreCRUD operations work correctly
- (L1)
- Component: Token Endpoint
- (L3) Create
POST /auth/tokenendpoint inTokenEndpointExtensions.cs- Guidance: Support
grant_type=client_credentials - Guidance: Support
grant_type=refresh_token - Guidance:
grant_type=authorization_codeis implemented in Feature 1.7 (requires/auth/authorize) - Guidance: Validate client authentication
- Guidance: Validate requested scopes against client's allowed scopes
- Guidance: Issue JWT access tokens using
ITokenService - Guidance: Issue refresh tokens using
IRefreshTokenStore - Guidance: Implement refresh token rotation (new token on each use)
- Guidance: Support
- (L3) Create
- Component: Token Response Models
- (L1) Create
TokenRequestrecord - (L1) Create
TokenResponserecordpublic record TokenResponse( string AccessToken, string TokenType, int ExpiresIn, string? RefreshToken = null, string? Scope = null, string? IdToken = null );
- (L1) Create
- Component: Token Service Enhancement
- (L2) Extend
ITokenServiceto support scope claims - (L2) Add
jticlaim generation for all tokens - (L2) Add configurable token lifetimes per client
- (L2) Extend
- Component: Custom Claims Provider
- (L1) Create
ICustomClaimsProviderinterface- Guidance: Provide a hook to add/transform claims based on subject, client, and granted scopes
- (L2) Integrate
ICustomClaimsProviderinto token issuance
- (L1) Create
- Component: Refresh Token Rotation
- (L3) Implement rotation: consume old token, issue new token with same family
- (L3) Implement theft detection: if consumed token is reused, revoke entire family
- Test Case (Unit):
- (L1) Token response includes all required fields
- (L2) Refresh token rotation creates new token in same family
- Test Case (Integration):
- (L2)
POST /auth/tokenwithclient_credentialsreturns access token - (L2)
POST /auth/tokenwithrefresh_tokenreturns new tokens - (L3) Refresh token rotation works correctly
- (L3) Reusing consumed refresh token revokes family (theft detection)
- (L1) Invalid client credentials return 401
- (L1) Invalid grant returns 400
- (L2) Client authentication works in token endpoints (from Feature 0.3)
- (L2)
- Documentation:
- (L1) Document token endpoint usage
- (L2) Document refresh token rotation behavior
- (L1) Document client configuration options (from Feature 0.3)
Status: Access token revocation complete. Refresh token revocation to be completed after Feature 0.5 (Token Issuance).
- Component:
ITokenRevocationStoreInterface- (L1) Create
CoreIdent.Core/Stores/ITokenRevocationStore.cspublic interface ITokenRevocationStore { Task RevokeTokenAsync(string jti, string tokenType, DateTime expiry, CancellationToken ct = default); Task<bool> IsRevokedAsync(string jti, CancellationToken ct = default); Task CleanupExpiredAsync(CancellationToken ct = default); }
- (L1) Create
- Component:
InMemoryTokenRevocationStore- (L2) Create in-memory implementation using
ConcurrentDictionary - (L2) Implement automatic cleanup of expired entries
- (L2) Create in-memory implementation using
- Component:
EfTokenRevocationStore- (L2) Create EF Core implementation in
CoreIdent.Storage.EntityFrameworkCore - (L1) Add
RevokedTokenentity toCoreIdentDbContext - (L2) Inject
TimeProviderfor testability (consistent with in-memory store)
- (L2) Create EF Core implementation in
- Component: Revocation Endpoint
- (L3) Create
POST /auth/revokeendpoint inTokenManagementEndpointsExtensions.cs- Guidance: Accept
tokenand optionaltoken_type_hintparameters - Guidance: Support both access tokens and refresh tokens
- Guidance: For refresh tokens: mark as consumed in
IRefreshTokenStore - Guidance: For access tokens: add JTI to revocation store
- Guidance: Require client authentication for confidential clients
- Guidance: Always return 200 OK (per RFC 7009 - don't leak token validity)
- Guidance: JWT revocation reality: revoked JWT access tokens are only rejected by resource servers that perform an online check (introspection and/or shared revocation store). Default posture is short-lived access tokens + refresh token revocation/rotation.
- Guidance: Accept
- (L3) Create
- Component: Token Validation Integration
- (L3) Create token validation middleware that checks revocation store
- (L3) Integrate
ITokenRevocationStorecheck in protected endpoint middleware
- Component: Revocation Endpoint Enhancement (Post-0.5)
- (L2) Update revocation endpoint to use full
IRefreshTokenStorefor refresh token revocation - (L2) Validate client owns the token being revoked
- (L2) Update revocation endpoint to use full
- Test Case (Unit):
- (L1)
InMemoryTokenRevocationStorestores and retrieves revocations correctly - (L1) Cleanup removes only expired entries
- (L1)
- Test Case (Integration):
- (L2)
POST /auth/revokewith valid refresh token invalidates it (requires Feature 0.5) - (L2)
POST /auth/revokewith valid access token adds to revocation list - (L3) Revoked access token is rejected by protected endpoints
- (L2) Revoked refresh token cannot be used for token refresh (requires Feature 0.5)
- (L1) Invalid token revocation returns 200 OK (no information leakage)
- (L2) Confidential client must authenticate to revoke tokens
- (L2)
- Documentation:
- (L1) Add revocation endpoint to README.md
- (L1) Document revocation behavior and client requirements
Note: Introspection of refresh tokens requires Feature 0.5 (Token Issuance) to be complete.
- Component: Introspection Endpoint
- (L3) Create
POST /auth/introspectendpoint inTokenManagementEndpointsExtensions.cs- Guidance: Accept
tokenand optionaltoken_type_hintparameters - Guidance: Require client authentication (resource server credentials)
- Guidance: Validate token signature, expiry, revocation status
- Guidance: Check
IRefreshTokenStorefor refresh token introspection - Guidance: Return standardized response:
{ "active": true, "scope": "openid profile", "client_id": "client123", "username": "user@example.com", "token_type": "Bearer", "exp": 1234567890, "iat": 1234567800, "sub": "user-id", "aud": "resource-server", "iss": "https://issuer.example.com" }
- Guidance: Accept
- (L3) Create
- Component: Introspection Response Models
- (L1) Create
TokenIntrospectionRequestrecord - (L1) Create
TokenIntrospectionResponserecord
- (L1) Create
- Test Case (Integration):
- (L2) Valid access token returns
active: truewith claims - (L1) Expired token returns
active: false - (L2) Revoked token returns
active: false - (L1) Invalid token returns
active: false - (L1) Unauthenticated request returns 401
- (L2) Response includes all standard claims
- (L2) Valid refresh token returns
active: true(requires Feature 0.5) - (L2) Revoked/consumed refresh token returns
active: false(requires Feature 0.5)
- (L2) Valid access token returns
- Documentation:
- (L1) Add introspection endpoint to README.md
- (L2) Document resource server integration pattern
Note: Entity builders (UserBuilder, ClientBuilder, ScopeBuilder) require Features 0.3-0.4 to be complete.
- Component:
CoreIdent.TestingPackage- (L1) Create new project
tests/CoreIdent.Testing/CoreIdent.Testing.csproj - (L1) Add package references: xUnit, Shouldly, Microsoft.AspNetCore.Mvc.Testing
- (L1) Create new project
- Component:
CoreIdentWebApplicationFactory- (L3) Create
CoreIdent.Testing/Fixtures/CoreIdentWebApplicationFactory.cs- Guidance: Encapsulate SQLite in-memory setup
- Guidance: Provide
ConfigureTestServiceshook - Guidance: Provide
SeedDatabasehook - Guidance: Auto-seed standard OIDC scopes
- Guidance: Handle connection lifecycle properly
- (L3) Create
- Component:
CoreIdentTestFixtureBase Class- (L2) Create
CoreIdent.Testing/Fixtures/CoreIdentTestFixture.cs- Guidance: Implement
IAsyncLifetime - Guidance: Provide
Client(HttpClient) property - Guidance: Provide
Services(IServiceProvider) property - Guidance: Provide helper methods:
CreateUserAsync(),CreateClientAsync(),AuthenticateAsAsync()
- Guidance: Implement
- (L2) Create
- Component: Fluent Builders
- (L2) Create
CoreIdent.Testing/Builders/UserBuilder.cs- Guidance: Fluent API:
.WithEmail(),.WithPassword(),.WithClaim()
- Guidance: Fluent API:
- (L2) Create
CoreIdent.Testing/Builders/ClientBuilder.cs- Guidance: Fluent API:
.WithClientId(),.WithSecret(),.AsPublicClient(),.AsConfidentialClient()
- Guidance: Fluent API:
- (L1) Create
CoreIdent.Testing/Builders/ScopeBuilder.cs
- (L2) Create
- Component: Assertion Extensions
- (L2) Create
CoreIdent.Testing/Extensions/JwtAssertionExtensions.cs- Guidance:
.ShouldBeValidJwt(),.ShouldHaveClaim(),.ShouldExpireAfter()
- Guidance:
- (L1) Create
CoreIdent.Testing/Extensions/HttpResponseAssertionExtensions.cs- Guidance:
.ShouldBeSuccessful(),.ShouldBeUnauthorized(),.ShouldBeBadRequest()
- Guidance:
- (L2) Create
- Component: Standard Seeders
- (L1) Create
CoreIdent.Testing/Seeders/StandardScopes.cs- Guidance: Pre-defined openid, profile, email, offline_access scopes
- (L1) Create
CoreIdent.Testing/Seeders/StandardClients.cs- Guidance: Pre-defined test clients (public, confidential)
- (L1) Create
- Component: Integration Test Setup
- (L2) Create
CoreIdent.Integration.Testsproject using new fixtures - (L2) Write initial integration tests using builders
- (L2) Create
- Test Case:
- (L1) Fixture-based tests are simple and readable
- (L1) Test execution time is reasonable
- (L1) Integration smoke test implemented and passing (app boots with test fixture, health/check endpoint returns 200)
Note: .NET 10 provides built-in metrics (
aspnetcore.authentication.*,aspnetcore.identity.*). CoreIdent adds supplementary metrics for OAuth/OIDC-specific operations. Requires Feature 0.5 (Token Issuance) forcoreident.token.issuedmetric.
- Component: Metrics Instrumentation
- (L2) Integrate with .NET 10's built-in
Microsoft.AspNetCore.Authenticationmetrics - (L2) Integrate with
Microsoft.AspNetCore.Identitymetrics (user ops, sign-ins, 2FA) - (L2) Add CoreIdent-specific metrics:
coreident.token.issued— Tokens issued (by type)coreident.token.revoked— Tokens revokedcoreident.client.authenticated— Client authentications
- (L2) Integrate with .NET 10's built-in
- Component: Metrics Configuration
- (L1) Add
AddCoreIdentMetrics()extension method - (L2) Support filtering/sampling
- (L1) Add
- Test Case:
- (L2) Metrics are emitted for key operations
- (L2) Metrics integrate with Aspire dashboard
- Documentation:
- (L1) Metrics and observability guide
Note:
client addcommand requires Feature 0.3 (Client Store) to be complete.
- Component: CLI Package (
CoreIdent.Cli)- (L2) Create .NET tool package
- (L1) Register as
dotnet tool install -g CoreIdent.Cli
- Component:
initCommand- (L2) Scaffold
appsettings.jsonwith CoreIdent section - (L2) Generate secure random signing key (for dev)
- (L1) Add package references to
.csproj
- (L2) Scaffold
- Component:
keys generateCommand- (L2) Generate RSA key pair (PEM format)
- (L2) Generate ECDSA key pair (PEM format)
- (L1) Output to file or stdout
- Component:
client addCommand- (L2) Interactive client registration
- (L1) Generate client ID and secret
- (L1) Output configuration snippet
- Component:
migrateCommand- (L2) Wrapper around EF Core migrations for CoreIdent schema
- Test Case:
- (L1) Each command works in isolation
- (L2) Generated keys are valid and usable
- Documentation:
- (L1) CLI reference guide
- Component:
.devcontainer/Setup- (L1) Create
devcontainer.json - (L1) Configure .NET 10 SDK
- (L1) Include recommended VS Code extensions
- (L1) Pre-configure database (SQLite for simplicity)
- (L1) Create
- Component: Codespaces Support
- (L1) Test in GitHub Codespaces
- (L1) Add "Open in Codespaces" badge to README
- Documentation:
- (L1) Contributing guide with dev container instructions
Goal: Make passwordless authentication trivially easy; establish the "5-minute auth" story.
Estimated Duration: 3-4 weeks
Prerequisites: Phase 0 complete
- Component:
IEmailSenderInterface- (L1) Create
CoreIdent.Core/Services/IEmailSender.cspublic interface IEmailSender { Task SendAsync(EmailMessage message, CancellationToken ct = default); } public record EmailMessage(string To, string Subject, string HtmlBody, string? TextBody = null);
- (L1) Create
- Component:
SmtpEmailSenderImplementation- (L2) Create default SMTP implementation
- (L1) Support configuration via
SmtpOptions(host, port, credentials, TLS)
- Component:
IPasswordlessTokenStoreInterface- (L1) Create
CoreIdent.Core/Stores/IPasswordlessTokenStore.cspublic interface IPasswordlessTokenStore { Task<string> CreateTokenAsync(PasswordlessToken token, CancellationToken ct = default); Task<PasswordlessToken?> ValidateAndConsumeAsync(string token, CancellationToken ct = default); Task CleanupExpiredAsync(CancellationToken ct = default); }
- (L1) Create
- Component:
PasswordlessTokenModel- (L1) Create model with: Id, Email, TokenHash, CreatedAt, ExpiresAt, Consumed, UserId
- Component:
InMemoryPasswordlessTokenStore- (L2) Create in-memory implementation
- Component:
EfPasswordlessTokenStore- (L2) Create EF Core implementation
- (L1) Add entity mapping (migrations owned by consuming host app)
- Component: Passwordless Endpoints
- (L3) Create
POST /auth/passwordless/email/start- Guidance: Accept email, generate secure token, store hashed, send email
- Guidance: Rate limit per email address
- Guidance: Always return success (don't leak email existence)
- (L3) Create
GET /auth/passwordless/email/verify- Guidance: Accept token, validate, consume, create/find user, issue tokens
- Guidance: Redirect to configured success URL with tokens
- (L3) Create
- Component:
PasswordlessEmailOptions- (L1) Create configuration class
public class PasswordlessEmailOptions { public TimeSpan TokenLifetime { get; set; } = TimeSpan.FromMinutes(15); public int MaxAttemptsPerHour { get; set; } = 5; public string EmailSubject { get; set; } = "Sign in to {AppName}"; public string? EmailTemplatePath { get; set; } public string VerifyEndpointUrl { get; set; } = "passwordless/email/verify"; public string? SuccessRedirectUrl { get; set; } }
- (L1) Create configuration class
- Component: Email Templates
- (L1) Create default HTML email template
- (L2) Support custom template loading
- Test Case (Unit):
- (L2) Token generation creates unique, secure tokens
- (L2) Token hashing is one-way and consistent
- (L2) Rate limiting blocks excessive requests
- Test Case (Integration):
- (L2)
POST /auth/passwordless/email/startsends email (mock sender) - (L3)
GET /auth/passwordless/email/verifywith valid token issues tokens - (L1) Expired token returns error
- (L1) Already-consumed token returns error
- (L2) New user is created if email not found
- (L2) Existing user is authenticated if email found
- (L2)
- Documentation:
- (L1) Add passwordless email setup guide
- (L1) Document SMTP configuration
- (L1) Recommend SMTP for demos/self-hosted; provider email APIs for production; document how to extend CoreIdent with custom
IEmailSender(separate package/DI swap) - (L1) Provide email template customization examples
Note: .NET 10 provides built-in passkey support via
IdentityPasskeyOptionsand ASP.NET Core Identity. CoreIdent wraps this for minimal-API scenarios and adds convenience configuration.
- Component:
CoreIdentPasskeyOptions- (L2) Create wrapper around .NET 10's
IdentityPasskeyOptionspublic class CoreIdentPasskeyOptions { public string ClientId { get; set; } = "passkey"; public string? RelyingPartyId { get; set; } public string RelyingPartyName { get; set; } = "CoreIdent"; public TimeSpan ChallengeTimeout { get; set; } = TimeSpan.FromMinutes(5); public int ChallengeSize { get; set; } = 32; // ~~UserVerificationRequirement UserVerification~~ — not exposed by .NET 10's IdentityPasskeyOptions }
- (L2) Create wrapper around .NET 10's
- Component: Passkey Service
- (L1) Create
IPasskeyServiceinterface - (L2) Implement using .NET 10's built-in passkey support
- (L2) Handle registration ceremony
- (L2) Handle authentication ceremony
- (L1) Create
- Component: Passkey Credential Storage
- (L1) Create
IPasskeyCredentialStoreinterface - (L1) Create
PasskeyCredentialmodel - (L2) Implement in-memory store
- (L2) Implement EF Core store
- (L1) Create
- Component: Passkey Endpoints
- (L2)
POST /auth/passkey/register/options- Get registration options - (L2)
POST /auth/passkey/register/complete- Complete registration - (L2)
POST /auth/passkey/authenticate/options- Get authentication options - (L2)
POST /auth/passkey/authenticate/complete- Complete authentication
- (L2)
- Component: DI Registration
- (L1) Add
AddPasskeys()extension method
- (L1) Add
- Test Case (Integration):
- (L2) Registration flow returns valid options
- (L2) Authentication flow returns valid options
- (Note: Full WebAuthn testing requires browser automation or mocks)
- Documentation:
- (L1) Add passkey setup guide
- (L1) Document browser requirements
- (L2) Provide JavaScript integration examples
- Component:
ISmsProviderInterface- (L1) Create
CoreIdent.Core/Services/ISmsProvider.cspublic interface ISmsProvider { Task SendAsync(string phoneNumber, string message, CancellationToken ct = default); }
- (L1) Create
- Component:
ConsoleSmsProvider(Dev/Testing)- (L1) Create implementation that logs to console
- Component: SMS OTP Endpoints
- (L2)
POST /auth/passwordless/sms/start- Send OTP - (L2)
POST /auth/passwordless/sms/verify- Verify OTP
- (L2)
- Component: OTP Generation and Storage
- (L1) Reuse
IPasswordlessTokenStorewith SMS-specific token type - (L1) Generate 6-digit numeric OTP
- (L1) Reuse
- Test Case (Integration):
- (L1) OTP is sent via provider (mock)
- (L2) Valid OTP authenticates user
- (L1) Expired OTP fails
- (L2) Rate limiting works
- Documentation:
- (L1) Document SMS provider interface
- (L2) Provide Twilio implementation example (separate package)
Note: Moved from Feature 0.1 — verification is more meaningful once core APIs exist.
- Component: F# Compatibility Verification
- (L2) Verify all public APIs are F#-friendly (no
outparameters in critical paths) - (L2) Create F# sample project using Giraffe/Saturn
- (L2) Add F# template (
coreident-api-fsharp) - (L1) Document F# usage patterns
- (L2) Verify all public APIs are F#-friendly (no
- Test Case:
- (L1) F# sample project compiles and runs
- (L2) All core interfaces are usable from F#
- Documentation:
- (L1) F# usage guide
Note: We should support both C# and F# templates
- Component: Template Package Structure
- (L1) Create
templates/directory structure - (L1) Create
CoreIdent.Templates.csprojfor packaging
- (L1) Create
- Component:
coreident-apiTemplate- (L2) Create minimal API template with CoreIdent auth
- (L2) Include
template.jsonwith parameters (usePasswordless, useEfCore) - (L1) Include sample
appsettings.json
- Component:
coreident-serverTemplate- (L2) Create full OAuth/OIDC server template
- (L2) Include EF Core setup
- (L1) Include sample clients and scopes
- Component: Template Testing
- (L2) Create test that instantiates templates and builds them
- Documentation:
- (L1) Add template usage to getting started guide
- (L1) Document template parameters
- Component:
CoreIdent.AspirePackage- (L2) Create package targeting Aspire v13 (net10.0)
- (L3) Provide AppHost integration via
IDistributedApplicationBuilderextension methods (Aspire.Hosting)
- Component: Dashboard Integration
- (L2) OpenTelemetry metrics integration that includes CoreIdent
System.Diagnostics.Metricsmeter(s) - (L2) Structured logging integration guidance (OpenTelemetry logging)
- (L2) Distributed tracing for auth flows (CoreIdent ActivitySource spans)
- (L2) OpenTelemetry metrics integration that includes CoreIdent
- Component: Health Checks
- (L1) Database connectivity check (when EF Core DbContext is configured)
- (L1) Key availability check (ISigningKeyProvider)
- (L2) External provider connectivity (if configured)
- Component: Service Defaults
- (L2)
AddCoreIdentDefaults()extension for Aspire-style service defaults - (L2)
MapCoreIdentDefaultEndpoints()helper for mapping/health+/alive
- (L2)
- Test Case:
- (L2) OpenTelemetry configuration includes CoreIdent meter(s)
- (L2) OpenTelemetry configuration includes CoreIdent tracing spans
- (L1) Health checks report correctly
- Documentation:
- (L1) Aspire integration guide
- Component: Authorization Code Model
- (L1) Create
CoreIdentAuthorizationCodemodel- Guidance: Include code handle, client_id, subject_id, redirect_uri, scopes, created/expires, consumed, nonce, code_challenge, code_challenge_method
- (L1) Create
- Component:
IAuthorizationCodeStoreInterface- (L1) Create store interface
- Guidance:
CreateAsync,GetAsync,ConsumeAsync,CleanupExpiredAsync
- Guidance:
- (L1) Create store interface
- Component: In-Memory Store
- (L2) Implement
InMemoryAuthorizationCodeStoreusingConcurrentDictionary
- (L2) Implement
- Component: EF Core Store
- (L2) Implement
EfAuthorizationCodeStoreinCoreIdent.Storage.EntityFrameworkCore - (L1) Add
AuthorizationCodeEntity+ DbContext configuration
- (L2) Implement
- Component: Authorization Code Cleanup
- (L2) Add hosted service that periodically calls
CleanupExpiredAsync- Guidance: Must be opt-out via options
- (L2) Add hosted service that periodically calls
- Component: Authorize Endpoint
- (L3) Implement
GET /auth/authorize- Guidance: Validate
client_id,redirect_uri,response_type=code, and requested scopes - Guidance: Enforce PKCE: require
code_challenge+code_challenge_method=S256 - Guidance: Require
stateround-trip - Guidance: Require authenticated user (
HttpContext.User.Identity.IsAuthenticated); otherwise challenge - Guidance: Persist authorization code via
IAuthorizationCodeStore - Guidance: Redirect back to client with
codeandstateorerroranderror_description
- Guidance: Validate
- (L3) Implement
- Component: Token Endpoint (
authorization_codegrant)- (L3) Extend
POST /auth/tokento supportgrant_type=authorization_code- Guidance: Validate code exists and not expired/consumed
- Guidance: Validate
redirect_urimatches the one stored in the code - Guidance: Validate PKCE
code_verifieragainst stored challenge - Guidance: Consume code atomically (single-use)
- Guidance: Issue access token and (optionally) refresh token
- (L3) Extend
- Component: OpenID Connect ID Token (when
openidscope is granted)- (L2) Issue
id_tokenin token response forauthorization_codewhenopenidscope is granted- Guidance: Include
nonce(if provided), setaudtoclient_id, and include scope-derived claims - Guidance: Use signing key provider /
ITokenServiceconsistently
- Guidance: Include
- (L2) Issue
- Component: DI Registration
- (L1) Add store registration extensions for authorization code store (in-memory + EF)
- Test Case (Integration):
- (L3) Authorization code flow works end-to-end (authorize -> token)
- (L2) PKCE failure returns
invalid_grant - (L2) Redirect URI mismatch returns
invalid_request - (L2) Consumed code cannot be reused
- Documentation:
- (L1) Document Authorization Code + PKCE flow and required parameters
- Component: User Grant Model
- (L1) Create
CoreIdentUserGrantmodel- Guidance: Include subject_id, client_id, granted scopes, created/expires
- (L1) Create
- Component:
IUserGrantStoreInterface- (L1) Create interface for consent persistence
- Guidance: Include
FindAsync(subjectId, clientId),SaveAsync(grant),RevokeAsync(...),HasUserGrantedConsentAsync(...)
- Guidance: Include
- (L1) Create interface for consent persistence
- Component: In-Memory Store
- (L2) Implement
InMemoryUserGrantStore
- (L2) Implement
- Component: EF Core Store
- (L2) Implement
EfUserGrantStore - (L1) Add
UserGrantEntity+ DbContext configuration
- (L2) Implement
- Component: Consent UX Endpoints
- (L3) Implement
GET /auth/consent(default minimal HTML)- Guidance: Must be replaceable by host app; driven by
CoreIdentRouteOptions.ConsentPath
- Guidance: Must be replaceable by host app; driven by
- (L3) Implement
POST /auth/consentto persist grant or deny- Guidance: Deny returns
access_deniedback to client redirect_uri
- Guidance: Deny returns
- (L3) Implement
- Component: Authorize Endpoint Consent Integration
- (L3) Integrate consent checks into
/auth/authorize- Guidance: If client requires consent and no existing grant satisfies requested scopes, redirect to consent UI
- (L3) Integrate consent checks into
- Test Case (Integration):
- (L3) Consent required redirects to consent UI
- (L3) Allow persists grant and completes code flow
- (L2) Deny returns
access_denied
- Documentation:
- (L1) Document consent behavior and how to replace the default consent UI
- Component: Delegated User Store Options
- (L1) Create
DelegatedUserStoreOptionswith required delegates:FindUserByIdAsyncFindUserByUsernameAsyncValidateCredentialsAsync- optional:
GetClaimsAsync
- (L1) Create
- Component:
DelegatedUserStoreImplementation- (L2) Implement
IUserStorevia configured delegates- Guidance: Must never store password hashes; credential validation is delegated
- (L2) Implement
- Component: Validation
- (L1) Add startup validation to ensure required delegates are provided
- Component: DI Registration
- (L1) Add
AddCoreIdentDelegatedUserStore(...)extension method- Guidance: Should replace any previously-registered
IUserStore
- Guidance: Should replace any previously-registered
- (L1) Add
- Test Case (Unit):
- (L2) Missing required delegates fails validation on startup
- (L2) Delegates are invoked correctly for find + credential validation
- Documentation:
- (L1) Document integration pattern and security responsibilities
- Component: UserInfo Endpoint
- (L3) Implement
GET /auth/userinfo- Guidance: Path must be configurable via
CoreIdentRouteOptions.UserInfoPath - Guidance: Require a valid access token (bearer auth)
- Guidance: Use scopes to determine returned claims (e.g.,
profile,email,address,phone) - Guidance: Source claims from
IUserStoreand/orICustomClaimsProvider - Guidance: Return standard OIDC claims when present; omit claims not granted by scope
- Guidance: Path must be configurable via
- (L3) Implement
- Component: UserInfo Response Model
- (L1) Define a response model (record/dictionary) suitable for OIDC userinfo
- Test Case (Integration):
- (L2) Unauthenticated request returns 401
- (L3) With
openid profilescope, userinfo returnssuband profile claims - (L2) With
openid emailscope, userinfo returnsemail
- Documentation:
- (L1) Document userinfo endpoint behavior and scope-to-claims mapping
Goal: Provide minimal, working endpoints for user registration, login, and profile retrieval. Full customization via delegate replacement. Supports both JSON API and HTML form workflows.
Philosophy:
- Convention over configuration — works out of the box with minimal HTML
- Developer can replace response handling entirely via delegate
- We do the security-critical work (hashing, token issuance), they control the response
- Content negotiation: JSON for API clients, HTML for browsers
-
Component:
CoreIdentResourceOwnerOptions- (L1) Create options class with delegate properties:
public class CoreIdentResourceOwnerOptions { // Delegate receives the created user; returns custom response or null for default public Func<HttpContext, CoreIdentUser, CancellationToken, Task<IResult?>>? RegisterHandler { get; set; } // Delegate receives authenticated user + issued tokens; returns custom response or null for default public Func<HttpContext, CoreIdentUser, TokenResponse, CancellationToken, Task<IResult?>>? LoginHandler { get; set; } // Delegate receives current user + claims; returns custom response or null for default public Func<HttpContext, CoreIdentUser, IReadOnlyList<Claim>, CancellationToken, Task<IResult?>>? ProfileHandler { get; set; } } public record TokenResponse(string AccessToken, string RefreshToken, int ExpiresIn, string TokenType = "Bearer");
- (L1) Create options class with delegate properties:
-
Component: Route Configuration
- (L1) Add paths to
CoreIdentRouteOptions:public string RegisterPath { get; set; } = "/register"; public string LoginPath { get; set; } = "/login"; public string ProfilePath { get; set; } = "/profile";
- (L1) Add paths to
-
Component: Content Negotiation Helper
- (L1) Create shared helper for detecting JSON vs HTML preference:
// Returns true if client prefers JSON (explicit Accept header or JSON Content-Type) // Returns false for form posts without JSON Accept, query string GETs, etc. private static bool WantsJson(HttpRequest request);
- (L1) Create shared helper for detecting JSON vs HTML preference:
-
Component:
ResourceOwnerEndpointsExtensions- (L2)
POST /auth/register:- Accept JSON body OR form-urlencoded
- Validate email + password
- Create user via
IUserStore.CreateAsync() - Hash password via
IPasswordHasher - Call delegate if provided; if delegate returns null or not provided, return default response
- Default JSON:
{ "userId": "...", "message": "Registered successfully" } - Default HTML: Minimal success page with user ID
- (L2)
GET /auth/register(optional form UI):- Return minimal HTML registration form
- Form posts to same endpoint
- (L2)
POST /auth/login:- Accept JSON body OR form-urlencoded
- Validate credentials via
IUserStore.FindByUsernameAsync()+IPasswordHasher.VerifyHashedPassword() - Issue tokens via
ITokenService+IRefreshTokenStore - Call delegate if provided
- Default JSON:
{ "access_token": "...", "refresh_token": "...", "expires_in": 3600, "token_type": "Bearer" } - Default HTML: Minimal success page (or redirect if
redirect_uriprovided)
- (L2)
GET /auth/login(optional form UI):- Return minimal HTML login form
- Form posts to same endpoint
- (L2)
GET /auth/profile:- Require bearer token authentication
- Get user via
IUserStore.FindByIdAsync()usingsubclaim - Get claims via
IUserStore.GetClaimsAsync() - Call delegate if provided
- Default JSON:
{ "id": "...", "email": "...", "claims": {...} } - Default HTML: Minimal profile display
- (L2)
-
Component: DI Registration
- (L1) Add
ConfigureResourceOwnerEndpoints(Action<CoreIdentResourceOwnerOptions>)extension - (L1) Integrate into
MapCoreIdentEndpoints()pipeline
- (L1) Add
-
Test Case (Unit):
- (L2) Register creates user with hashed password
- (L2) Register rejects duplicate email
- (L2) Login returns tokens for valid credentials
- (L2) Login rejects invalid credentials
- (L2) Profile returns user data for authenticated request
- (L2) Profile rejects unauthenticated request
-
Test Case (Integration):
- (L2) Full register → login → profile flow (JSON)
- (L2) Full register → login → profile flow (HTML form)
- (L2) Custom delegate is invoked and can override response
- (L2) Custom delegate returning null falls back to default
-
Documentation:
- (L1) Document default behavior and content negotiation
- (L1) Document delegate customization pattern with examples
- (L1) Document how to disable individual endpoints
Goal: Support grant_type=password in token endpoint for legacy/mobile scenarios.
Note: This grant type is deprecated in OAuth 2.1. A warning is logged when used.
-
Component: Password Grant Handler
- (L2) Add
GrantTypes.Passwordcase toTokenEndpointExtensions.HandleTokenRequest() - (L2) Validate
usernameandpasswordparameters - (L2) Authenticate via
IUserStore.FindByUsernameAsync()+IPasswordHasher.VerifyHashedPassword() - (L2) Issue tokens same as login endpoint
- (L1) Log deprecation warning: "Password grant is deprecated in OAuth 2.1. Consider using authorization code flow with PKCE."
- (L2) Add
-
Component: Client Configuration
- (L1) Add "password" as valid grant type in
CoreIdentClient.AllowedGrantTypes
- (L1) Add "password" as valid grant type in
-
Test Case (Integration):
- (L2) Password grant returns tokens for valid credentials
- (L2) Password grant rejects invalid credentials
- (L2) Password grant rejected if client doesn't allow it
- (L1) Deprecation warning is logged
-
Documentation:
- (L1) Document password grant with deprecation notice
- (L1) Recommend migration to authorization code flow
Goal: Clean up inconsistencies, address technical debt, and ensure codebase quality before Phase 1.5.
Estimated Duration: 1-2 weeks
- Component: Replace
DateTime.UtcNowwithTimeProvider- (L2)
InMemoryUserStore.cs— ReplaceDateTime.UtcNowwith injectedTimeProvider.GetUtcNow() - (L2)
EfUserStore.cs— ReplaceDateTime.UtcNowwith injectedTimeProvider.GetUtcNow() - (L2)
CliApp.cs— ReplaceDateTime.UtcNowin client creation (or accept as CLI-only exception) - (L2)
PasswordlessEmailEndpointsExtensions.cs— Replace 2 instances withTimeProvider - (L2)
PasswordlessSmsEndpointsExtensions.cs— Replace 2 instances withTimeProvider - (L2)
ResourceOwnerEndpointsExtensions.cs— Replace instance withTimeProvider - (L1) Ensure
TimeProvideris registered in DI (already done inServiceCollectionExtensions.cs)
- (L2)
- Test Case:
- (L2) Unit tests can control time via
FakeTimeProviderfor user creation timestamps
- (L2) Unit tests can control time via
Decision: Parameterless endpoint overloads should read from
IOptions<CoreIdentRouteOptions>via DI. Hardcoded defaults are not acceptable for production-quality code.
- Component: Refactor parameterless overloads to use
IOptions<CoreIdentRouteOptions>- (L2)
TokenEndpointExtensions.cs— Refactor to resolveTokenPathfromIOptions<CoreIdentRouteOptions> - (L2)
TokenManagementEndpointsExtensions.cs— Refactor to resolveRevocationPath,IntrospectionPathfrom options - (L2)
ResourceOwnerEndpointsExtensions.cs— Refactor to resolveRegisterPath,LoginPath,ProfilePathfrom options - (L2)
PasswordlessEmailEndpointsExtensions.cs— Refactor to resolve passwordless email paths from options (may need to add paths toCoreIdentRouteOptions) - (L2)
PasswordlessSmsEndpointsExtensions.cs— Refactor to resolve passwordless SMS paths from options (may need to add paths toCoreIdentRouteOptions) - (L2)
UserInfoEndpointExtensions.cs— Refactor to resolveUserInfoPathfrom options - (L2)
ConsentEndpointExtensions.cs— Refactor to resolveConsentPathfrom options - (L2)
AuthorizationEndpointExtensions.cs— Refactor to resolveAuthorizePathfrom options - (L2)
PasskeyEndpointsExtensions.cs— Refactor to resolve passkey paths from options (may need to add paths toCoreIdentRouteOptions)
- (L2)
- Component: Extend
CoreIdentRouteOptionsif needed- (L2) Add
PasswordlessEmailStartPath,PasswordlessEmailVerifyPathif not present - (L2) Add
PasswordlessSmsStartPath,PasswordlessSmsVerifyPathif not present - (L2) Add passkey paths (
PasskeyRegisterOptionsPath,PasskeyRegisterCompletePath,PasskeyAuthenticateOptionsPath,PasskeyAuthenticateCompletePath) if not present
- (L2) Add
- Documentation:
- (L1) Document route customization patterns in Developer_Guide.md if not already covered
- Component: RFC 7807 Problem Details
- (L3) Audit error responses across all endpoints for consistency
- (L3) Consider adopting
Results.Problem()/ProblemDetailsfor error responses - (L2) Create
CoreIdentProblemDetailshelper or extension for standardized error formatting - (L2) Document error response format in Developer_Guide.md
- Component: Structured Logging
- (L2) Audit logging statements for structured logging best practices
- (L2) Add correlation ID support (e.g.,
Activity.Current?.Idor custom header) - (L2) Ensure sensitive data (tokens, passwords, PII) is never logged
- (L1) Document logging configuration in Developer_Guide.md
- Test Case:
- (L2) Error responses include consistent structure (error code, message, optional details)
Decision: Move to 1.0 GA before starting Phase 1.5. This feature is the gate for GA release.
- Component: Version String Updates
- (L1)
CliApp.cs— UpdatePackageVersionfrom"0.4.0"to"1.0.0"(or make dynamic via assembly version) - (L2) Search for other hardcoded
0.4version strings in codebase and update to1.0 - (L1) Update all
.csprojfiles with<Version>1.0.0</Version>(or appropriate pre-release tag) - (L1) Update NuGet package metadata for 1.0 release
- (L1)
- Component: Documentation Path Restructure
- (L2) Move contents of
docs/version folder todocs/root - (L2) Update all internal doc links in:
- README.md
- CLAUDE.md
- Developer_Guide.md
- All other docs that reference the old versioned doc paths
- (L2) Update template references that point to the old versioned doc paths
- (L2) Update CLI output that references the old versioned doc paths
- (L1) Remove or archive the empty versioned docs folder
- (L2) Move contents of
- Component: Release Preparation
- (L1) Create CHANGELOG.md or RELEASE_NOTES.md for 1.0
- (L1) Review and finalize MIGRATION.md
- (L1) Tag release in git as
v1.0.0
- Documentation:
- (L1) Update CLAUDE.md with new doc paths
- (L1) Update README.md badges and version references
- Component: CLAUDE.md Review
- (L1) Verify project structure section matches current layout
- (L1) Verify code style guidance matches C# 14 / .NET 10 patterns in use
- (L1) Add any missing guidance discovered during Phase 1 implementation
- Component: README.md Review
- (L1) Verify quickstart examples work with current codebase
- (L1) Verify feature list matches implemented features
- (L1) Update status badges if needed
- Component: Developer_Guide.md Review
- (L2) Verify all endpoint documentation matches implementation
- (L2) Verify configuration examples are accurate
- (L2) Add any missing sections for Phase 1 features
- Component: README_Detailed.md Review
- (L1) Verify roadmap status table is accurate
- (L1) Verify metrics documentation matches implementation
- Component: Technical_Plan.md Review
- (L2) Mark completed items or remove outdated sections
- (L2) Update "Open Questions" section with decisions made
- Component: Project_Overview.md Review
- (L1) Verify architecture diagrams match current structure
- (L1) Verify phase descriptions match DEVPLAN.md
- Component: Other Docs
- (L1) Passkeys.md — Verify setup guide is accurate
- (L1) CLI_Reference.md — Verify command documentation is complete
- (L1) Aspire_Integration.md — Verify integration guide is accurate
- Component: Nullable Reference Type Audit
- (L2) Ensure all projects have
<Nullable>enable</Nullable> - (L2) Address any nullable warnings in CI build output
- (L2) Ensure all projects have
- Component: XML Documentation
- (L2) Ensure all public APIs have XML doc comments
- (L2) Consider enabling
<GenerateDocumentationFile>true</GenerateDocumentationFile>for NuGet packages
- Component: Code Style Consistency
- (L1) Run
dotnet formatacross solution - (L1) Address any formatting inconsistencies
- (L1) Run
- Component: Unused Code Removal
- (L2) Audit for unused
usingstatements - (L2) Audit for dead code paths or commented-out code
- (L2) Audit for unused
- Test Case:
- (L1) CI build passes with zero warnings (or document accepted warnings)
- Component: Coverage Analysis
- (L2) Run coverage report (e.g.,
dotnet test --collect:"XPlat Code Coverage") - (L2) Identify gaps in critical paths (token issuance, revocation, auth flows)
- (L2) Add tests for any uncovered critical paths
- (L2) Achieve 90% line coverage for all of
src/CoreIdent.Core(merged, de-duplicated across all test projects) - (L2) Add GitHub Actions coverage gate to fail CI if normalized
CoreIdent.Coreline coverage is below 90%
- (L2) Run coverage report (e.g.,
- Component: Test Quality
- (L1) Ensure all tests have descriptive assertion messages (per CLAUDE.md Shouldly guidance)
- (L2) Review flaky tests and stabilize (repeat-runs performed; no flakes reproduced)
- Documentation:
- (L1) Document test coverage expectations in CONTRIBUTING.md
-
Component: OIDC Discovery Metadata Completeness
- (L2)
DiscoveryEndpointsExtensions.cs— Populategrant_types_supportedinstead of returning an empty list- Guidance: Include currently supported grants (
client_credentials,refresh_token,authorization_code,password(deprecated)) - Guidance: Ensure the discovery document remains accurate if features are disabled via endpoint mapping
- Note: This addresses an incomplete implementation from Feature 0.4.2 which specified including
grant_types_supported
- Guidance: Include currently supported grants (
- (L2) Consider adding other commonly expected discovery fields (only if compatible with current scope):
response_types_supported(e.g.,code)token_endpoint_auth_methods_supported(e.g.,client_secret_basic,client_secret_post)
- (L2)
-
Test Case (Integration):
- (L2)
/.well-known/openid-configurationreturns a non-emptygrant_types_supportedlist matching implemented features
- (L2)
-
Component: Sync-over-Async Hotspots
- (L2)
DelegatedPasswordHasher.cs— Remove sync-over-async (GetAwaiter().GetResult()) when validating delegated credentials- Guidance: If
IPasswordHashermust remain synchronous, introduce a dedicated synchronous delegate inDelegatedUserStoreOptionsfor password verification - Guidance: Alternatively, introduce an
IAsyncPasswordVerifierabstraction and adapt the token endpoint to use it
- Guidance: If
- (L2) Remove
CancellationToken.Noneusage in the delegated password verification path where feasible
- (L2)
-
Test Case (Unit):
- (L2) Delegated credential validation can be tested without blocking threads or requiring sync-over-async
-
Component: PII / Sensitive Data Logging Audit
- (L2) Audit logs for PII disclosure in passwordless flows (email, phone)
PasswordlessEmailEndpointsExtensions.cs(logs email)PasswordlessSmsEndpointsExtensions.cs(logs phone)ConsoleSmsProvider.cs(writes full SMS message including OTP)
- (L2) Define a standard redaction strategy:
- Mask email/phone values in logs (e.g.,
j***@example.com,+1******4567) - Never log OTP values or magic link tokens
- Mask email/phone values in logs (e.g.,
- (L2) Replace
Console.WriteLinein default providers withILogger(or ensure these providers are explicitly dev-only and opt-in)
- (L2) Audit logs for PII disclosure in passwordless flows (email, phone)
-
Test Case:
- (L2) Tests assert logs do not contain OTP/token material for passwordless flows
-
Component: Remove Silent Exception Swallowing
- (L2) Remove
catch { }blocks in Basic auth parsing helpers:TokenEndpointExtensions.cs(ExtractClientCredentials)TokenManagementEndpointsExtensions.cs(ExtractClientCredentials)- Guidance: Prefer
Try*parsing patterns and consider logging at Debug/Trace level for malformed Authorization headers
- (L2) Remove
-
Test Case:
- (L2) Malformed Basic auth headers reliably return
invalid_clientwithout throwing and without leaking secrets
- (L2) Malformed Basic auth headers reliably return
Goal: Provide automatic API documentation and discoverability for all CoreIdent HTTP endpoints.
Estimated Duration: 1-2 weeks
Prerequisites: XML documentation complete (1.13.7)
-
Component: OpenAPI Integration Package
- (L1) Create new project
CoreIdent.OpenApitargetingnet10.0 - (L1) Add dependency on
Microsoft.AspNetCore.OpenApifor .NET 10 OpenAPI support - (L1) Add dependency on
CoreIdent.Corefor access to endpoint models - (L2) Design OpenAPI configuration options:
public class CoreIdentOpenApiOptions { public string DocumentTitle { get; set; } = "CoreIdent API"; public string DocumentVersion { get; set; } = "v1"; public string OpenApiRoute { get; set; } = "/openapi/v1.json"; public bool IncludeXmlComments { get; set; } = true; public bool IncludeSecurityDefinitions { get; set; } = true; }
- (L2) Create extension method
AddCoreIdentOpenApi()forIServiceCollection - (L2) Create extension method
MapCoreIdentOpenApi()forIEndpointRouteBuilder - (L2) Configure OpenAPI document to include:
- All OAuth 2.0 and OIDC endpoints (token, authorize, userinfo, etc.)
- Passwordless endpoints (magic links, OTPs, WebAuthn)
- Token management endpoints
- Discovery endpoints (.well-known)
- JWKS endpoint
- Passkey endpoints (if enabled)
- (L2) Add proper security schemes:
client_secret_basicfor client authenticationclient_secret_postfor client authenticationauthorization_codeflow with PKCErefresh_tokenflowBearertoken authentication
- (L2) Add request/response examples for key endpoints
- (L2) Add descriptions from XML documentation to OpenAPI schemas
- (L1) Create new project
-
Component: Optional API Reference UI (Scalar)
- (L2) Do not ship a UI in CoreIdent packages (no Swashbuckle / no Swagger UI)
- (L2) Ensure the generated OpenAPI document is compatible with Scalar
- (L2) Document how a host app can add Scalar to serve the OpenAPI JSON (host-managed)
-
Documentation Updates:
- (L2) Update
Developer_Guide.mdwith OpenAPI setup instructions - (L2) Add OpenAPI configuration examples to README_Detailed.md
- (L2) Document security scheme usage in API documentation
- (L2) Document optional Scalar integration (no UI implementation in CoreIdent)
- (L2) Update
-
Test Cases:
- (L2) OpenAPI document builds without warnings
- (L2) All public endpoints are included in OpenAPI document
- (L2) Security schemes are properly defined and usable
- (L2) Smoke test:
GET /openapi/v1.jsonreturns 200 with valid OpenAPI document
-
Quality Gates:
- (L2) OpenAPI document passes validation (no schema errors)
- (L2) All examples in documentation are valid
- (L2) Security definitions match actual endpoint requirements
- (L2) CI build includes OpenAPI validation step
Goal: Enable any .NET application to authenticate against CoreIdent (or any OAuth/OIDC server) with minimal code.
Estimated Duration: 3-4 weeks
Prerequisites: Phase 1 complete (server-side passwordless)
- Component:
CoreIdent.ClientPackage- (L1) Create new project targeting
net10.0 - (L1) Define
ICoreIdentClientinterfacepublic interface ICoreIdentClient { Task<AuthResult> LoginAsync(CancellationToken ct = default); Task<AuthResult> LoginSilentAsync(CancellationToken ct = default); Task LogoutAsync(CancellationToken ct = default); Task<string?> GetAccessTokenAsync(CancellationToken ct = default); Task<ClaimsPrincipal?> GetUserAsync(CancellationToken ct = default); bool IsAuthenticated { get; } event EventHandler<AuthStateChangedEventArgs>? AuthStateChanged; }
- (L1) Define
CoreIdentClientOptionspublic class CoreIdentClientOptions { public string Authority { get; set; } = string.Empty; public string ClientId { get; set; } = string.Empty; public string? ClientSecret { get; set; } public string RedirectUri { get; set; } = string.Empty; public string PostLogoutRedirectUri { get; set; } = string.Empty; public IEnumerable<string> Scopes { get; set; } = ["openid", "profile"]; public bool UsePkce { get; set; } = true; public bool UseDPoP { get; set; } = false; public TimeSpan TokenRefreshThreshold { get; set; } = TimeSpan.FromMinutes(5); }
- (L1) Create new project targeting
- Component: Token Storage Abstraction
- (L1) Define
ISecureTokenStorageinterfacepublic interface ISecureTokenStorage { Task StoreTokensAsync(TokenSet tokens, CancellationToken ct = default); Task<TokenSet?> GetTokensAsync(CancellationToken ct = default); Task ClearTokensAsync(CancellationToken ct = default); }
- (L1) Implement
InMemoryTokenStorage(default, non-persistent) - (L2) Implement
FileTokenStorage(encrypted file, for console apps)
- (L1) Define
- Component: Browser Abstraction
- (L1) Define
IBrowserLauncherinterfacepublic interface IBrowserLauncher { Task<BrowserResult> LaunchAsync(string url, string redirectUri, CancellationToken ct = default); }
- (L3) Implement
SystemBrowserLauncher(opens default browser, listens on localhost)
- (L1) Define
- Component: OAuth/OIDC Flow Implementation
- (L3) Implement Authorization Code + PKCE flow
- (L2) Implement token refresh logic
- (L2) Implement logout (revocation + optional end session when available)
- (L2) Handle discovery document fetching and caching
- Test Case (Unit):
- (L2) PKCE code verifier/challenge generation is correct
- (L2) Token refresh triggers before expiry
- (L2) State parameter prevents CSRF
- Test Case (Integration):
- (L3) Full login flow against CoreIdent test server
- (L2) Token refresh works correctly
- (L1) Logout clears tokens
Decision: Implement early in Phase 1.5 as shared testing infrastructure for client libraries.
-
Status: ✅ Implemented (L1/L2/L3)
-
Rationale: Most client features can be tested headlessly, but browser/E2E infrastructure is needed to validate real redirect-based flows, storage behavior in-browser, and future WebAuthn/passkey work. Building this once and reusing it across client packages reduces regression risk and avoids platform-specific ad-hoc harnesses.
-
Testing tiers (design goal):
- (Tier 1) Unit tests (pure logic: PKCE, URL building, token parsing, claim merging)
- (Tier 2) Headless integration tests (real HTTP against a local CoreIdent test host; no real browser UI)
- (Tier 3) Browser-driven E2E (Playwright; real redirect + callback; used for a small set of smoke tests)
-
Component: Tooling decision
- (L2) Standardize on Playwright as the primary browser automation tool (single supported harness)
- (L1) Document local setup + CI prerequisites (browsers, env vars, timeouts, headless/headful)
-
Component:
CoreIdent.Testingbuilding blocks- (L2) Create
CoreIdent.Testing.Hosthelpers to start a CoreIdent server for tests (in-proc or local port) - (L2) Create
CoreIdent.Testing.Httphelpers (assertions for discovery, token, userinfo, revocation) - (L2) Create
CoreIdent.Testing.Browserhelpers for redirect-based flows:- (L2) Playwright fixture + deterministic tracing/screenshot capture on failure
- (L2) Helpers for callback listener (localhost redirect URI) and URL wait conditions
- (L2) "Headless authorize" harness (non-UI) for fast CI coverage when the server supports it
- (L2) Create
-
Component: OAuth/OIDC E2E coverage (CoreIdent + external providers)
- (L2) Authorization Code + PKCE flow works end-to-end against CoreIdent test host
- (L2) Token refresh behavior works end-to-end (refresh threshold + rotation scenarios)
- (L2) Logout works (revocation + end session when advertised)
- (L2) UserInfo behavior is correct (scope-gated claims, reserved claims filtering)
- (L3) External provider smoke lane (Keycloak or other containerized provider) for cross-provider parity
-
Component: WebAuthn/Passkey E2E (future expansion)
- (L3) Implement passkey E2E tests using Playwright virtual authenticator (where supported)
-
CI strategy:
- (L2) Run Tier 1 + Tier 2 on every PR
- (L1) Run Tier 3 browser smoke tests on a dedicated lane (nightly and/or required on main)
- (L1) Keep Tier 3 small and stable; favor headless integration tests for most coverage
-
Quality gates:
- (L2) Tests produce deterministic diagnostics (traces/logs) on failure
- (L2) Timeouts are explicit and bounded; tests fail fast and do not hang CI
- (L1) Document supported CI runners and what platforms are required for MAUI/WPF UI automation (optional)
- Component:
CoreIdent.Client.MauiPackage- (L1) Create project targeting
net10.0-android;net10.0-ios;net10.0-maccatalyst - (L3) Implement
MauiSecureTokenStorageusingSecureStorage - (L3) Implement
MauiBrowserLauncherusingWebAuthenticator - (L1) Add
UseCoreIdentClient()extension forMauiAppBuilder
- (L1) Create project targeting
- Test Case:
- (L2) Tokens persist across app restarts
- (L3) WebAuthenticator flow completes successfully
- Test Case (Integration):
- (L2) Tier 2 headless integration: MAUI client logs in against CoreIdent test host using CoreIdent.Testing.Host + headless authorize harness (1.5.2)
- (L3) Tier 3 UI automation (optional): device/simulator smoke test for WebAuthenticator redirect roundtrip (1.5.2)
- Documentation:
- (L1) MAUI integration guide with sample app
- Component:
CoreIdent.Client.WpfPackage- (L1) Create project targeting
net10.0-windows - (L3) Implement
DpapiTokenStorageusing Windows DPAPI - (L3) Implement
WebView2BrowserLauncher(embedded browser) - (L2) Implement
SystemBrowserLauncher(external browser with localhost callback)
- (L1) Create project targeting
- Test Case:
- (L2) DPAPI storage encrypts/decrypts correctly
- (L3) WebView2 flow works
- Test Case (Integration):
- (L2) Tier 2 integration: SystemBrowserLauncher login against CoreIdent test host with localhost callback (1.5.2)
- (L3) Tier 3 UI automation (Windows runner): WebView2 flow completes (1.5.2)
- Documentation:
- (L1) WPF/WinForms integration guide
- Component:
CoreIdent.Client.ConsolePackage- (L1) Create project targeting
net10.0 - (L3) Implement
EncryptedFileTokenStorage - (L2) Implement device code flow support (for headless scenarios)
- (L1) Create project targeting
- Test Case:
- (L2) Device code flow works
- (L3) File storage is encrypted
- Test Case (Integration):
- (L2) Tier 2 integration: device code flow against CoreIdent test host (1.5.2)
- (L2) Tier 2 integration: token refresh + logout against CoreIdent test host (1.5.2)
- Documentation:
- (L1) Console/CLI app integration guide
- Component:
CoreIdent.Client.BlazorPackage- (L1) Create project targeting
net10.0 - (L3) Implement
BrowserStorageTokenStorageusinglocalStorage/sessionStorage - (L3) Integrate with Blazor's
AuthenticationStateProvider
- (L1) Create project targeting
- Test Case:
- (L2) Auth state propagates to Blazor components
- (L2) Token refresh works in browser
- Test Case (Integration):
- (L2) Tier 2 integration: Blazor WASM login in Playwright against CoreIdent test host (1.5.2)
- (L2) Tier 3 browser smoke: token refresh + logout in real browser (1.5.2)
- Documentation:
- (L1) Blazor WASM integration guide
Goal: Seamless integration with third-party OAuth/OIDC providers.
Estimated Duration: 2-3 weeks
Prerequisites: Phase 1.5 complete
- Component:
CoreIdent.Providers.AbstractionsPackage- (L1) Create new project
- (L2) Define
IExternalAuthProviderinterface - (L2) Define
ExternalAuthResultmodel - (L2) Define
ExternalUserProfilemodel
- Component: Account Linking
- (L1) Add
ExternalLoginentity to user model - (L3) Support linking multiple providers to one user
- (L3) Handle provider-to-user mapping
- (L1) Add
- Documentation:
- (L1) Document provider implementation guide
- Component:
CoreIdent.Providers.GooglePackage- (L1) Create new project
- (L3) Implement
IExternalAuthProviderfor Google - (L3) Handle OAuth flow with Google
- (L1) Map Google profile to
ExternalUserProfile
- Component: Configuration
- (L1) Create
GoogleProviderOptions(ClientId, ClientSecret, Scopes) - (L1) Add
AddGoogleProvider()extension method
- (L1) Create
- Test Case (Integration):
- (L1) Configuration validation works
- (Full flow requires manual testing or mock)
- Documentation:
- (L1) Add Google setup guide with screenshots
- Component:
CoreIdent.Providers.MicrosoftPackage- (L1) Create new project
- (L3) Implement for Microsoft/Entra ID
- (L3) Support both personal and work/school accounts
- Documentation:
- (L1) Add Microsoft/Entra setup guide
- Component:
CoreIdent.Providers.GitHubPackage- (L1) Create new project
- (L3) Implement for GitHub OAuth
- Documentation:
- (L1) Add GitHub setup guide
Goal: Production-grade OAuth 2.0 / OIDC server capabilities.
Estimated Duration: 4-5 weeks
Prerequisites: Phase 2 complete
- Component:
IKeyRotationService- (L1) Define interface for key rotation operations
- (L3) Implement automatic rotation based on schedule
- (L3) Support overlap period for old keys
- Component: Multiple Keys in JWKS
- (L3) Extend JWKS endpoint to return all active keys
- (L1) Include key expiry metadata
- Test Case:
- (L3) Old tokens remain valid during overlap period
- (L3) New tokens use new key
- (L3) JWKS contains both keys during rotation
- Component: Session Tracking
- (L1) Create
ISessionStoreinterface - (L2) Track active sessions per user
- (L1) Create
- Component: OIDC Logout Endpoint
- (L3) Implement
GET /auth/logout(end_session_endpoint) - (L3) Support
id_token_hint,post_logout_redirect_uri,state - (L3) Revoke associated tokens
- (L3) Implement
- Test Case:
- (L3) Logout invalidates session
- (L1) Logout redirects correctly
- Component: Registration Endpoint
- (L3) Implement
POST /auth/registerfor clients - (L3) Support initial access tokens for authorization
- (L1) Return client credentials
- (L3) Implement
- Test Case:
- (L3) Client can register and receive credentials
- (L1) Invalid registration is rejected
- Component: Device Authorization Endpoint
- (L3) Implement
POST /auth/device_authorization - (L1) Return device_code, user_code, verification_uri
- (L3) Implement
- Component: Device Token Endpoint
- (L3) Extend token endpoint for
urn:ietf:params:oauth:grant-type:device_code
- (L3) Extend token endpoint for
- Test Case:
- (L3) Device flow completes successfully
- (L3) Polling returns appropriate responses
- Component: PAR Endpoint
- (L3) Implement
POST /auth/par - (L1) Return request_uri
- (L3) Implement
- Component: Authorize Endpoint Extension
- (L3) Add
request_uriparameter support to authorize endpoint
- (L3) Add
- Test Case:
- (L3) PAR flow works end-to-end
- Component: DPoP Proof Validation
- (L3) Implement DPoP proof parsing and validation
- (L3) Validate
htm,htu,iat,jti, signature
- Component: Token Endpoint DPoP Support
- (L3) Add DPoP header acceptance to token endpoint
- (L3) Bind tokens to DPoP key
- Component: Token Validation DPoP Support
- (L3) Add DPoP proof validation to protected endpoints
- Component: Client Library DPoP Support
- (L3) Implement DPoP proof JWT creation (ES256) with
typ=dpop+jwtand public JWK header - (L3) Send
DPoPheader on token endpoint requests when enabled - (L3) Send
Authorization: DPoP <token>+DPoPproof (withath) for UserInfo when enabled - (L3) Handle
DPoP-Noncereplay/nonce requirements (header +use_dpop_nonceerrors) - (L3) Ensure DPoP key material lifecycle is handled safely (no leaks)
- (L3) Implement DPoP proof JWT creation (ES256) with
- Test Case:
- (L3) DPoP-bound token requires valid proof
- (L3) Token without DPoP is rejected if DPoP was used at issuance
- (L3) Client sends DPoP headers when
UseDPoP=true
- Component: Authorization Details Support
- (L3) Parse
authorization_detailsparameter - (L3) Store with authorization code
- (L3) Include in token claims
- (L3) Parse
- Test Case:
- (L3) Authorization details flow through to token
- Component: Token Exchange Endpoint
- (L3) Implement
POST /auth/tokenwithgrant_type=urn:ietf:params:oauth:grant-type:token-exchange - (L3) Support
subject_tokenandactor_token - (L3) Support token type indicators
- (L3) Implement
- Component: Exchange Policies
- (L1) Define
ITokenExchangePolicyinterface - (L3) Implement delegation policy
- (L3) Implement impersonation policy
- (L1) Define
- Test Case:
- (L3) Delegation exchange produces valid token
- (L3) Impersonation exchange includes
actclaim - (L3) Unauthorized exchanges are rejected
- Documentation:
- (L1) Token exchange guide with use cases
- Component: Request Object Support
- (L3) Parse
requestparameter (JWT) - (L3) Validate signature against registered client keys
- (L3) Support
request_urifor remote request objects
- (L3) Parse
- Component: Encryption Support (Optional)
- (L3) Decrypt JWE request objects
- Test Case:
- (L3) Signed request object is validated
- (L3) Invalid signature is rejected
- Documentation:
- (L1) JAR implementation guide
- Component:
IWebhookServiceInterface- (L1) Define webhook event types
- (L3) Define delivery mechanism
- Component: Webhook Configuration
- (L1) Per-event endpoint configuration
- (L3) Secret for signature verification
- (L2) Retry policy configuration
- Component: Event Types
- (L1)
user.created,user.updated,user.deleted - (L1)
user.login.success,user.login.failed - (L1)
token.issued,token.revoked - (L1)
consent.granted,consent.revoked - (L1)
client.created,client.updated
- (L1)
- Component: Delivery
- (L2) HTTP POST with JSON payload
- (L3) HMAC signature header
- (L3) Exponential backoff retry
- Test Case:
- (L3) Webhooks fire on events
- (L3) Retry logic works correctly
- (L3) Signature verification works
- Documentation:
- (L1) Webhook integration guide
- Component: Conformance Test Integration
- (L3) Set up OIDC conformance test suite
- (L1) Document test results
- (L3) Fix any conformance issues
- Documentation:
- (L1) Publish conformance status
Goal: Provide a first-class “revocable access token” story for distributed resource servers that you control. This complements Phase 0’s revocation + introspection endpoints.
- Component: Resource Server Validation Package
- (L2) Create
CoreIdent.ResourceServerpackage - (L3) Implement introspection-based authentication handler/middleware (RFC 7662) for APIs
- (L3) Add caching strategy and guidance (fail-closed by default; configurable TTL; protect introspection endpoint)
- (L2) Create
- Component: Optional Opaque/Reference Access Tokens
- (L3) Add configuration to issue opaque/reference access tokens (instead of JWT) for APIs that require immediate revocation
- (L3) Ensure introspection becomes the validation path for opaque tokens
- Test Case (Integration):
- (L3) Revoked access token becomes inactive via introspection across services
- (L3) Cache behaves correctly (revocation latency bounded by cache TTL)
- Documentation:
- (L2) Document validation modes: offline JWT vs introspection vs opaque/reference tokens
- (L2) Document when to choose which mode (embedded vs distributed)
Goal: Optional UI components for common flows.
Estimated Duration: 3-4 weeks
Prerequisites: Phase 3 complete
- Component: Package Setup
- (L1) Create Razor Class Library project
- (L2) Define themeable components
- Component: Login Page
- (L2) Username/password form
- (L2) Passwordless options (email, passkey)
- (L2) External provider buttons
- Component: Registration Page
- (L2) Registration form
- (L3) Email verification flow
- Component: Consent Page
- (L2) Scope display
- (L1) Allow/Deny buttons
- Component: Account Management
- (L2) Change email
- (L2) Manage passkeys
- (L2) View active sessions
- Documentation:
- (L1) UI customization guide
- Component: Account Settings
- (L3) Change email (with verification)
- (L3) Change password
- (L3) Enable/disable MFA
- Component: Session Management
- (L2) List active sessions (device, location, time)
- (L3) Revoke individual sessions
- (L3) "Sign out everywhere" option
- Component: Linked Accounts
- (L2) View linked external providers
- (L2) Link new provider
- (L3) Unlink provider (if other auth method exists)
- Component: Activity Log
- (L2) View own login history
- (L1) View consent grants
- (L2) View security events
- Test Case:
- (L2) User can manage own account
- (L3) Session revocation works
- Documentation:
- (L1) User portal customization guide
- Component: User Management Endpoints
- (L2) CRUD operations for users
- (L2) Search and pagination
- Component: Client Management Endpoints
- (L2) CRUD operations for clients
- Component: Authorization
- (L3) Admin role/scope requirements
- Documentation:
- (L1) Admin API reference
- Component: Tenant Model
- (L1)
CoreIdentTenantentity - (L2) Tenant-scoped configuration
- (L1)
- Component: Tenant Resolution
- (L1)
ITenantResolverinterface - (L2) Host-based resolution (subdomain)
- (L2) Path-based resolution
- (L1) Header-based resolution
- (L1)
- Component: Tenant Isolation
- (L3) Per-tenant signing keys
- (L3) Per-tenant user stores
- (L2) Per-tenant client registrations
- Component: Tenant Configuration
- (L2) Per-tenant branding (logo, colors)
- (L2) Per-tenant enabled providers
- (L2) Per-tenant policies
- Test Case:
- (L3) Tenants are isolated
- (L3) Cross-tenant access is prevented
- Documentation:
- (L1) Multi-tenancy setup guide
Goal: Extended capabilities for specialized use cases.
Estimated Duration: Ongoing
- Component: TOTP Support (L2)
- Component: Backup Codes (L2)
- Component: MFA Enforcement Policies (L2)
- Component: FGA/RBAC Hooks (L3)
- Component: Policy evaluation interface (L2)
- Component:
IAuditLoggerInterface (L1) - Component: Structured event logging (L2)
- Component: Default console/file implementation (L1)
- Component: SCIM User endpoints (L3)
- Component: SCIM Group endpoints (L3)
- Component:
CoreIdent.Identity.Spiffepackage (L2) - Component: Workload identity validation (L3)
- Component: SVID integration (L3)
- Component: Device Fingerprinting
- (L2) Collect device characteristics
- (L2) Store known devices per user
- (L1) Flag unknown devices
- Component: Geo-location Checks
- (L2) IP-based location lookup
- (L3) Impossible travel detection
- (L2) Location-based policies
- Component: Step-up Authentication
- (L2) Define step-up triggers
- (L2) Force MFA for sensitive operations
- (L2) Re-authentication prompts
- Component: Risk Scoring
- (L1)
IRiskScorerinterface - (L2) Configurable risk thresholds
- (L1)
- Test Case:
- (L2) Unknown device triggers step-up
- (L3) Impossible travel is detected
- Documentation:
- (L1) Risk-based auth configuration guide
- Component: HaveIBeenPwned Integration
- (L3) k-Anonymity API integration
- (L1) Check on registration
- (L1) Check on password change
- (L2) Optional check on login
- Component: Policy Configuration
- (L1) Block compromised passwords
- (L1) Warn but allow
- (L3) Force password change
- Component: Alerts
- (L3) Notify user of compromised credential
- (L1) Admin notification option
- Test Case:
- (L3) Known compromised password is detected
- (L3) Policy enforcement works
- Documentation:
- (L1) Breach detection setup guide
- Component: YARP Integration Examples
- (L3) Token validation middleware
- (L3) Token transformation
- (L2) Rate limiting integration
- Component: Token Exchange for Downstream
- (L3) Exchange external token for internal
- (L2) Scope downgrade for microservices
- Documentation:
- (L1) API gateway patterns guide
- Component:
CoreIdent.Client.BlazorServerPackage- (L3) Circuit-aware token storage
- (L3) Automatic token refresh in circuit
- (L3) Handle circuit disconnection gracefully
- Component: Server-side Session
- (L2) Session state management
- (L2) Distributed cache support
- Component: AuthenticationStateProvider
- (L2) Custom provider for server-side Blazor
- (L2) Cascading auth state
- Test Case:
- (L3) Auth persists across circuit reconnection
- (L2) Token refresh works in background
- Documentation:
- (L1) Blazor Server integration guide
- Component: W3C VC issuance (L3)
- Component: VC verification (L3)
This summary is shown near the top of the document.
The following items are tracked here for completeness and cross-referencing:
- (L2) JWKS Endpoint (now with asymmetric keys) — Covered in Feature 0.2
- (L2) JWT Access Tokens — Covered in Feature 0.2 (JwtTokenService)
- (L2) Refresh Tokens — Covered in Features 0.4-0.5
- (L3) Refresh Token Rotation & Family Tracking — Covered in Feature 0.5
- (L3) Token Theft Detection — Covered in Feature 0.5
- (L2) Client Credentials Flow — Covered in Feature 0.5
- (L3) OAuth2 Authorization Code Flow with PKCE — Covered in Feature 1.7
- (L2) ID Token Issuance — Covered in Feature 1.7 (OIDC ID token)
- (L2) OIDC Discovery Endpoint — Covered in Feature 0.4.2
- (L2) OIDC UserInfo Endpoint — Covered in Feature 1.10
- (L2) User Consent Mechanism — Covered in Feature 1.8
- (L2) EF Core Storage Provider — Covered in Features 0.3-0.4 (EfClientStore, EfScopeStore, etc.)
- (L2) Delegated User Store Adapter — Covered in Feature 1.9
- (L2) User Registration Endpoint — Covered in Feature 1.11
- (L2) User Login Endpoint — Covered in Feature 1.11
- (L2) User Profile Endpoint — Covered in Feature 1.11
- (L2) Password Grant (ROPC) — Covered in Feature 1.12
- (L1) Custom Claims Provider — Covered in Feature 0.5
Note: Many items are now explicitly covered in Phase 0 features and referenced here for clarity.
| Feature | Reason |
|---|---|
| Web3 Wallet Login | Niche adoption |
| LNURL-auth | Very niche |
| AI Framework SDK Integrations | Premature |
| CIBA for AI Actions | Specialized |
| Token Vault / Secrets Management | Out of scope |