Skip to content
bitzorcas
中EN

Guide

Identity testing strategy and production operations

Build an Identity test pyramid and operating playbook from aggregates, application flows, persistence contracts, API Shell, and production probes.

Last updated

Identity testing must prove more than a successful password. Delivery evidence comes from failure branches, tenant isolation, ticket replay, client-platform token isolation, adapter replacement, and production alerts.

1. Test layers

Aggregate and policy tests
fast state-edge coverage

Application deep-module tests
coordination and compensation

Store / ORM contracts
tenant filters, transactions, query parity

API Shell / Host tests
auth, rate limit, serialization, fail closed

Production probes and drills
notification, federation, keys, multi-instance MFA

Higher layers are closer to delivery and slower. Lower layers enumerate state; upper layers prove that composition and infrastructure preserve that state.

2. Aggregate and pure-policy tests

Test classFocus
UserAggregateTestsSecurity-stamp rotation, lockout, enable/disable, activation, Host semantics
OrganizationUnitAggregateTestsPath, move, descendant, enable, and ordering
PlatformTenantLifecycleTestsProvisioning steps, completion, suspension, terminal state
TenantTransitionGuardTestsEvery permitted edge, same-state idempotency, invalid code
PasswordHasherTestsBCrypt verification and rehash detection
PasswordValidatorTestsIndependent failure for every complexity rule
FIDO2 / MFA testsCredential clone detection, cache, and risk policy

Use a fixed DateTimeOffset, not wall-clock time. Assert error code and error type as the stable contract; localized message text is not stable.

3. Deep login-module tests

LoginFlowTests drives ILoginFlow directly and currently covers:

  • Normal login and tokens.
  • User 2FA and risk MFA.
  • Fail closed when MFA challenge issuance fails.
  • No success when refresh-token storage fails.
  • Refresh-token revocation when session storage fails.
  • Captcha first challenge, correct answer, missing answer, generation and verification failure.
  • Risk Block before password.
  • Wrong password, absent user, locked, disabled, and timed unlock.
  • Current fail-open behavior when the risk engine fails.
  • Forced password change, cross-tenant email lookup, and cancellation.

Run the group:

Login and risk regression
# FullyQualifiedName keeps this run on the Identity deep module.
dotnet test tests/BitzOrcas.Application.Tests/BitzOrcas.Application.Tests.csproj \
--configuration Release \
--no-restore \
--filter 'FullyQualifiedName~Identity.LoginFlowTests|FullyQualifiedName~Identity.LoginRiskIntegrationTests|FullyQualifiedName~Identity.VerifyMfaLoginCompletionTests'

When issuance order changes, add a test where one step succeeds and the next fails. Happy-path tests cannot prove that a valid refresh token is not leaked.

4. Account-admission tests

AccountAdmissionFlowTests is a continuous business case, not unrelated mocked handlers. It covers:

  1. Directed invitation accepts only its target email.
  2. Open link enforces email domain, usage, and expiry.
  3. Approval creates PendingActivation; activation enables login.
  4. Default roles apply, and invalid roles reject approval.
  5. A rejected admission cannot activate.
  6. Missing and replayed tokens return generic security errors.

After adding a transaction adapter, prove against a real database that a failed accept cannot split Admission from UsedCount, and that activation-token deletion failure cannot leave both an Active user and a replayable token.

5. Client-platform token isolation

PlatformTokenIsolationTests uses real JwtTokenService with in-memory stores and pins three contracts:

  • Refreshing Web does not change the App slot.
  • Refreshing App does not change the Web slot.
  • The new RefreshTokenRecord inherits the original platform.
JWT claims and platform isolation
# Use the real JwtTokenService to pin claims and Web/App isolation.
dotnet test tests/BitzOrcas.Unit.Tests/BitzOrcas.Unit.Tests.csproj \
--configuration Release \
--no-restore \
--filter 'FullyQualifiedName~Identity.TokenServiceClaimsTests|FullyQualifiedName~Identity.PlatformTokenIsolationTests'

Run the same behavior against production stores. Dictionary isolation does not prove ORM unique keys and filters.

6. Persistence and both ORMs

Identity combines unified aggregate repositories with specialized query stores. Persistence tests need at least:

ContractSqlSugarEF Core
User TenantId filterRequiredRequired
Global Host user lookupRequiredRequired
Invitation TokenHash lookupRequiredRequired
Refresh-token platform and hash indexRequiredRequired
OU tree and paginationRequiredRequired
Admission, User, and Token transactionRequiredRequired

Existing entries include ProductionIdentityStoreTests, RepositoryIdentityStoreTests, IdentityQueryHandlerTests, and read-model adapter parity. A new store must test cross-tenant denial and hidden soft-deleted data, not only save and load.

7. API Shell boundary

Without production database, API Shell uses fail-closed default stores but routes still start. API tests prove:

  • Invitation acceptance and activation are not 401; unavailable store or invalid ticket fails for the correct reason.
  • Invitation management is 401 without authentication.
  • Login/Refresh are anonymous; Me/Logout require authentication.
  • SmartEnum JSON writes names and reads names or values.
  • Result/Error maps consistently to Problem Details.
API Shell security boundary
# Shell needs no production database but must preserve public and management auth boundaries.
dotnet test tests/BitzOrcas.Integration.Tests/BitzOrcas.Integration.Tests.csproj \
--configuration Release \
--no-restore \
--filter 'FullyQualifiedName~ApiShellTests'

A public endpoint being “not 401” does not mean it may succeed. Missing stores, invalid tickets, and untrusted tenants still fail closed.

8. Coverage of the production adapter gate

ProductionAdapterReadinessGuard currently blocks NullUnitOfWork, InMemoryTenantStore, InMemoryApiClientStore, and other defaults in Production/Staging. It also checks Redis, file storage, notification publisher, Feature, and critical Webhook ports.

It does not yet cover every Identity email/SMS delivery port, IRefreshTokenStore, federation provider, MFA configuration store, or distributed challenge cache. Identity GA therefore needs supplemental probes; a Host that passes the common guard is not complete evidence.

9. Observability signals

SignalDimensionsSuggested alert
Login failure rateTenant, provider, reason; no plaintext user nameBaseline spike and brute-force pattern
Risk degradationRisk error and instanceAlert on sustained growth
Captcha generation/verificationProvider and stageSeparate provider failure from attack traffic
MFA challenge failureMethod and cache implementationCross-instance failure
Refresh-token reuseTenant, hashed UserId, platformHigh-priority security alert
Session compensationStore error codeRisk after token issuance
Activation notificationChannel, provider, error classOnboarding blocked
Invalid tenant transitionCurrent and target stateBad operation script or concurrency

Logs may use UserId or token hash as diagnostic identifiers, but never password, raw token, full phone, JWT, refresh token, or connection string.

10. Login troubleshooting

InvalidCredentials

Check tenant resolution, Host/email/tenant-local lookup, account state, and password verification in order. Keep the public error generic and correlate internal sanitized login logs by CorrelationId.

Captcha repeats forever

Confirm stage two returns the original captchaChallengeId and answer; check whether another attempt consumed the ticket; confirm proxy behavior did not change IP, tenant, or device context.

Correct password but no token

Inspect requiresMfa, requiresCaptcha, TwoFactorEnabled, and risk challenge. Then inspect role lookup, refresh-token store, and session-store failures.

Refreshing one client signs out another

Inspect names RefreshToken:Web and RefreshToken:App, confirm the store key includes Name, and verify rotated records preserve Platform.

11. Invitation and activation troubleshooting

SymptomFirst checks
Every invitation is InvalidEffective tenant, token hash, clock, store query
Directed email mismatchNormalization and submitted address
No mail after approvalFinal IEmailDeliveryPort, frontend URL, provider receipt
Activation always Invalid72h expiry, token encoding, UserToken Provider/Name
Approval leaves partial userUoW, role write, notification failure, recovery command
Replay succeedsToken deletion, Admission consume, concurrency uniqueness

Preserve audit evidence before repairing data. Do not manually set a user Active and skip Admission or token state. Recovery should run through an audited use case or one-time operations command.

12. Security incident response

Suspected refresh-token theft

  1. Locate RefreshTokenRecord and family by token hash.
  2. Revoke the family and related sessions.
  3. Require reauthentication or password reset based on risk.
  4. Check client platforms separately before widening impact.
  5. Preserve platform, IP, DeviceId, time, and CorrelationId in sanitized audit.
  1. Revoke Invitation and preserve actor and time.
  2. Find admissions created from it and review their state.
  3. Reject suspicious admissions; treat already active users as account incidents.
  4. Create a new token; the old raw token cannot be recovered.

Leaked JWT signing key

  1. Activate a new Kid and stop old-key issuance.
  2. Shorten or remove old-key validation grace according to severity.
  3. Revoke refresh tokens and high-risk sessions.
  4. Verify every instance loaded the same key ring.
  5. Record rotation evidence and investigate the secret source.

13. Full pre-release commands

Terminal window
# Restore and build from one Release input to pin dependencies and output.
dotnet restore BitzOrcas.Modern.slnx --locked-mode
dotnet build BitzOrcas.Modern.slnx --configuration Release --no-restore
# Run Identity tests by layer so a failure identifies its responsible boundary.
dotnet test tests/BitzOrcas.Unit.Tests/BitzOrcas.Unit.Tests.csproj \
--configuration Release --no-build --no-restore \
--filter 'FullyQualifiedName~Identity|FullyQualifiedName~Platform.Tenancy'
dotnet test tests/BitzOrcas.Application.Tests/BitzOrcas.Application.Tests.csproj \
--configuration Release --no-build --no-restore \
--filter 'FullyQualifiedName~Identity'
dotnet test tests/BitzOrcas.Architecture.Tests/BitzOrcas.Architecture.Tests.csproj \
--configuration Release --no-build --no-restore \
--filter 'FullyQualifiedName~Identity'

Whether Integration Tests can use --no-build depends on the solution build output. CI should use the repository’s established test entry.

14. Global sweeps and expectations

Terminal window
# Find copied login orchestration or direct password processing in a Host.
rg -n "VerifyPasswordFailed|DummyPasswordHash|IssueMfaChallenge" \
src/Hosts src/Platform/Identity -g '*.cs'
# Matches outside LoginFlow require explanation and must not form a second password-login state machine.
# Unified aggregates should have no one-to-one entity/mapper copies.
find src/Platform/Identity -type f \
\( -name 'UserEntity.cs' -o -name 'AccountInvitationEntity.cs' -o -name 'PlatformTenantEntity.cs' \)
# Expected: no output unless an asymmetric exception is recorded under docs/architecture.

15. GA sign-off

  • Every layer passes with real ORM, messaging, cache, and notification adapters in integration tests.
  • Risk fail open is an explicit product and security decision.
  • Password-change session revocation has a clear product decision, matching tests and UI.
  • Notification, federation, MFA, and refresh-token stores are observable concrete implementations.
  • Cross-tenant, Host, client-platform, ticket replay, and terminal-state negatives are covered.
  • Key leak, token theft, invitation leak, and partial approval have rehearsed procedures.

Identity overview · Configuration · Production security checklist

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%