@ngxtm/devkit 3.3.0 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/index.js +59 -13
- package/cli/rules.js +248 -0
- package/package.json +2 -1
- package/rules/README.md +141 -0
- package/rules/dart/best-practices/SKILL.md +23 -0
- package/rules/dart/language/SKILL.md +52 -0
- package/rules/dart/tooling/SKILL.md +43 -0
- package/rules/dotnet/aspnet-core/SKILL.md +92 -0
- package/rules/dotnet/aspnet-core/references/REFERENCE.md +335 -0
- package/rules/dotnet/best-practices/SKILL.md +101 -0
- package/rules/dotnet/best-practices/references/REFERENCE.md +256 -0
- package/rules/dotnet/blazor/SKILL.md +146 -0
- package/rules/dotnet/blazor/references/REFERENCE.md +392 -0
- package/rules/dotnet/language/SKILL.md +82 -0
- package/rules/dotnet/language/references/REFERENCE.md +222 -0
- package/rules/dotnet/patterns.rule.md +388 -0
- package/rules/dotnet/razor-pages/SKILL.md +124 -0
- package/rules/dotnet/razor-pages/references/REFERENCE.md +321 -0
- package/rules/dotnet/security/SKILL.md +89 -0
- package/rules/dotnet/security/references/REFERENCE.md +295 -0
- package/rules/dotnet/tooling/SKILL.md +92 -0
- package/rules/dotnet/tooling/references/REFERENCE.md +300 -0
- package/rules/flutter/auto-route-navigation/SKILL.md +43 -0
- package/rules/flutter/auto-route-navigation/references/REFERENCE.md +19 -0
- package/rules/flutter/auto-route-navigation/references/router-config.md +62 -0
- package/rules/flutter/bloc-state-management/SKILL.md +64 -0
- package/rules/flutter/bloc-state-management/references/REFERENCE.md +20 -0
- package/rules/flutter/bloc-state-management/references/auth-bloc-example.md +52 -0
- package/rules/flutter/bloc-state-management/references/equatable-usage.md +56 -0
- package/rules/flutter/bloc-state-management/references/property-based-state.md +68 -0
- package/rules/flutter/bloc.rule.md +76 -0
- package/rules/flutter/cicd/SKILL.md +48 -0
- package/rules/flutter/cicd/references/advanced-workflow.md +66 -0
- package/rules/flutter/cicd/references/fastlane.md +139 -0
- package/rules/flutter/cicd/references/github-actions.md +59 -0
- package/rules/flutter/dependency-injection/SKILL.md +42 -0
- package/rules/flutter/dependency-injection/references/REFERENCE.md +15 -0
- package/rules/flutter/dependency-injection/references/modules.md +37 -0
- package/rules/flutter/error-handling/SKILL.md +32 -0
- package/rules/flutter/error-handling/references/REFERENCE.md +19 -0
- package/rules/flutter/error-handling/references/error-mapping.md +31 -0
- package/rules/flutter/feature-based-clean-architecture/SKILL.md +46 -0
- package/rules/flutter/feature-based-clean-architecture/references/REFERENCE.md +14 -0
- package/rules/flutter/feature-based-clean-architecture/references/folder-structure.md +36 -0
- package/rules/flutter/getx-navigation/SKILL.md +70 -0
- package/rules/flutter/getx-navigation/references/app-pages.md +40 -0
- package/rules/flutter/getx-navigation/references/middleware-example.md +29 -0
- package/rules/flutter/getx-state-management/SKILL.md +76 -0
- package/rules/flutter/getx-state-management/references/binding-example.md +32 -0
- package/rules/flutter/getx-state-management/references/reactive-vs-simple.md +39 -0
- package/rules/flutter/go-router-navigation/SKILL.md +57 -0
- package/rules/flutter/idiomatic-flutter/SKILL.md +20 -0
- package/rules/flutter/layer-based-clean-architecture/SKILL.md +50 -0
- package/rules/flutter/layer-based-clean-architecture/references/REFERENCE.md +60 -0
- package/rules/flutter/layer-based-clean-architecture/references/repository-mapping.md +50 -0
- package/rules/flutter/localization/SKILL.md +50 -0
- package/rules/flutter/localization/references/REFERENCE.md +48 -0
- package/rules/flutter/localization/references/sheet-loader.md +33 -0
- package/rules/flutter/navigator-v1-navigation/SKILL.md +71 -0
- package/rules/flutter/navigator-v1-navigation/references/on-generate-route.md +48 -0
- package/rules/flutter/performance/SKILL.md +24 -0
- package/rules/flutter/retrofit-networking/SKILL.md +51 -0
- package/rules/flutter/retrofit-networking/references/REFERENCE.md +19 -0
- package/rules/flutter/retrofit-networking/references/token-refresh.md +40 -0
- package/rules/flutter/riverpod-state-management/SKILL.md +53 -0
- package/rules/flutter/riverpod-state-management/references/architecture.md +124 -0
- package/rules/flutter/riverpod-state-management/references/best-practices.md +89 -0
- package/rules/flutter/riverpod-state-management/references/testing.md +73 -0
- package/rules/flutter/riverpod.rule.md +78 -0
- package/rules/flutter/security/SKILL.md +33 -0
- package/rules/flutter/security/references/REFERENCE.md +15 -0
- package/rules/flutter/security/references/network-security.md +28 -0
- package/rules/flutter/testing/SKILL.md +44 -0
- package/rules/flutter/testing/references/REFERENCE.md +21 -0
- package/rules/flutter/testing/references/bloc-testing.md +38 -0
- package/rules/flutter/testing/references/integration-testing.md +128 -0
- package/rules/flutter/testing/references/robot-pattern.md +82 -0
- package/rules/flutter/testing/references/unit-testing.md +130 -0
- package/rules/flutter/testing/references/widget-testing.md +120 -0
- package/rules/flutter/widgets/SKILL.md +37 -0
- package/rules/golang/chi-router/SKILL.md +219 -0
- package/rules/golang/chi-router/references/REFERENCE.md +13 -0
- package/rules/golang/chi-router/references/routing-patterns.md +205 -0
- package/rules/golang/cobra-cli/SKILL.md +227 -0
- package/rules/golang/cobra-cli/references/REFERENCE.md +13 -0
- package/rules/golang/cobra-cli/references/command-patterns.md +224 -0
- package/rules/golang/core/SKILL.md +210 -0
- package/rules/golang/core/references/REFERENCE.md +14 -0
- package/rules/golang/core/references/concurrency-patterns.md +114 -0
- package/rules/golang/core/references/error-handling.md +87 -0
- package/rules/golang/echo-framework/SKILL.md +215 -0
- package/rules/golang/echo-framework/references/REFERENCE.md +14 -0
- package/rules/golang/echo-framework/references/middleware-patterns.md +141 -0
- package/rules/golang/echo-framework/references/routing-patterns.md +140 -0
- package/rules/golang/ent-orm/SKILL.md +239 -0
- package/rules/golang/ent-orm/references/REFERENCE.md +13 -0
- package/rules/golang/ent-orm/references/schema-patterns.md +255 -0
- package/rules/golang/fiber-framework/SKILL.md +196 -0
- package/rules/golang/fiber-framework/references/REFERENCE.md +13 -0
- package/rules/golang/fiber-framework/references/routing-patterns.md +191 -0
- package/rules/golang/gin-framework/SKILL.md +205 -0
- package/rules/golang/gin-framework/references/REFERENCE.md +14 -0
- package/rules/golang/gin-framework/references/middleware-patterns.md +119 -0
- package/rules/golang/gorm-orm/SKILL.md +196 -0
- package/rules/golang/gorm-orm/references/REFERENCE.md +14 -0
- package/rules/golang/gorm-orm/references/model-definitions.md +167 -0
- package/rules/golang/gorm-orm/references/query-patterns.md +161 -0
- package/rules/golang/grpc/SKILL.md +231 -0
- package/rules/golang/grpc/references/REFERENCE.md +13 -0
- package/rules/golang/grpc/references/service-patterns.md +276 -0
- package/rules/golang/testify/SKILL.md +239 -0
- package/rules/golang/testify/references/REFERENCE.md +13 -0
- package/rules/golang/testify/references/assert-patterns.md +170 -0
- package/rules/golang/validator/SKILL.md +234 -0
- package/rules/golang/validator/references/REFERENCE.md +13 -0
- package/rules/golang/validator/references/validation-tags.md +211 -0
- package/rules/golang/viper-config/SKILL.md +244 -0
- package/rules/golang/viper-config/references/REFERENCE.md +13 -0
- package/rules/golang/viper-config/references/config-loading.md +181 -0
- package/rules/golang/wire-di/SKILL.md +243 -0
- package/rules/golang/wire-di/references/REFERENCE.md +13 -0
- package/rules/golang/wire-di/references/provider-patterns.md +193 -0
- package/rules/golang/zap-logging/SKILL.md +203 -0
- package/rules/golang/zap-logging/references/REFERENCE.md +13 -0
- package/rules/golang/zap-logging/references/logger-config.md +165 -0
- package/rules/java/build-gradle/SKILL.md +92 -0
- package/rules/java/build-gradle/references/REFERENCE.md +14 -0
- package/rules/java/build-gradle/references/kotlin-dsl.md +118 -0
- package/rules/java/build-gradle/references/task-configuration.md +132 -0
- package/rules/java/build-maven/SKILL.md +86 -0
- package/rules/java/build-maven/references/REFERENCE.md +14 -0
- package/rules/java/build-maven/references/dependency-management.md +111 -0
- package/rules/java/build-maven/references/lifecycle-phases.md +114 -0
- package/rules/java/graalvm-native/SKILL.md +105 -0
- package/rules/java/graalvm-native/references/REFERENCE.md +12 -0
- package/rules/java/java-collections-streams/SKILL.md +148 -0
- package/rules/java/java-collections-streams/references/REFERENCE.md +15 -0
- package/rules/java/java-collections-streams/references/collectors-patterns.md +178 -0
- package/rules/java/java-collections-streams/references/stream-pipelines.md +165 -0
- package/rules/java/java-concurrency/SKILL.md +187 -0
- package/rules/java/java-concurrency/references/REFERENCE.md +17 -0
- package/rules/java/java-concurrency/references/completable-future.md +165 -0
- package/rules/java/java-concurrency/references/executor-patterns.md +176 -0
- package/rules/java/java-concurrency/references/virtual-threads.md +190 -0
- package/rules/java/java-core-language/SKILL.md +121 -0
- package/rules/java/java-core-language/references/REFERENCE.md +15 -0
- package/rules/java/java-core-language/references/jvm-memory-model.md +160 -0
- package/rules/java/java-core-language/references/modern-java-features.md +168 -0
- package/rules/java/java-project-structure/SKILL.md +195 -0
- package/rules/java/java-project-structure/references/REFERENCE.md +15 -0
- package/rules/java/java-project-structure/references/maven-project-layout.md +199 -0
- package/rules/java/java-project-structure/references/module-system.md +159 -0
- package/rules/java/micronaut-core/SKILL.md +99 -0
- package/rules/java/micronaut-core/references/REFERENCE.md +12 -0
- package/rules/java/micronaut-reactive/SKILL.md +68 -0
- package/rules/java/micronaut-reactive/references/REFERENCE.md +12 -0
- package/rules/java/quarkus-core/SKILL.md +85 -0
- package/rules/java/quarkus-core/references/REFERENCE.md +12 -0
- package/rules/java/quarkus-reactive/SKILL.md +67 -0
- package/rules/java/quarkus-reactive/references/REFERENCE.md +12 -0
- package/rules/java/spring-batch/SKILL.md +102 -0
- package/rules/java/spring-batch/references/REFERENCE.md +12 -0
- package/rules/java/spring-boot-architecture/SKILL.md +206 -0
- package/rules/java/spring-boot-architecture/references/REFERENCE.md +15 -0
- package/rules/java/spring-boot-architecture/references/auto-configuration.md +158 -0
- package/rules/java/spring-boot-architecture/references/configuration-properties.md +202 -0
- package/rules/java/spring-boot-web/SKILL.md +217 -0
- package/rules/java/spring-boot-web/references/REFERENCE.md +17 -0
- package/rules/java/spring-cloud/SKILL.md +109 -0
- package/rules/java/spring-cloud/references/REFERENCE.md +13 -0
- package/rules/java/spring-data-jpa/SKILL.md +241 -0
- package/rules/java/spring-data-jpa/references/REFERENCE.md +16 -0
- package/rules/java/spring-security/SKILL.md +161 -0
- package/rules/java/spring-security/references/REFERENCE.md +16 -0
- package/rules/java/spring-security/references/jwt-auth-flow.md +213 -0
- package/rules/java/testing-junit-mockito/SKILL.md +135 -0
- package/rules/java/testing-junit-mockito/references/REFERENCE.md +15 -0
- package/rules/java/testing-junit-mockito/references/junit5-patterns.md +159 -0
- package/rules/java/testing-junit-mockito/references/mockito-patterns.md +148 -0
- package/rules/java/testing-junit-mockito/references/spring-boot-testing.md +152 -0
- package/rules/javascript/best-practices/SKILL.md +64 -0
- package/rules/javascript/best-practices/references/REFERENCE.md +91 -0
- package/rules/javascript/language/SKILL.md +71 -0
- package/rules/javascript/language/references/REFERENCE.md +106 -0
- package/rules/javascript/tooling/SKILL.md +60 -0
- package/rules/javascript/tooling/references/REFERENCE.md +107 -0
- package/rules/metadata.json +54 -0
- package/rules/nestjs/api-standards/SKILL.md +47 -0
- package/rules/nestjs/api-standards/references/pagination-wrapper.md +87 -0
- package/rules/nestjs/architecture/SKILL.md +68 -0
- package/rules/nestjs/architecture/references/dynamic-module.md +53 -0
- package/rules/nestjs/caching/SKILL.md +51 -0
- package/rules/nestjs/caching/references/REFERENCE.md +13 -0
- package/rules/nestjs/caching/references/cache-patterns.md +183 -0
- package/rules/nestjs/configuration/SKILL.md +41 -0
- package/rules/nestjs/configuration/references/REFERENCE.md +13 -0
- package/rules/nestjs/configuration/references/config-patterns.md +184 -0
- package/rules/nestjs/controllers-services/SKILL.md +63 -0
- package/rules/nestjs/controllers-services/references/REFERENCE.md +14 -0
- package/rules/nestjs/controllers-services/references/controller-patterns.md +119 -0
- package/rules/nestjs/controllers-services/references/service-patterns.md +129 -0
- package/rules/nestjs/database/SKILL.md +102 -0
- package/rules/nestjs/database/references/REFERENCE.md +14 -0
- package/rules/nestjs/database/references/typeorm-patterns.md +156 -0
- package/rules/nestjs/deployment/SKILL.md +36 -0
- package/rules/nestjs/deployment/references/REFERENCE.md +13 -0
- package/rules/nestjs/deployment/references/deployment-patterns.md +140 -0
- package/rules/nestjs/documentation/SKILL.md +64 -0
- package/rules/nestjs/documentation/references/REFERENCE.md +13 -0
- package/rules/nestjs/documentation/references/swagger-patterns.md +139 -0
- package/rules/nestjs/error-handling/SKILL.md +55 -0
- package/rules/nestjs/error-handling/references/REFERENCE.md +13 -0
- package/rules/nestjs/error-handling/references/exception-filters.md +152 -0
- package/rules/nestjs/file-uploads/SKILL.md +35 -0
- package/rules/nestjs/file-uploads/references/REFERENCE.md +13 -0
- package/rules/nestjs/file-uploads/references/upload-patterns.md +125 -0
- package/rules/nestjs/observability/SKILL.md +39 -0
- package/rules/nestjs/observability/references/REFERENCE.md +13 -0
- package/rules/nestjs/observability/references/logging-metrics.md +175 -0
- package/rules/nestjs/performance/SKILL.md +60 -0
- package/rules/nestjs/performance/references/REFERENCE.md +13 -0
- package/rules/nestjs/performance/references/performance-patterns.md +107 -0
- package/rules/nestjs/real-time/SKILL.md +45 -0
- package/rules/nestjs/real-time/references/REFERENCE.md +13 -0
- package/rules/nestjs/real-time/references/websocket-patterns.md +121 -0
- package/rules/nestjs/scheduling/SKILL.md +39 -0
- package/rules/nestjs/scheduling/references/REFERENCE.md +13 -0
- package/rules/nestjs/scheduling/references/scheduling-patterns.md +137 -0
- package/rules/nestjs/search/SKILL.md +41 -0
- package/rules/nestjs/search/references/REFERENCE.md +13 -0
- package/rules/nestjs/search/references/search-patterns.md +137 -0
- package/rules/nestjs/security/SKILL.md +87 -0
- package/rules/nestjs/security/references/REFERENCE.md +14 -0
- package/rules/nestjs/security/references/authentication.md +151 -0
- package/rules/nestjs/testing/SKILL.md +40 -0
- package/rules/nestjs/testing/references/REFERENCE.md +14 -0
- package/rules/nestjs/testing/references/unit-testing.md +179 -0
- package/rules/nestjs/transport/SKILL.md +45 -0
- package/rules/nestjs/transport/references/REFERENCE.md +13 -0
- package/rules/nestjs/transport/references/microservices-patterns.md +170 -0
- package/rules/nextjs/app-router/SKILL.md +46 -0
- package/rules/nextjs/app-router/references/REFERENCE.md +14 -0
- package/rules/nextjs/app-router/references/routing-patterns.md +182 -0
- package/rules/nextjs/architecture/SKILL.md +44 -0
- package/rules/nextjs/architecture/references/fsd-structure.md +77 -0
- package/rules/nextjs/authentication/SKILL.md +29 -0
- package/rules/nextjs/authentication/references/auth-implementation.md +73 -0
- package/rules/nextjs/caching/SKILL.md +66 -0
- package/rules/nextjs/caching/references/REFERENCE.md +13 -0
- package/rules/nextjs/caching/references/cache-strategies.md +168 -0
- package/rules/nextjs/data-access-layer/SKILL.md +33 -0
- package/rules/nextjs/data-access-layer/references/patterns.md +66 -0
- package/rules/nextjs/data-fetching/SKILL.md +59 -0
- package/rules/nextjs/data-fetching/references/REFERENCE.md +13 -0
- package/rules/nextjs/data-fetching/references/fetch-patterns.md +160 -0
- package/rules/nextjs/internationalization/SKILL.md +105 -0
- package/rules/nextjs/internationalization/references/REFERENCE.md +13 -0
- package/rules/nextjs/internationalization/references/i18n-patterns.md +180 -0
- package/rules/nextjs/optimization/SKILL.md +64 -0
- package/rules/nextjs/optimization/references/REFERENCE.md +13 -0
- package/rules/nextjs/optimization/references/optimization-patterns.md +190 -0
- package/rules/nextjs/rendering/SKILL.md +91 -0
- package/rules/nextjs/rendering/references/REFERENCE.md +13 -0
- package/rules/nextjs/rendering/references/rendering-modes.md +163 -0
- package/rules/nextjs/server-actions/SKILL.md +46 -0
- package/rules/nextjs/server-actions/references/REFERENCE.md +13 -0
- package/rules/nextjs/server-actions/references/action-patterns.md +188 -0
- package/rules/nextjs/server-components/SKILL.md +52 -0
- package/rules/nextjs/server-components/references/REFERENCE.md +13 -0
- package/rules/nextjs/server-components/references/component-patterns.md +175 -0
- package/rules/nextjs/state-management/SKILL.md +73 -0
- package/rules/nextjs/state-management/references/REFERENCE.md +13 -0
- package/rules/nextjs/state-management/references/state-patterns.md +218 -0
- package/rules/nextjs/styling/SKILL.md +31 -0
- package/rules/nextjs/styling/references/implementation.md +56 -0
- package/rules/react/component-patterns/SKILL.md +66 -0
- package/rules/react/component-patterns/references/REFERENCE.md +126 -0
- package/rules/react/hooks/SKILL.md +60 -0
- package/rules/react/hooks/references/REFERENCE.md +132 -0
- package/rules/react/hooks.rule.md +79 -0
- package/rules/react/performance/SKILL.md +69 -0
- package/rules/react/performance/references/REFERENCE.md +143 -0
- package/rules/react/security/SKILL.md +46 -0
- package/rules/react/security/references/REFERENCE.md +170 -0
- package/rules/react/state-management/SKILL.md +56 -0
- package/rules/react/state-management/references/REFERENCE.md +137 -0
- package/rules/react/testing/SKILL.md +45 -0
- package/rules/react/testing/references/REFERENCE.md +149 -0
- package/rules/react/tooling/SKILL.md +39 -0
- package/rules/react/typescript/SKILL.md +53 -0
- package/rules/rust/actix-web/SKILL.md +160 -0
- package/rules/rust/actix-web/references/REFERENCE.md +13 -0
- package/rules/rust/actix-web/references/handler-patterns.md +198 -0
- package/rules/rust/async-graphql/SKILL.md +228 -0
- package/rules/rust/async-graphql/references/REFERENCE.md +13 -0
- package/rules/rust/async-graphql/references/schema-patterns.md +215 -0
- package/rules/rust/axum/SKILL.md +161 -0
- package/rules/rust/axum/references/REFERENCE.md +14 -0
- package/rules/rust/axum/references/handler-patterns.md +97 -0
- package/rules/rust/bevy/SKILL.md +206 -0
- package/rules/rust/bevy/references/REFERENCE.md +13 -0
- package/rules/rust/bevy/references/ecs-patterns.md +226 -0
- package/rules/rust/clap/SKILL.md +217 -0
- package/rules/rust/clap/references/REFERENCE.md +13 -0
- package/rules/rust/clap/references/derive-patterns.md +205 -0
- package/rules/rust/core/SKILL.md +154 -0
- package/rules/rust/core/references/REFERENCE.md +14 -0
- package/rules/rust/core/references/error-handling.md +92 -0
- package/rules/rust/diesel-orm/SKILL.md +176 -0
- package/rules/rust/diesel-orm/references/REFERENCE.md +13 -0
- package/rules/rust/diesel-orm/references/schema-patterns.md +206 -0
- package/rules/rust/rocket/SKILL.md +182 -0
- package/rules/rust/rocket/references/REFERENCE.md +13 -0
- package/rules/rust/rocket/references/handler-patterns.md +209 -0
- package/rules/rust/sea-orm/SKILL.md +230 -0
- package/rules/rust/sea-orm/references/REFERENCE.md +13 -0
- package/rules/rust/sea-orm/references/entity-patterns.md +221 -0
- package/rules/rust/serde-serialization/SKILL.md +150 -0
- package/rules/rust/serde-serialization/references/REFERENCE.md +13 -0
- package/rules/rust/serde-serialization/references/serialization-patterns.md +199 -0
- package/rules/rust/sqlx-database/SKILL.md +140 -0
- package/rules/rust/sqlx-database/references/REFERENCE.md +13 -0
- package/rules/rust/sqlx-database/references/query-patterns.md +210 -0
- package/rules/rust/tauri/SKILL.md +180 -0
- package/rules/rust/tauri/references/REFERENCE.md +13 -0
- package/rules/rust/tauri/references/command-patterns.md +209 -0
- package/rules/rust/tokio-runtime/SKILL.md +167 -0
- package/rules/rust/tokio-runtime/references/REFERENCE.md +14 -0
- package/rules/rust/tokio-runtime/references/async-patterns.md +137 -0
- package/rules/rust/tokio-runtime/references/synchronization.md +152 -0
- package/rules/rust/tonic/SKILL.md +231 -0
- package/rules/rust/tonic/references/REFERENCE.md +13 -0
- package/rules/rust/tonic/references/service-patterns.md +213 -0
- package/rules/rust/tracing/SKILL.md +214 -0
- package/rules/rust/tracing/references/REFERENCE.md +13 -0
- package/rules/rust/tracing/references/instrumentation.md +187 -0
- package/rules/typescript/best-practices/SKILL.md +108 -0
- package/rules/typescript/best-practices/references/REFERENCE.md +68 -0
- package/rules/typescript/language/SKILL.md +72 -0
- package/rules/typescript/language/references/REFERENCE.md +67 -0
- package/rules/typescript/patterns.rule.md +85 -0
- package/rules/typescript/security/SKILL.md +59 -0
- package/rules/typescript/security/references/REFERENCE.md +113 -0
- package/rules/typescript/tooling/SKILL.md +52 -0
- package/rules/typescript/tooling/references/REFERENCE.md +110 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# Razor Pages Reference
|
|
2
|
+
|
|
3
|
+
Page conventions, view components, and form handling patterns.
|
|
4
|
+
|
|
5
|
+
## References
|
|
6
|
+
|
|
7
|
+
- [**Page Conventions**](page-conventions.md) - Routing and authorization.
|
|
8
|
+
- [**View Components**](view-components.md) - Reusable UI components.
|
|
9
|
+
- [**Forms**](forms.md) - Form handling and validation.
|
|
10
|
+
|
|
11
|
+
## Page Routing Conventions
|
|
12
|
+
|
|
13
|
+
```csharp
|
|
14
|
+
// Program.cs - Custom conventions
|
|
15
|
+
builder.Services.AddRazorPages(options =>
|
|
16
|
+
{
|
|
17
|
+
// Authorization
|
|
18
|
+
options.Conventions.AuthorizeFolder("/Admin");
|
|
19
|
+
options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage");
|
|
20
|
+
options.Conventions.AllowAnonymousToPage("/Public/Index");
|
|
21
|
+
|
|
22
|
+
// Custom routes
|
|
23
|
+
options.Conventions.AddPageRoute("/Products/Details", "/p/{id}");
|
|
24
|
+
|
|
25
|
+
// Page model conventions
|
|
26
|
+
options.Conventions.AddFolderApplicationModelConvention("/Blog",
|
|
27
|
+
model => model.Filters.Add(new BlogPageFilter()));
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## View Components
|
|
32
|
+
|
|
33
|
+
```csharp
|
|
34
|
+
// ViewComponents/ShoppingCartViewComponent.cs
|
|
35
|
+
public class ShoppingCartViewComponent(ICartService cartService) : ViewComponent
|
|
36
|
+
{
|
|
37
|
+
public async Task<IViewComponentResult> InvokeAsync()
|
|
38
|
+
{
|
|
39
|
+
var cart = await cartService.GetCurrentCartAsync(
|
|
40
|
+
HttpContext.User.Identity?.Name);
|
|
41
|
+
|
|
42
|
+
return View(new ShoppingCartViewModel
|
|
43
|
+
{
|
|
44
|
+
ItemCount = cart?.Items.Count ?? 0,
|
|
45
|
+
Total = cart?.Total ?? 0
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```html
|
|
52
|
+
@* Views/Shared/Components/ShoppingCart/Default.cshtml *@
|
|
53
|
+
@model ShoppingCartViewModel
|
|
54
|
+
|
|
55
|
+
<div class="cart-widget">
|
|
56
|
+
<a asp-page="/Cart/Index">
|
|
57
|
+
<span class="badge">@Model.ItemCount</span>
|
|
58
|
+
Cart: @Model.Total.ToString("C")
|
|
59
|
+
</a>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
@* Usage in layout *@
|
|
63
|
+
<nav>
|
|
64
|
+
@await Component.InvokeAsync("ShoppingCart")
|
|
65
|
+
</nav>
|
|
66
|
+
|
|
67
|
+
@* Or with tag helper *@
|
|
68
|
+
<vc:shopping-cart></vc:shopping-cart>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Handler Methods
|
|
72
|
+
|
|
73
|
+
```csharp
|
|
74
|
+
public class ProductsModel : PageModel
|
|
75
|
+
{
|
|
76
|
+
[BindProperty(SupportsGet = true)]
|
|
77
|
+
public string? SearchTerm { get; set; }
|
|
78
|
+
|
|
79
|
+
[BindProperty(SupportsGet = true)]
|
|
80
|
+
public int PageNumber { get; set; } = 1;
|
|
81
|
+
|
|
82
|
+
public List<ProductDto> Products { get; set; } = [];
|
|
83
|
+
|
|
84
|
+
// GET /Products
|
|
85
|
+
public async Task OnGetAsync()
|
|
86
|
+
{
|
|
87
|
+
Products = await _service.SearchAsync(SearchTerm, PageNumber);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// GET /Products?handler=Export
|
|
91
|
+
public async Task<IActionResult> OnGetExportAsync()
|
|
92
|
+
{
|
|
93
|
+
var csv = await _service.ExportToCsvAsync(SearchTerm);
|
|
94
|
+
return File(csv, "text/csv", "products.csv");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// POST /Products (add to cart)
|
|
98
|
+
public async Task<IActionResult> OnPostAddToCartAsync(int productId)
|
|
99
|
+
{
|
|
100
|
+
await _cartService.AddItemAsync(productId);
|
|
101
|
+
return RedirectToPage();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// POST /Products?handler=Delete
|
|
105
|
+
public async Task<IActionResult> OnPostDeleteAsync(int id)
|
|
106
|
+
{
|
|
107
|
+
await _service.DeleteAsync(id);
|
|
108
|
+
TempData["Success"] = "Product deleted";
|
|
109
|
+
return RedirectToPage();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```html
|
|
115
|
+
@* Multiple forms on same page *@
|
|
116
|
+
<form method="get">
|
|
117
|
+
<input asp-for="SearchTerm" placeholder="Search..." />
|
|
118
|
+
<button type="submit">Search</button>
|
|
119
|
+
</form>
|
|
120
|
+
|
|
121
|
+
<a asp-page-handler="Export" asp-route-searchTerm="@Model.SearchTerm">
|
|
122
|
+
Export to CSV
|
|
123
|
+
</a>
|
|
124
|
+
|
|
125
|
+
@foreach (var product in Model.Products)
|
|
126
|
+
{
|
|
127
|
+
<div class="product">
|
|
128
|
+
<h3>@product.Name</h3>
|
|
129
|
+
|
|
130
|
+
<form method="post" asp-page-handler="AddToCart">
|
|
131
|
+
<input type="hidden" name="productId" value="@product.Id" />
|
|
132
|
+
<button type="submit">Add to Cart</button>
|
|
133
|
+
</form>
|
|
134
|
+
|
|
135
|
+
<form method="post" asp-page-handler="Delete"
|
|
136
|
+
onsubmit="return confirm('Are you sure?')">
|
|
137
|
+
<input type="hidden" name="id" value="@product.Id" />
|
|
138
|
+
<button type="submit" class="btn-danger">Delete</button>
|
|
139
|
+
</form>
|
|
140
|
+
</div>
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## AJAX with Razor Pages
|
|
145
|
+
|
|
146
|
+
```csharp
|
|
147
|
+
// PageModel
|
|
148
|
+
public class ContactModel : PageModel
|
|
149
|
+
{
|
|
150
|
+
[BindProperty]
|
|
151
|
+
public ContactForm Form { get; set; } = new();
|
|
152
|
+
|
|
153
|
+
public async Task<IActionResult> OnPostAsync()
|
|
154
|
+
{
|
|
155
|
+
if (!ModelState.IsValid)
|
|
156
|
+
{
|
|
157
|
+
// Return validation errors for AJAX
|
|
158
|
+
return new JsonResult(new
|
|
159
|
+
{
|
|
160
|
+
success = false,
|
|
161
|
+
errors = ModelState.ToDictionary(
|
|
162
|
+
kvp => kvp.Key,
|
|
163
|
+
kvp => kvp.Value?.Errors.Select(e => e.ErrorMessage))
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
await _emailService.SendContactFormAsync(Form);
|
|
168
|
+
|
|
169
|
+
return new JsonResult(new { success = true });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
```html
|
|
175
|
+
@* AJAX form submission *@
|
|
176
|
+
<form id="contactForm" method="post">
|
|
177
|
+
@Html.AntiForgeryToken()
|
|
178
|
+
|
|
179
|
+
<input asp-for="Form.Name" />
|
|
180
|
+
<input asp-for="Form.Email" />
|
|
181
|
+
<textarea asp-for="Form.Message"></textarea>
|
|
182
|
+
|
|
183
|
+
<button type="submit">Send</button>
|
|
184
|
+
</form>
|
|
185
|
+
|
|
186
|
+
<div id="result"></div>
|
|
187
|
+
|
|
188
|
+
@section Scripts {
|
|
189
|
+
<script>
|
|
190
|
+
document.getElementById('contactForm').addEventListener('submit', async (e) => {
|
|
191
|
+
e.preventDefault();
|
|
192
|
+
|
|
193
|
+
const form = e.target;
|
|
194
|
+
const formData = new FormData(form);
|
|
195
|
+
|
|
196
|
+
const response = await fetch(form.action, {
|
|
197
|
+
method: 'POST',
|
|
198
|
+
body: formData
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const result = await response.json();
|
|
202
|
+
|
|
203
|
+
if (result.success) {
|
|
204
|
+
document.getElementById('result').innerHTML =
|
|
205
|
+
'<div class="alert alert-success">Message sent!</div>';
|
|
206
|
+
form.reset();
|
|
207
|
+
} else {
|
|
208
|
+
// Display validation errors
|
|
209
|
+
console.log(result.errors);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
</script>
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Partial Views with Model
|
|
217
|
+
|
|
218
|
+
```csharp
|
|
219
|
+
// Partial with strongly-typed model
|
|
220
|
+
// Pages/Shared/_ProductCard.cshtml
|
|
221
|
+
@model ProductDto
|
|
222
|
+
|
|
223
|
+
<div class="card">
|
|
224
|
+
<img src="@Model.ImageUrl" alt="@Model.Name" />
|
|
225
|
+
<h5>@Model.Name</h5>
|
|
226
|
+
<p>@Model.Price.ToString("C")</p>
|
|
227
|
+
<a asp-page="/Products/Details" asp-route-id="@Model.Id">View</a>
|
|
228
|
+
</div>
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
```html
|
|
232
|
+
@* Usage *@
|
|
233
|
+
@foreach (var product in Model.Products)
|
|
234
|
+
{
|
|
235
|
+
<partial name="_ProductCard" model="product" />
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
@* Or with tag helper *@
|
|
239
|
+
<partial name="_ProductCard" model="Model.FeaturedProduct" />
|
|
240
|
+
|
|
241
|
+
@* Pass additional ViewData *@
|
|
242
|
+
<partial name="_ProductCard" model="product"
|
|
243
|
+
view-data='new ViewDataDictionary(ViewData) { { "ShowActions", true } }' />
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Areas Organization
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
Areas/
|
|
250
|
+
├── Admin/
|
|
251
|
+
│ ├── Pages/
|
|
252
|
+
│ │ ├── _ViewStart.cshtml @{ Layout = "_AdminLayout"; }
|
|
253
|
+
│ │ ├── Dashboard/
|
|
254
|
+
│ │ │ └── Index.cshtml
|
|
255
|
+
│ │ └── Users/
|
|
256
|
+
│ │ ├── Index.cshtml
|
|
257
|
+
│ │ └── Edit.cshtml
|
|
258
|
+
│ └── _AdminLayout.cshtml
|
|
259
|
+
└── Identity/
|
|
260
|
+
└── Pages/
|
|
261
|
+
└── Account/
|
|
262
|
+
├── Login.cshtml
|
|
263
|
+
└── Register.cshtml
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
```html
|
|
267
|
+
@* Link to area page *@
|
|
268
|
+
<a asp-area="Admin" asp-page="/Dashboard/Index">Admin Dashboard</a>
|
|
269
|
+
<a asp-area="Identity" asp-page="/Account/Login">Login</a>
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## TempData and Flash Messages
|
|
273
|
+
|
|
274
|
+
```csharp
|
|
275
|
+
// Set messages
|
|
276
|
+
TempData["Success"] = "Record saved successfully";
|
|
277
|
+
TempData["Error"] = "An error occurred";
|
|
278
|
+
TempData["Warning"] = "Please review your changes";
|
|
279
|
+
|
|
280
|
+
// In PageModel
|
|
281
|
+
public IActionResult OnPostDelete(int id)
|
|
282
|
+
{
|
|
283
|
+
var result = _service.Delete(id);
|
|
284
|
+
|
|
285
|
+
if (result.IsSuccess)
|
|
286
|
+
{
|
|
287
|
+
TempData["Success"] = $"Item {id} deleted";
|
|
288
|
+
return RedirectToPage("./Index");
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
TempData["Error"] = result.Error;
|
|
292
|
+
return RedirectToPage();
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
```html
|
|
297
|
+
@* _Layout.cshtml - Flash message partial *@
|
|
298
|
+
@if (TempData["Success"] is string success)
|
|
299
|
+
{
|
|
300
|
+
<div class="alert alert-success alert-dismissible fade show">
|
|
301
|
+
@success
|
|
302
|
+
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
303
|
+
</div>
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
@if (TempData["Error"] is string error)
|
|
307
|
+
{
|
|
308
|
+
<div class="alert alert-danger alert-dismissible fade show">
|
|
309
|
+
@error
|
|
310
|
+
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
311
|
+
</div>
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
@if (TempData["Warning"] is string warning)
|
|
315
|
+
{
|
|
316
|
+
<div class="alert alert-warning alert-dismissible fade show">
|
|
317
|
+
@warning
|
|
318
|
+
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
319
|
+
</div>
|
|
320
|
+
}
|
|
321
|
+
```
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: .NET Security
|
|
3
|
+
description: Security standards for .NET applications based on OWASP guidelines.
|
|
4
|
+
metadata:
|
|
5
|
+
labels: [security, auth, owasp, dotnet]
|
|
6
|
+
triggers:
|
|
7
|
+
files: ['**/*.cs', '**/appsettings*.json']
|
|
8
|
+
keywords: [Authorize, Authentication, Identity, JWT, CORS, password]
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# .NET Security
|
|
12
|
+
|
|
13
|
+
## **Priority: P0 (CRITICAL)**
|
|
14
|
+
|
|
15
|
+
Security standards for .NET applications based on OWASP guidelines.
|
|
16
|
+
|
|
17
|
+
## Implementation Guidelines
|
|
18
|
+
|
|
19
|
+
- **Authentication**: ASP.NET Identity for users, JWT Bearer for APIs, Cookie auth for web apps.
|
|
20
|
+
- **Authorization**: Policy-based over role-based. Resource-based for fine-grained control.
|
|
21
|
+
- **Input Validation**: `FluentValidation` or `DataAnnotations`. Validate at API boundaries.
|
|
22
|
+
- **SQL Injection**: Always use parameterized queries. EF Core and Dapper handle this automatically.
|
|
23
|
+
- **XSS Prevention**: Razor auto-encodes by default. Use `HtmlEncoder` for manual encoding.
|
|
24
|
+
- **CSRF**: Anti-forgery tokens for forms. `SameSite=Strict` cookies.
|
|
25
|
+
- **Secrets**: Never hardcode. Use User Secrets (dev), Azure Key Vault (prod).
|
|
26
|
+
- **HTTPS**: Always `UseHttpsRedirection()`. Enable HSTS in production.
|
|
27
|
+
- **Headers**: Use security headers middleware (CSP, X-Frame-Options, etc.).
|
|
28
|
+
- **Rate Limiting**: Use built-in `RateLimiter` middleware (.NET 7+).
|
|
29
|
+
|
|
30
|
+
## Anti-Patterns
|
|
31
|
+
|
|
32
|
+
- **No hardcoded secrets**: Never commit API keys, connection strings, passwords.
|
|
33
|
+
- **No `[AllowAnonymous]` on sensitive endpoints**: Review all anonymous access.
|
|
34
|
+
- **No raw SQL with interpolation**: `$"SELECT * FROM Users WHERE Id = {id}"` is vulnerable.
|
|
35
|
+
- **No `*` CORS in production**: Specify allowed origins explicitly.
|
|
36
|
+
- **No disabled SSL validation**: Never `ServerCertificateCustomValidationCallback = (_, _, _, _) => true`.
|
|
37
|
+
|
|
38
|
+
## Code
|
|
39
|
+
|
|
40
|
+
```csharp
|
|
41
|
+
// JWT Bearer configuration
|
|
42
|
+
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
|
43
|
+
.AddJwtBearer(options =>
|
|
44
|
+
{
|
|
45
|
+
options.TokenValidationParameters = new TokenValidationParameters
|
|
46
|
+
{
|
|
47
|
+
ValidateIssuer = true,
|
|
48
|
+
ValidateAudience = true,
|
|
49
|
+
ValidateLifetime = true,
|
|
50
|
+
ValidateIssuerSigningKey = true,
|
|
51
|
+
ValidIssuer = builder.Configuration["Jwt:Issuer"],
|
|
52
|
+
ValidAudience = builder.Configuration["Jwt:Audience"],
|
|
53
|
+
IssuerSigningKey = new SymmetricSecurityKey(
|
|
54
|
+
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!))
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Policy-based authorization
|
|
59
|
+
builder.Services.AddAuthorization(options =>
|
|
60
|
+
{
|
|
61
|
+
options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
|
|
62
|
+
options.AddPolicy("CanEditOrder", policy =>
|
|
63
|
+
policy.Requirements.Add(new ResourceOwnerRequirement()));
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Parameterized query (safe from SQL injection)
|
|
67
|
+
var user = await connection.QuerySingleAsync<User>(
|
|
68
|
+
"SELECT * FROM Users WHERE Id = @Id AND Status = @Status",
|
|
69
|
+
new { Id = userId, Status = "Active" });
|
|
70
|
+
|
|
71
|
+
// Rate limiting
|
|
72
|
+
builder.Services.AddRateLimiter(options =>
|
|
73
|
+
{
|
|
74
|
+
options.AddFixedWindowLimiter("api", cfg =>
|
|
75
|
+
{
|
|
76
|
+
cfg.Window = TimeSpan.FromMinutes(1);
|
|
77
|
+
cfg.PermitLimit = 100;
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Reference & Examples
|
|
83
|
+
|
|
84
|
+
For JWT patterns, Identity configuration, and security headers:
|
|
85
|
+
See [references/REFERENCE.md](references/REFERENCE.md).
|
|
86
|
+
|
|
87
|
+
## Related Topics
|
|
88
|
+
|
|
89
|
+
language | best-practices | aspnet-core
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# .NET Security Reference
|
|
2
|
+
|
|
3
|
+
Authentication, authorization, and security headers configuration.
|
|
4
|
+
|
|
5
|
+
## References
|
|
6
|
+
|
|
7
|
+
- [**JWT Authentication**](jwt-authentication.md) - Token generation and validation.
|
|
8
|
+
- [**Identity Configuration**](identity-configuration.md) - Password rules, lockout settings.
|
|
9
|
+
- [**Security Headers**](security-headers.md) - CSP, HSTS, X-Frame-Options.
|
|
10
|
+
|
|
11
|
+
## JWT Token Generation
|
|
12
|
+
|
|
13
|
+
```csharp
|
|
14
|
+
public class TokenService(IOptions<JwtSettings> options)
|
|
15
|
+
{
|
|
16
|
+
private readonly JwtSettings _settings = options.Value;
|
|
17
|
+
|
|
18
|
+
public string GenerateToken(User user)
|
|
19
|
+
{
|
|
20
|
+
var claims = new[]
|
|
21
|
+
{
|
|
22
|
+
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
|
23
|
+
new Claim(ClaimTypes.Email, user.Email),
|
|
24
|
+
new Claim(ClaimTypes.Role, user.Role),
|
|
25
|
+
new Claim("tenant_id", user.TenantId.ToString())
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_settings.Key));
|
|
29
|
+
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
|
30
|
+
|
|
31
|
+
var token = new JwtSecurityToken(
|
|
32
|
+
issuer: _settings.Issuer,
|
|
33
|
+
audience: _settings.Audience,
|
|
34
|
+
claims: claims,
|
|
35
|
+
expires: DateTime.UtcNow.AddHours(_settings.ExpiryHours),
|
|
36
|
+
signingCredentials: credentials);
|
|
37
|
+
|
|
38
|
+
return new JwtSecurityTokenHandler().WriteToken(token);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public ClaimsPrincipal? ValidateToken(string token)
|
|
42
|
+
{
|
|
43
|
+
var handler = new JwtSecurityTokenHandler();
|
|
44
|
+
try
|
|
45
|
+
{
|
|
46
|
+
return handler.ValidateToken(token, new TokenValidationParameters
|
|
47
|
+
{
|
|
48
|
+
ValidateIssuer = true,
|
|
49
|
+
ValidateAudience = true,
|
|
50
|
+
ValidateLifetime = true,
|
|
51
|
+
ValidateIssuerSigningKey = true,
|
|
52
|
+
ValidIssuer = _settings.Issuer,
|
|
53
|
+
ValidAudience = _settings.Audience,
|
|
54
|
+
IssuerSigningKey = new SymmetricSecurityKey(
|
|
55
|
+
Encoding.UTF8.GetBytes(_settings.Key)),
|
|
56
|
+
ClockSkew = TimeSpan.Zero
|
|
57
|
+
}, out _);
|
|
58
|
+
}
|
|
59
|
+
catch
|
|
60
|
+
{
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## ASP.NET Identity Configuration
|
|
68
|
+
|
|
69
|
+
```csharp
|
|
70
|
+
// Program.cs
|
|
71
|
+
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
|
|
72
|
+
{
|
|
73
|
+
// Password requirements
|
|
74
|
+
options.Password.RequiredLength = 12;
|
|
75
|
+
options.Password.RequireDigit = true;
|
|
76
|
+
options.Password.RequireLowercase = true;
|
|
77
|
+
options.Password.RequireUppercase = true;
|
|
78
|
+
options.Password.RequireNonAlphanumeric = true;
|
|
79
|
+
options.Password.RequiredUniqueChars = 4;
|
|
80
|
+
|
|
81
|
+
// Lockout settings
|
|
82
|
+
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
|
|
83
|
+
options.Lockout.MaxFailedAccessAttempts = 5;
|
|
84
|
+
options.Lockout.AllowedForNewUsers = true;
|
|
85
|
+
|
|
86
|
+
// User settings
|
|
87
|
+
options.User.RequireUniqueEmail = true;
|
|
88
|
+
options.SignIn.RequireConfirmedEmail = true;
|
|
89
|
+
})
|
|
90
|
+
.AddEntityFrameworkStores<AppDbContext>()
|
|
91
|
+
.AddDefaultTokenProviders();
|
|
92
|
+
|
|
93
|
+
// Password hasher upgrade (Argon2id recommended for new apps)
|
|
94
|
+
builder.Services.Configure<PasswordHasherOptions>(options =>
|
|
95
|
+
{
|
|
96
|
+
options.IterationCount = 310000; // OWASP recommendation
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Security Headers Middleware
|
|
101
|
+
|
|
102
|
+
```csharp
|
|
103
|
+
// Using a middleware class
|
|
104
|
+
public class SecurityHeadersMiddleware(RequestDelegate next)
|
|
105
|
+
{
|
|
106
|
+
public async Task InvokeAsync(HttpContext context)
|
|
107
|
+
{
|
|
108
|
+
// Prevent clickjacking
|
|
109
|
+
context.Response.Headers.Append("X-Frame-Options", "DENY");
|
|
110
|
+
|
|
111
|
+
// Prevent MIME sniffing
|
|
112
|
+
context.Response.Headers.Append("X-Content-Type-Options", "nosniff");
|
|
113
|
+
|
|
114
|
+
// XSS protection (legacy, CSP is better)
|
|
115
|
+
context.Response.Headers.Append("X-XSS-Protection", "1; mode=block");
|
|
116
|
+
|
|
117
|
+
// Referrer policy
|
|
118
|
+
context.Response.Headers.Append("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
119
|
+
|
|
120
|
+
// Content Security Policy
|
|
121
|
+
context.Response.Headers.Append("Content-Security-Policy",
|
|
122
|
+
"default-src 'self'; " +
|
|
123
|
+
"script-src 'self' 'unsafe-inline'; " +
|
|
124
|
+
"style-src 'self' 'unsafe-inline'; " +
|
|
125
|
+
"img-src 'self' data: https:; " +
|
|
126
|
+
"font-src 'self'; " +
|
|
127
|
+
"frame-ancestors 'none';");
|
|
128
|
+
|
|
129
|
+
// Permissions Policy
|
|
130
|
+
context.Response.Headers.Append("Permissions-Policy",
|
|
131
|
+
"geolocation=(), microphone=(), camera=()");
|
|
132
|
+
|
|
133
|
+
await next(context);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Usage in Program.cs
|
|
138
|
+
app.UseMiddleware<SecurityHeadersMiddleware>();
|
|
139
|
+
app.UseHsts(); // Strict-Transport-Security
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## CORS Configuration
|
|
143
|
+
|
|
144
|
+
```csharp
|
|
145
|
+
// Development (allow specific origins)
|
|
146
|
+
builder.Services.AddCors(options =>
|
|
147
|
+
{
|
|
148
|
+
options.AddPolicy("Development", policy =>
|
|
149
|
+
{
|
|
150
|
+
policy.WithOrigins("http://localhost:3000", "http://localhost:5173")
|
|
151
|
+
.AllowAnyHeader()
|
|
152
|
+
.AllowAnyMethod()
|
|
153
|
+
.AllowCredentials();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
options.AddPolicy("Production", policy =>
|
|
157
|
+
{
|
|
158
|
+
policy.WithOrigins(
|
|
159
|
+
builder.Configuration.GetSection("Cors:AllowedOrigins").Get<string[]>()!)
|
|
160
|
+
.WithHeaders("Authorization", "Content-Type", "X-Requested-With")
|
|
161
|
+
.WithMethods("GET", "POST", "PUT", "DELETE")
|
|
162
|
+
.AllowCredentials()
|
|
163
|
+
.SetPreflightMaxAge(TimeSpan.FromMinutes(10));
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Usage
|
|
168
|
+
app.UseCors(builder.Environment.IsDevelopment() ? "Development" : "Production");
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Resource-Based Authorization
|
|
172
|
+
|
|
173
|
+
```csharp
|
|
174
|
+
// Requirement
|
|
175
|
+
public class ResourceOwnerRequirement : IAuthorizationRequirement { }
|
|
176
|
+
|
|
177
|
+
// Handler
|
|
178
|
+
public class ResourceOwnerHandler : AuthorizationHandler<ResourceOwnerRequirement, Order>
|
|
179
|
+
{
|
|
180
|
+
protected override Task HandleRequirementAsync(
|
|
181
|
+
AuthorizationHandlerContext context,
|
|
182
|
+
ResourceOwnerRequirement requirement,
|
|
183
|
+
Order resource)
|
|
184
|
+
{
|
|
185
|
+
var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
186
|
+
|
|
187
|
+
if (resource.UserId.ToString() == userId ||
|
|
188
|
+
context.User.IsInRole("Admin"))
|
|
189
|
+
{
|
|
190
|
+
context.Succeed(requirement);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return Task.CompletedTask;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Registration
|
|
198
|
+
builder.Services.AddScoped<IAuthorizationHandler, ResourceOwnerHandler>();
|
|
199
|
+
|
|
200
|
+
// Usage in controller
|
|
201
|
+
public async Task<IActionResult> UpdateOrder(int id, UpdateOrderDto dto)
|
|
202
|
+
{
|
|
203
|
+
var order = await _orderService.GetByIdAsync(id);
|
|
204
|
+
if (order is null) return NotFound();
|
|
205
|
+
|
|
206
|
+
var authResult = await _authService.AuthorizeAsync(
|
|
207
|
+
User, order, new ResourceOwnerRequirement());
|
|
208
|
+
|
|
209
|
+
if (!authResult.Succeeded) return Forbid();
|
|
210
|
+
|
|
211
|
+
await _orderService.UpdateAsync(order, dto);
|
|
212
|
+
return NoContent();
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Rate Limiting (.NET 7+)
|
|
217
|
+
|
|
218
|
+
```csharp
|
|
219
|
+
builder.Services.AddRateLimiter(options =>
|
|
220
|
+
{
|
|
221
|
+
// Fixed window: 100 requests per minute
|
|
222
|
+
options.AddFixedWindowLimiter("fixed", cfg =>
|
|
223
|
+
{
|
|
224
|
+
cfg.Window = TimeSpan.FromMinutes(1);
|
|
225
|
+
cfg.PermitLimit = 100;
|
|
226
|
+
cfg.QueueLimit = 10;
|
|
227
|
+
cfg.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Sliding window: smoother rate limiting
|
|
231
|
+
options.AddSlidingWindowLimiter("sliding", cfg =>
|
|
232
|
+
{
|
|
233
|
+
cfg.Window = TimeSpan.FromMinutes(1);
|
|
234
|
+
cfg.PermitLimit = 100;
|
|
235
|
+
cfg.SegmentsPerWindow = 6; // 10-second segments
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Token bucket: burst allowance
|
|
239
|
+
options.AddTokenBucketLimiter("token", cfg =>
|
|
240
|
+
{
|
|
241
|
+
cfg.TokenLimit = 100;
|
|
242
|
+
cfg.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
|
|
243
|
+
cfg.TokensPerPeriod = 20;
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Per-user limiting
|
|
247
|
+
options.AddPolicy("per-user", context =>
|
|
248
|
+
RateLimitPartition.GetFixedWindowLimiter(
|
|
249
|
+
context.User.Identity?.Name ?? context.Connection.RemoteIpAddress?.ToString() ?? "anonymous",
|
|
250
|
+
_ => new FixedWindowRateLimiterOptions
|
|
251
|
+
{
|
|
252
|
+
Window = TimeSpan.FromMinutes(1),
|
|
253
|
+
PermitLimit = 50
|
|
254
|
+
}));
|
|
255
|
+
|
|
256
|
+
options.OnRejected = async (context, token) =>
|
|
257
|
+
{
|
|
258
|
+
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
|
|
259
|
+
await context.HttpContext.Response.WriteAsJsonAsync(new
|
|
260
|
+
{
|
|
261
|
+
error = "Too many requests. Please try again later."
|
|
262
|
+
}, token);
|
|
263
|
+
};
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Usage
|
|
267
|
+
app.UseRateLimiter();
|
|
268
|
+
|
|
269
|
+
app.MapGet("/api/data", () => "Hello")
|
|
270
|
+
.RequireRateLimiting("fixed");
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Secrets Management
|
|
274
|
+
|
|
275
|
+
```csharp
|
|
276
|
+
// Development: User Secrets
|
|
277
|
+
// dotnet user-secrets init
|
|
278
|
+
// dotnet user-secrets set "Database:Password" "secret123"
|
|
279
|
+
|
|
280
|
+
// Production: Azure Key Vault
|
|
281
|
+
builder.Configuration.AddAzureKeyVault(
|
|
282
|
+
new Uri($"https://{builder.Configuration["KeyVault:Name"]}.vault.azure.net/"),
|
|
283
|
+
new DefaultAzureCredential());
|
|
284
|
+
|
|
285
|
+
// Environment variables (Docker/K8s)
|
|
286
|
+
builder.Configuration.AddEnvironmentVariables(prefix: "APP_");
|
|
287
|
+
|
|
288
|
+
// Never do this:
|
|
289
|
+
// ❌ var password = "hardcoded_password";
|
|
290
|
+
// ❌ var connString = "Server=...;Password=secret;";
|
|
291
|
+
|
|
292
|
+
// Always do this:
|
|
293
|
+
// ✅ var password = builder.Configuration["Database:Password"];
|
|
294
|
+
// ✅ var connString = builder.Configuration.GetConnectionString("Default");
|
|
295
|
+
```
|