Skip to content

Latest commit

 

History

History
1517 lines (1327 loc) · 79.3 KB

File metadata and controls

1517 lines (1327 loc) · 79.3 KB

CoreIdent: Completed Development Tasks

This document archives the completed task-level detail from DEVPLAN.md. It preserves all checkboxes, code snippets, test cases, guidance notes, and documentation items for reference.

Maintaining this document

This file is paired with DEVPLAN.md. When a feature is fully complete (every checkbox checked, all tests passing, no remaining concerns):

  1. Move the entire feature section from DEVPLAN.md into this file, under the appropriate phase heading. Preserve all detail: checkboxes, code snippets, guidance notes, test cases, documentation items.
  2. Replace the moved section in DEVPLAN.md with a compact summary linking back here (see the maintenance instructions at the top of DEVPLAN.md for the template).

Keep features in chronological/phase order. Do not edit archived content unless correcting factual errors.

Phase 0: Foundation Reset — COMPLETE

Goal: Establish production-ready cryptographic foundation, essential token lifecycle endpoints, and robust test infrastructure.

Estimated Duration: 3-4 weeks

Prerequisites: .NET 10 SDK installed

Phase 0 Milestones (to keep scope executable)

  • 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)

Feature 0.1: .NET 10 Migration

  • Component: Solution & Project Setup
    • (L1) Create CoreIdent.sln solution file
    • (L1) Create CoreIdent.Core.csproj targeting net10.0
    • (L1) Create CoreIdent.Storage.EntityFrameworkCore.csproj targeting net10.0
    • (L1) Create CoreIdent.Adapters.DelegatedUserStore.csproj targeting net10.0
    • (L1) Create test projects targeting net10.0
    • (L2) Configure NuGet package references for .NET 10
      • Microsoft.AspNetCore.Authentication.JwtBearer → 10.x
      • Microsoft.Extensions.Identity.Core → 10.x
      • Microsoft.EntityFrameworkCore → 10.x
      • Microsoft.IdentityModel.Tokens → latest stable
  • Component: C# 14 Features
    • (L1) Enable C# 14 in all projects (<LangVersion>14</LangVersion>)
    • (L2) Add ClaimsPrincipalExtensions using extension members syntax
  • Test Case:
    • (L1) Solution builds without warnings on .NET 10
  • Documentation:
    • (L1) Update README.md with .NET 10 requirement

Feature 0.2: Asymmetric Key Support (RS256/ES256)

  • Component: ISigningKeyProvider Interface
    • (L1) Create CoreIdent.Core/Services/ISigningKeyProvider.cs
      public 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);
  • Component: CoreIdentKeyOptions Configuration
    • (L1) Create CoreIdent.Core/Configuration/CoreIdentKeyOptions.cs
      public 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 }
  • Component: RsaSigningKeyProvider Implementation
    • (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
  • Component: EcdsaSigningKeyProvider Implementation
    • (L3) Create CoreIdent.Core/Services/EcdsaSigningKeyProvider.cs
      • Guidance: Support ES256 (P-256 curve)
      • Guidance: Similar loading patterns as RSA
  • Component: SymmetricSigningKeyProvider Implementation (Legacy/Dev)
    • (L2) Create CoreIdent.Core/Services/SymmetricSigningKeyProvider.cs
      • Guidance: Implement HS256 logic (for dev/testing only)
      • Guidance: Log deprecation warning when used
  • Component: JwtTokenService
    • (L2) Create JwtTokenService using ISigningKeyProvider
    • (L2) Use SigningCredentials from provider for all token generation
    • (L2) Include kid claim in JWT header
  • Component: JWKS Endpoint
    • (L2) Create DiscoveryEndpointsExtensions.cs with JWKS endpoint using ISigningKeyProvider.GetValidationKeysAsync()
    • (L3) Return proper RSA key format (kty: "RSA", n, e, kid, use: "sig", alg)
    • (L2) Support multiple keys in JWKS (for rotation)
  • 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
  • Test Case (Unit):
    • (L2) RsaSigningKeyProvider loads key from PEM file correctly
    • (L2) RsaSigningKeyProvider loads key from PEM string correctly
    • (L2) RsaSigningKeyProvider generates key when none configured
    • (L2) EcdsaSigningKeyProvider loads ES256 key correctly
    • (L1) Generated tokens include kid in header
    • (L2) JWKS endpoint returns valid RSA public key structure
  • 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

Feature 0.3: Client Store & Model

  • Component: CoreIdentClient Model
    • (L1) Create CoreIdent.Core/Models/CoreIdentClient.cs
      public 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 }
  • Component: IClientStore Interface
    • (L1) Create CoreIdent.Core/Stores/IClientStore.cs
      public 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);
      }
  • Component: InMemoryClientStore
    • (L2) Create in-memory implementation using ConcurrentDictionary
    • (L2) Support seeding clients at startup
  • Component: EfClientStore
    • (L2) Create EF Core implementation in CoreIdent.Storage.EntityFrameworkCore
    • (L1) Add ClientEntity entity configuration to CoreIdentDbContext
  • Component: Client Secret Hashing
    • (L2) Create IClientSecretHasher interface and DefaultClientSecretHasher implementation
    • (L2) Use secure hashing (PBKDF2 with SHA256) for client secrets
  • Component: DI Registration
    • (L1) Add AddInMemoryClientStore() extension method
    • (L1) Add AddInMemoryClients(IEnumerable<CoreIdentClient>) extension
    • (L1) Add AddEntityFrameworkCoreClientStore() extension method
  • Test Case (Unit):
    • (L1) InMemoryClientStore CRUD operations work correctly
    • (L1) Client secret validation works correctly
    • (L1) EfClientStore CRUD operations work correctly

Feature 0.4: Scope & Core Models

  • Component: CoreIdentScope Model
    • (L1) Create CoreIdent.Core/Models/CoreIdentScope.cs
      public 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; } = [];
      }
  • Component: IScopeStore Interface
    • (L1) Create CoreIdent.Core/Stores/IScopeStore.cs
      public 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);
      }
  • 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: CoreIdentRefreshToken Model
    • (L1) Create CoreIdent.Core/Models/CoreIdentRefreshToken.cs
      public 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;
      }
  • Component: IRefreshTokenStore Interface (Full)
    • (L1) Expand CoreIdent.Core/Stores/IRefreshTokenStore.cs
      public 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);
      }
  • Component: InMemoryRefreshTokenStore
    • (L2) Create in-memory implementation with ConcurrentDictionary
  • Component: EfRefreshTokenStore
    • (L2) Create EF Core implementation
    • (L1) Add entity + DbContext schema configuration (migrations are app-owned)
    • (L2) Inject TimeProvider for testability (consistent with in-memory store)
  • Component: Standard Scope Helpers
    • (L1) Create StandardScopes static class with predefined OIDC scopes
  • Component: DI Registration
    • (L1) Add AddScopeStore() and AddRefreshTokenStore() extension methods
    • (L1) Add AddInMemoryScopes(IEnumerable<CoreIdentScope>) extension
  • Test Case (Unit):
    • (L1) Scope store operations work correctly
    • (L1) Refresh token store CRUD and family revocation work correctly
  • Documentation:
    • (L1) Document scope configuration

Feature 0.4.1: Core Registration & Routing (Unambiguous Host Integration)

  • Component: CoreIdentOptions Configuration
    • (L1) Create CoreIdentOptions with 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 ITokenService as a low-level primitive (caller provides issuer/audience/expires). Higher-level endpoints/features should read from IOptions<CoreIdentOptions> and pass values into ITokenService.
    • (L1) Add startup validation (fail fast)
      • Guidance: Validate required fields (issuer/audience), validate lifetimes are positive
  • Component: CoreIdentRouteOptions
    • (L1) Create route options to remove all ambiguity around endpoint mapping
      • Guidance: Include BasePath (default /auth), TokenPath (default token)
      • Guidance: Include root-relative DiscoveryPath (default /.well-known/openid-configuration)
      • Guidance: Include root-relative JwksPath (default /.well-known/jwks.json)
      • Guidance: Include ConsentPath (default consent, relative to BasePath) for future consent UI
      • Guidance: Include UserInfoPath (default userinfo, relative to BasePath) 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 BasePath changes.
      • 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).
  • Component: DI Registration (AddCoreIdent)
    • (L2) Create AddCoreIdent() extension method that registers:
      • CoreIdentOptions + validation
      • CoreIdentRouteOptions
      • ITokenService and related core services
      • Default stores when not overridden (in-memory)
      • Guidance: Provide parameterless and parameterized overloads (e.g. AddCoreIdent() and AddCoreIdent(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., via appsettings.json binding to CoreIdentOptions). Validation will fail at startup if required values are missing.
    • (L1) Document registration order for EF Core
      • Guidance: AddCoreIdent() -> AddDbContext(...) -> AddEntityFrameworkCore*Store()
  • Component: Endpoint Mapping (MapCoreIdentEndpoints)
    • (L2) Create MapCoreIdentEndpoints() extension method that maps all CoreIdent endpoints
      • Guidance: Map endpoints under BasePath unless 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.
  • 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 (ignoring BasePath)
        • Base-path endpoints respond under the configured default BasePath

Feature 0.4.2: OIDC Discovery Metadata (Unambiguous /.well-known/openid-configuration)

  • Component: Discovery Document Endpoint
    • (L2) Add /.well-known/openid-configuration endpoint
      • Guidance: Always root-relative, ignore BasePath
      • Guidance: issuer must exactly match configured CoreIdentOptions.Issuer
      • Guidance: Advertise endpoints using CoreIdentRouteOptions (token, revocation, introspection, JWKS)
      • Guidance: Include supported grant_types_supported based on implemented features
      • Guidance: Include scopes_supported based on IScopeStore.GetAllAsync() (filter ShowInDiscoveryDocument)
      • Guidance: Include signing algs from ISigningKeyProvider.Algorithm
  • 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

Feature 0.4.3: User Model & Store Foundation (Required for All User-Based Flows)

  • Component: CoreIdentUser Model
    • (L1) Create CoreIdent.Core/Models/CoreIdentUser.cs
      • Guidance: Include at minimum: Id, UserName, NormalizedUserName, CreatedAt, UpdatedAt
  • Component: IUserStore Interface
    • (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)
  • Component: In-Memory User Store
    • (L2) Create InMemoryUserStore using ConcurrentDictionary
      • Guidance: Normalize usernames consistently
  • Component: EF Core User Store
    • (L2) Create EfUserStore in CoreIdent.Storage.EntityFrameworkCore
    • (L1) Add UserEntity + DbContext configuration
  • Component: Password Hashing
    • (L1) Create IPasswordHasher interface 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
  • Component: DI Registration
    • (L1) Add AddInMemoryUserStore() extension method
    • (L1) Add AddEntityFrameworkCoreUserStore() extension method
  • Test Case (Unit):
    • (L1) InMemoryUserStore CRUD operations work correctly
    • (L1) EfUserStore CRUD operations work correctly

Feature 0.5: Token Issuance Endpoint

  • Component: Token Endpoint
    • (L3) Create POST /auth/token endpoint in TokenEndpointExtensions.cs
      • Guidance: Support grant_type=client_credentials
      • Guidance: Support grant_type=refresh_token
      • Guidance: grant_type=authorization_code is 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)
  • Component: Token Response Models
    • (L1) Create TokenRequest record
    • (L1) Create TokenResponse record
      public record TokenResponse(
          string AccessToken,
          string TokenType,
          int ExpiresIn,
          string? RefreshToken = null,
          string? Scope = null,
          string? IdToken = null
      );
  • Component: Token Service Enhancement
    • (L2) Extend ITokenService to support scope claims
    • (L2) Add jti claim generation for all tokens
    • (L2) Add configurable token lifetimes per client
  • Component: Custom Claims Provider
    • (L1) Create ICustomClaimsProvider interface
      • Guidance: Provide a hook to add/transform claims based on subject, client, and granted scopes
    • (L2) Integrate ICustomClaimsProvider into token issuance
  • 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/token with client_credentials returns access token
    • (L2) POST /auth/token with refresh_token returns 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)
  • Documentation:
    • (L1) Document token endpoint usage
    • (L2) Document refresh token rotation behavior
    • (L1) Document client configuration options (from Feature 0.3)

Feature 0.6: Token Revocation Endpoint (RFC 7009)

Status: Access token revocation complete. Refresh token revocation to be completed after Feature 0.5 (Token Issuance).

  • Component: ITokenRevocationStore Interface
    • (L1) Create CoreIdent.Core/Stores/ITokenRevocationStore.cs
      public 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);
      }
  • Component: InMemoryTokenRevocationStore
    • (L2) Create in-memory implementation using ConcurrentDictionary
    • (L2) Implement automatic cleanup of expired entries
  • Component: EfTokenRevocationStore
    • (L2) Create EF Core implementation in CoreIdent.Storage.EntityFrameworkCore
    • (L1) Add RevokedToken entity to CoreIdentDbContext
    • (L2) Inject TimeProvider for testability (consistent with in-memory store)
  • Component: Revocation Endpoint
    • (L3) Create POST /auth/revoke endpoint in TokenManagementEndpointsExtensions.cs
      • Guidance: Accept token and optional token_type_hint parameters
      • 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.
  • Component: Token Validation Integration
    • (L3) Create token validation middleware that checks revocation store
    • (L3) Integrate ITokenRevocationStore check in protected endpoint middleware
  • Component: Revocation Endpoint Enhancement (Post-0.5)
    • (L2) Update revocation endpoint to use full IRefreshTokenStore for refresh token revocation
    • (L2) Validate client owns the token being revoked
  • Test Case (Unit):
    • (L1) InMemoryTokenRevocationStore stores and retrieves revocations correctly
    • (L1) Cleanup removes only expired entries
  • Test Case (Integration):
    • (L2) POST /auth/revoke with valid refresh token invalidates it (requires Feature 0.5)
    • (L2) POST /auth/revoke with 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
  • Documentation:
    • (L1) Add revocation endpoint to README.md
    • (L1) Document revocation behavior and client requirements

Feature 0.7: Token Introspection Endpoint (RFC 7662)

Note: Introspection of refresh tokens requires Feature 0.5 (Token Issuance) to be complete.

  • Component: Introspection Endpoint
    • (L3) Create POST /auth/introspect endpoint in TokenManagementEndpointsExtensions.cs
      • Guidance: Accept token and optional token_type_hint parameters
      • Guidance: Require client authentication (resource server credentials)
      • Guidance: Validate token signature, expiry, revocation status
      • Guidance: Check IRefreshTokenStore for 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"
        }
  • Component: Introspection Response Models
    • (L1) Create TokenIntrospectionRequest record
    • (L1) Create TokenIntrospectionResponse record
  • Test Case (Integration):
    • (L2) Valid access token returns active: true with 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)
  • Documentation:
    • (L1) Add introspection endpoint to README.md
    • (L2) Document resource server integration pattern

Feature 0.8: Test Infrastructure Overhaul

Note: Entity builders (UserBuilder, ClientBuilder, ScopeBuilder) require Features 0.3-0.4 to be complete.

  • Component: CoreIdent.Testing Package
    • (L1) Create new project tests/CoreIdent.Testing/CoreIdent.Testing.csproj
    • (L1) Add package references: xUnit, Shouldly, Microsoft.AspNetCore.Mvc.Testing
  • Component: CoreIdentWebApplicationFactory
    • (L3) Create CoreIdent.Testing/Fixtures/CoreIdentWebApplicationFactory.cs
      • Guidance: Encapsulate SQLite in-memory setup
      • Guidance: Provide ConfigureTestServices hook
      • Guidance: Provide SeedDatabase hook
      • Guidance: Auto-seed standard OIDC scopes
      • Guidance: Handle connection lifecycle properly
  • Component: CoreIdentTestFixture Base 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()
  • Component: Fluent Builders
    • (L2) Create CoreIdent.Testing/Builders/UserBuilder.cs
      • Guidance: Fluent API: .WithEmail(), .WithPassword(), .WithClaim()
    • (L2) Create CoreIdent.Testing/Builders/ClientBuilder.cs
      • Guidance: Fluent API: .WithClientId(), .WithSecret(), .AsPublicClient(), .AsConfidentialClient()
    • (L1) Create CoreIdent.Testing/Builders/ScopeBuilder.cs
  • Component: Assertion Extensions
    • (L2) Create CoreIdent.Testing/Extensions/JwtAssertionExtensions.cs
      • Guidance: .ShouldBeValidJwt(), .ShouldHaveClaim(), .ShouldExpireAfter()
    • (L1) Create CoreIdent.Testing/Extensions/HttpResponseAssertionExtensions.cs
      • Guidance: .ShouldBeSuccessful(), .ShouldBeUnauthorized(), .ShouldBeBadRequest()
  • 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)
  • Component: Integration Test Setup
    • (L2) Create CoreIdent.Integration.Tests project using new fixtures
    • (L2) Write initial integration tests using builders
  • 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)

Feature 0.9: OpenTelemetry Metrics Integration

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) for coreident.token.issued metric.

  • Component: Metrics Instrumentation
    • (L2) Integrate with .NET 10's built-in Microsoft.AspNetCore.Authentication metrics
    • (L2) Integrate with Microsoft.AspNetCore.Identity metrics (user ops, sign-ins, 2FA)
    • (L2) Add CoreIdent-specific metrics:
      • coreident.token.issued — Tokens issued (by type)
      • coreident.token.revoked — Tokens revoked
      • coreident.client.authenticated — Client authentications
  • Component: Metrics Configuration
    • (L1) Add AddCoreIdentMetrics() extension method
    • (L2) Support filtering/sampling
  • Test Case:
    • (L2) Metrics are emitted for key operations
    • (L2) Metrics integrate with Aspire dashboard
  • Documentation:
    • (L1) Metrics and observability guide

Feature 0.10: CLI Tool (dotnet coreident)

Note: client add command 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: init Command
    • (L2) Scaffold appsettings.json with CoreIdent section
    • (L2) Generate secure random signing key (for dev)
    • (L1) Add package references to .csproj
  • Component: keys generate Command
    • (L2) Generate RSA key pair (PEM format)
    • (L2) Generate ECDSA key pair (PEM format)
    • (L1) Output to file or stdout
  • Component: client add Command
    • (L2) Interactive client registration
    • (L1) Generate client ID and secret
    • (L1) Output configuration snippet
  • Component: migrate Command
    • (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

Feature 0.11: Dev Container Configuration

  • 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)
  • Component: Codespaces Support
    • (L1) Test in GitHub Codespaces
    • (L1) Add "Open in Codespaces" badge to README
  • Documentation:
    • (L1) Contributing guide with dev container instructions

Phase 1: Passwordless & Developer Experience — COMPLETE

Goal: Make passwordless authentication trivially easy; establish the "5-minute auth" story.

Estimated Duration: 3-4 weeks

Prerequisites: Phase 0 complete


Feature 1.1: Email Magic Link Authentication

  • Component: IEmailSender Interface
    • (L1) Create CoreIdent.Core/Services/IEmailSender.cs
      public interface IEmailSender
      {
          Task SendAsync(EmailMessage message, CancellationToken ct = default);
      }
      
      public record EmailMessage(string To, string Subject, string HtmlBody, string? TextBody = null);
  • Component: SmtpEmailSender Implementation
    • (L2) Create default SMTP implementation
    • (L1) Support configuration via SmtpOptions (host, port, credentials, TLS)
  • Component: IPasswordlessTokenStore Interface
    • (L1) Create CoreIdent.Core/Stores/IPasswordlessTokenStore.cs
      public interface IPasswordlessTokenStore
      {
          Task<string> CreateTokenAsync(PasswordlessToken token, CancellationToken ct = default);
          Task<PasswordlessToken?> ValidateAndConsumeAsync(string token, CancellationToken ct = default);
          Task CleanupExpiredAsync(CancellationToken ct = default);
      }
  • Component: PasswordlessToken Model
    • (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
  • 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; }
      }
  • 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/start sends email (mock sender)
    • (L3) GET /auth/passwordless/email/verify with 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
  • 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

Feature 1.2: Passkey Integration (WebAuthn/FIDO2)

Note: .NET 10 provides built-in passkey support via IdentityPasskeyOptions and ASP.NET Core Identity. CoreIdent wraps this for minimal-API scenarios and adds convenience configuration.

  • Component: CoreIdentPasskeyOptions
    • (L2) Create wrapper around .NET 10's IdentityPasskeyOptions
      public 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
      }
  • Component: Passkey Service
    • (L1) Create IPasskeyService interface
    • (L2) Implement using .NET 10's built-in passkey support
    • (L2) Handle registration ceremony
    • (L2) Handle authentication ceremony
  • Component: Passkey Credential Storage
    • (L1) Create IPasskeyCredentialStore interface
    • (L1) Create PasskeyCredential model
    • (L2) Implement in-memory store
    • (L2) Implement EF Core store
  • 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
  • Component: DI Registration
    • (L1) Add AddPasskeys() extension method
  • 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

Feature 1.3: SMS OTP (Pluggable Provider)

  • Component: ISmsProvider Interface
    • (L1) Create CoreIdent.Core/Services/ISmsProvider.cs
      public interface ISmsProvider
      {
          Task SendAsync(string phoneNumber, string message, CancellationToken ct = default);
      }
  • 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
  • Component: OTP Generation and Storage
    • (L1) Reuse IPasswordlessTokenStore with SMS-specific token type
    • (L1) Generate 6-digit numeric OTP
  • 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)

Feature 1.4: F# Compatibility

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 out parameters in critical paths)
    • (L2) Create F# sample project using Giraffe/Saturn
    • (L2) Add F# template (coreident-api-fsharp)
    • (L1) Document F# usage patterns
  • Test Case:
    • (L1) F# sample project compiles and runs
    • (L2) All core interfaces are usable from F#
  • Documentation:
    • (L1) F# usage guide

Feature 1.5: dotnet new Templates

Note: We should support both C# and F# templates

  • Component: Template Package Structure
    • (L1) Create templates/ directory structure
    • (L1) Create CoreIdent.Templates.csproj for packaging
  • Component: coreident-api Template
    • (L2) Create minimal API template with CoreIdent auth
    • (L2) Include template.json with parameters (usePasswordless, useEfCore)
    • (L1) Include sample appsettings.json
  • Component: coreident-server Template
    • (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

Feature 1.6: Aspire Integration

  • Component: CoreIdent.Aspire Package
    • (L2) Create package targeting Aspire v13 (net10.0)
    • (L3) Provide AppHost integration via IDistributedApplicationBuilder extension methods (Aspire.Hosting)
  • Component: Dashboard Integration
    • (L2) OpenTelemetry metrics integration that includes CoreIdent System.Diagnostics.Metrics meter(s)
    • (L2) Structured logging integration guidance (OpenTelemetry logging)
    • (L2) Distributed tracing for auth flows (CoreIdent ActivitySource spans)
  • 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
  • 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

Feature 1.7: OAuth 2.0 Authorization Code Flow (PKCE Required)

  • Component: Authorization Code Model
    • (L1) Create CoreIdentAuthorizationCode model
      • Guidance: Include code handle, client_id, subject_id, redirect_uri, scopes, created/expires, consumed, nonce, code_challenge, code_challenge_method
  • Component: IAuthorizationCodeStore Interface
    • (L1) Create store interface
      • Guidance: CreateAsync, GetAsync, ConsumeAsync, CleanupExpiredAsync
  • Component: In-Memory Store
    • (L2) Implement InMemoryAuthorizationCodeStore using ConcurrentDictionary
  • Component: EF Core Store
    • (L2) Implement EfAuthorizationCodeStore in CoreIdent.Storage.EntityFrameworkCore
    • (L1) Add AuthorizationCodeEntity + DbContext configuration
  • Component: Authorization Code Cleanup
    • (L2) Add hosted service that periodically calls CleanupExpiredAsync
      • Guidance: Must be opt-out via options
  • 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 state round-trip
      • Guidance: Require authenticated user (HttpContext.User.Identity.IsAuthenticated); otherwise challenge
      • Guidance: Persist authorization code via IAuthorizationCodeStore
      • Guidance: Redirect back to client with code and state or error and error_description
  • Component: Token Endpoint (authorization_code grant)
    • (L3) Extend POST /auth/token to support grant_type=authorization_code
      • Guidance: Validate code exists and not expired/consumed
      • Guidance: Validate redirect_uri matches the one stored in the code
      • Guidance: Validate PKCE code_verifier against stored challenge
      • Guidance: Consume code atomically (single-use)
      • Guidance: Issue access token and (optionally) refresh token
  • Component: OpenID Connect ID Token (when openid scope is granted)
    • (L2) Issue id_token in token response for authorization_code when openid scope is granted
      • Guidance: Include nonce (if provided), set aud to client_id, and include scope-derived claims
      • Guidance: Use signing key provider / ITokenService consistently
  • 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

Feature 1.8: User Consent & Grants

  • Component: User Grant Model
    • (L1) Create CoreIdentUserGrant model
      • Guidance: Include subject_id, client_id, granted scopes, created/expires
  • Component: IUserGrantStore Interface
    • (L1) Create interface for consent persistence
      • Guidance: Include FindAsync(subjectId, clientId), SaveAsync(grant), RevokeAsync(...), HasUserGrantedConsentAsync(...)
  • Component: In-Memory Store
    • (L2) Implement InMemoryUserGrantStore
  • Component: EF Core Store
    • (L2) Implement EfUserGrantStore
    • (L1) Add UserGrantEntity + DbContext configuration
  • Component: Consent UX Endpoints
    • (L3) Implement GET /auth/consent (default minimal HTML)
      • Guidance: Must be replaceable by host app; driven by CoreIdentRouteOptions.ConsentPath
    • (L3) Implement POST /auth/consent to persist grant or deny
      • Guidance: Deny returns access_denied back to client redirect_uri
  • 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
  • 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

Feature 1.9: Delegated User Store Adapter (Integrate Existing User Systems)

  • Component: Delegated User Store Options
    • (L1) Create DelegatedUserStoreOptions with required delegates:
      • FindUserByIdAsync
      • FindUserByUsernameAsync
      • ValidateCredentialsAsync
      • optional: GetClaimsAsync
  • Component: DelegatedUserStore Implementation
    • (L2) Implement IUserStore via configured delegates
      • Guidance: Must never store password hashes; credential validation is delegated
  • 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
  • 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

Feature 1.10: OIDC UserInfo Endpoint

  • 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 IUserStore and/or ICustomClaimsProvider
      • Guidance: Return standard OIDC claims when present; omit claims not granted by scope
  • 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 profile scope, userinfo returns sub and profile claims
    • (L2) With openid email scope, userinfo returns email
  • Documentation:
    • (L1) Document userinfo endpoint behavior and scope-to-claims mapping

Feature 1.11: Resource Owner Endpoints (Register/Login/Profile)

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");
  • 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";
  • 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);
  • 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_uri provided)
    • (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() using sub claim
      • Get claims via IUserStore.GetClaimsAsync()
      • Call delegate if provided
      • Default JSON: { "id": "...", "email": "...", "claims": {...} }
      • Default HTML: Minimal profile display
  • Component: DI Registration

    • (L1) Add ConfigureResourceOwnerEndpoints(Action<CoreIdentResourceOwnerOptions>) extension
    • (L1) Integrate into MapCoreIdentEndpoints() pipeline
  • 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

Feature 1.12: Password Grant (Resource Owner Password Credentials)

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.Password case to TokenEndpointExtensions.HandleTokenRequest()
    • (L2) Validate username and password parameters
    • (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."
  • Component: Client Configuration

    • (L1) Add "password" as valid grant type in CoreIdentClient.AllowedGrantTypes
  • 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

Feature 1.13: Follow-Up Cleanup

Goal: Clean up inconsistencies, address technical debt, and ensure codebase quality before Phase 1.5.

Estimated Duration: 1-2 weeks


1.13.1: TimeProvider Consistency

  • Component: Replace DateTime.UtcNow with TimeProvider
    • (L2) InMemoryUserStore.cs — Replace DateTime.UtcNow with injected TimeProvider.GetUtcNow()
    • (L2) EfUserStore.cs — Replace DateTime.UtcNow with injected TimeProvider.GetUtcNow()
    • (L2) CliApp.cs — Replace DateTime.UtcNow in client creation (or accept as CLI-only exception)
    • (L2) PasswordlessEmailEndpointsExtensions.cs — Replace 2 instances with TimeProvider
    • (L2) PasswordlessSmsEndpointsExtensions.cs — Replace 2 instances with TimeProvider
    • (L2) ResourceOwnerEndpointsExtensions.cs — Replace instance with TimeProvider
    • (L1) Ensure TimeProvider is registered in DI (already done in ServiceCollectionExtensions.cs)
  • Test Case:
    • (L2) Unit tests can control time via FakeTimeProvider for user creation timestamps

1.13.2: Route Options Consistency

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 resolve TokenPath from IOptions<CoreIdentRouteOptions>
    • (L2) TokenManagementEndpointsExtensions.cs — Refactor to resolve RevocationPath, IntrospectionPath from options
    • (L2) ResourceOwnerEndpointsExtensions.cs — Refactor to resolve RegisterPath, LoginPath, ProfilePath from options
    • (L2) PasswordlessEmailEndpointsExtensions.cs — Refactor to resolve passwordless email paths from options (may need to add paths to CoreIdentRouteOptions)
    • (L2) PasswordlessSmsEndpointsExtensions.cs — Refactor to resolve passwordless SMS paths from options (may need to add paths to CoreIdentRouteOptions)
    • (L2) UserInfoEndpointExtensions.cs — Refactor to resolve UserInfoPath from options
    • (L2) ConsentEndpointExtensions.cs — Refactor to resolve ConsentPath from options
    • (L2) AuthorizationEndpointExtensions.cs — Refactor to resolve AuthorizePath from options
    • (L2) PasskeyEndpointsExtensions.cs — Refactor to resolve passkey paths from options (may need to add paths to CoreIdentRouteOptions)
  • Component: Extend CoreIdentRouteOptions if needed
    • (L2) Add PasswordlessEmailStartPath, PasswordlessEmailVerifyPath if not present
    • (L2) Add PasswordlessSmsStartPath, PasswordlessSmsVerifyPath if not present
    • (L2) Add passkey paths (PasskeyRegisterOptionsPath, PasskeyRegisterCompletePath, PasskeyAuthenticateOptionsPath, PasskeyAuthenticateCompletePath) if not present
  • Documentation:
    • (L1) Document route customization patterns in Developer_Guide.md if not already covered

1.13.3: Technical Debt from Technical_Plan.md

  • Component: RFC 7807 Problem Details
    • (L3) Audit error responses across all endpoints for consistency
    • (L3) Consider adopting Results.Problem() / ProblemDetails for error responses
    • (L2) Create CoreIdentProblemDetails helper 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?.Id or 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)

1.13.5: Version String and Documentation Path Cleanup (1.0 GA Preparation)

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 — Update PackageVersion from "0.4.0" to "1.0.0" (or make dynamic via assembly version)
    • (L2) Search for other hardcoded 0.4 version strings in codebase and update to 1.0
    • (L1) Update all .csproj files with <Version>1.0.0</Version> (or appropriate pre-release tag)
    • (L1) Update NuGet package metadata for 1.0 release
  • Component: Documentation Path Restructure
    • (L2) Move contents of docs/ version folder to docs/ 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
  • 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

1.13.6: Documentation Audit and Refresh

  • 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

1.13.7: Code Quality and Consistency

  • Component: Nullable Reference Type Audit
    • (L2) Ensure all projects have <Nullable>enable</Nullable>
    • (L2) Address any nullable warnings in CI build output
  • 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 format across solution
    • (L1) Address any formatting inconsistencies
  • Component: Unused Code Removal
    • (L2) Audit for unused using statements
    • (L2) Audit for dead code paths or commented-out code
  • Test Case:
    • (L1) CI build passes with zero warnings (or document accepted warnings)

1.13.8: Test Coverage Review

  • 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.Core line coverage is below 90%
  • 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

1.13.9: Additional Codebase Scan Follow-Ups

  • Component: OIDC Discovery Metadata Completeness

    • (L2) DiscoveryEndpointsExtensions.cs — Populate grant_types_supported instead 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
    • (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)
  • Test Case (Integration):

    • (L2) /.well-known/openid-configuration returns a non-empty grant_types_supported list matching implemented features
  • Component: Sync-over-Async Hotspots

    • (L2) DelegatedPasswordHasher.cs — Remove sync-over-async (GetAwaiter().GetResult()) when validating delegated credentials
      • Guidance: If IPasswordHasher must remain synchronous, introduce a dedicated synchronous delegate in DelegatedUserStoreOptions for password verification
      • Guidance: Alternatively, introduce an IAsyncPasswordVerifier abstraction and adapt the token endpoint to use it
    • (L2) Remove CancellationToken.None usage in the delegated password verification path where feasible
  • 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
    • (L2) Replace Console.WriteLine in default providers with ILogger (or ensure these providers are explicitly dev-only and opt-in)
  • 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
  • Test Case:

    • (L2) Malformed Basic auth headers reliably return invalid_client without throwing and without leaking secrets

Feature 1.13.10: OpenAPI Documentation

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.OpenApi targeting net10.0
    • (L1) Add dependency on Microsoft.AspNetCore.OpenApi for .NET 10 OpenAPI support
    • (L1) Add dependency on CoreIdent.Core for 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() for IServiceCollection
    • (L2) Create extension method MapCoreIdentOpenApi() for IEndpointRouteBuilder
    • (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_basic for client authentication
      • client_secret_post for client authentication
      • authorization_code flow with PKCE
      • refresh_token flow
      • Bearer token authentication
    • (L2) Add request/response examples for key endpoints
    • (L2) Add descriptions from XML documentation to OpenAPI schemas
  • 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.md with 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)
  • 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.json returns 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

Phase 1.5: Client Libraries — PARTIAL (completed features only)

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)


Feature 1.5.1: Core Client Library

  • Component: CoreIdent.Client Package
    • (L1) Create new project targeting net10.0
    • (L1) Define ICoreIdentClient interface
      public 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 CoreIdentClientOptions
      public 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);
      }
  • Component: Token Storage Abstraction
    • (L1) Define ISecureTokenStorage interface
      public 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)
  • Component: Browser Abstraction
    • (L1) Define IBrowserLauncher interface
      public interface IBrowserLauncher
      {
          Task<BrowserResult> LaunchAsync(string url, string redirectUri, CancellationToken ct = default);
      }
    • (L3) Implement SystemBrowserLauncher (opens default browser, listens on localhost)
  • 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

Feature 1.5.2: Browser Automation Testing Infrastructure

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.Testing building blocks

    • (L2) Create CoreIdent.Testing.Host helpers to start a CoreIdent server for tests (in-proc or local port)
    • (L2) Create CoreIdent.Testing.Http helpers (assertions for discovery, token, userinfo, revocation)
    • (L2) Create CoreIdent.Testing.Browser helpers 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
  • 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)

Feature 1.5.3: MAUI Client

  • Component: CoreIdent.Client.Maui Package
    • (L1) Create project targeting net10.0-android;net10.0-ios;net10.0-maccatalyst
    • (L3) Implement MauiSecureTokenStorage using SecureStorage
    • (L3) Implement MauiBrowserLauncher using WebAuthenticator
    • (L1) Add UseCoreIdentClient() extension for MauiAppBuilder
  • 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

Phase 2: External Provider Integration — PARTIAL (completed features only)

Goal: Seamless integration with third-party OAuth/OIDC providers.

Estimated Duration: 2-3 weeks

Prerequisites: Phase 1.5 complete


Feature 2.1: Provider Abstraction Layer

  • Component: CoreIdent.Providers.Abstractions Package
    • (L1) Create new project
    • (L2) Define IExternalAuthProvider interface
    • (L2) Define ExternalAuthResult model
    • (L2) Define ExternalUserProfile model
  • Component: Account Linking
    • (L1) Add ExternalLogin entity to user model
    • (L3) Support linking multiple providers to one user
    • (L3) Handle provider-to-user mapping
  • Documentation:
    • (L1) Document provider implementation guide