diff --git a/eng/targets/Helix.props b/eng/targets/Helix.props
index 89f7a626b2d6..a522bcfea552 100644
--- a/eng/targets/Helix.props
+++ b/eng/targets/Helix.props
@@ -12,7 +12,7 @@
00:45:00
$(MSBuildProjectName)--$(TargetFramework)
false
- 16.11.0
+ 20.7.0
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/RazorPagesWeb-CSharp.csproj.in b/src/ProjectTemplates/Web.ProjectTemplates/RazorPagesWeb-CSharp.csproj.in
index ee04cb442ea1..2ed95f454912 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/RazorPagesWeb-CSharp.csproj.in
+++ b/src/ProjectTemplates/Web.ProjectTemplates/RazorPagesWeb-CSharp.csproj.in
@@ -4,7 +4,7 @@
${DefaultNetCoreTargetFramework}
enable
enable
- aspnet-Company.WebApplication1-0ce56475-d1db-490f-8af1-a881ea4fcd2d
+ aspnet-Company.WebApplication1-53bc9b9d-9d6a-45d4-8429-2a2761773502
True
Company.WebApplication1
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/dotnetcli.host.json
index a8c678b039b3..897cc41741ca 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/dotnetcli.host.json
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/dotnetcli.host.json
@@ -21,6 +21,9 @@
"IncludeSampleContent": {
"isHidden": true
},
+ "UseLocalDB": {
+ "longName": "use-local-db"
+ },
"Framework": {
"longName": "framework"
},
@@ -51,5 +54,8 @@
"longName": "use-program-main",
"shortName": ""
}
- }
+ },
+ "usageExamples": [
+ "-int auto --auth individual --use-local-db"
+ ]
}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.cs.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.cs.json
index a4cec61a3fdf..1058719b3050 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.cs.json
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.cs.json
@@ -4,6 +4,7 @@
"description": "Ĺ ablona projektu pro vytvoĹ™enĂ webovĂ© aplikace Blazor, která podporuje vykreslovánĂ na stranÄ› serveru i interaktivitu klienta. Tato šablona se dá pouĹľĂt pro webovĂ© aplikace s bohatĂ˝mi dynamickĂ˝mi uĹľivatelskĂ˝mi rozhranĂmi (UI).",
"symbols/Framework/description": "CĂlová architektura pro projekt",
"symbols/Framework/choices/net8.0/description": "CĂlovĂ˝ net8.0",
+ "symbols/UserSecretsId/description": "The ID to use for secrets (use with Individual auth).",
"symbols/skipRestore/description": "Pokud se tato moĹľnost zadá, pĹ™eskoÄŤĂ automatickĂ© obnovenĂ projektu pĹ™i vytvoĹ™enĂ.",
"symbols/ExcludeLaunchSettings/description": "Určuje, jestli se má z vygenerované šablony vyloučit soubor launchSettings.json.",
"symbols/kestrelHttpPort/description": "ÄŚĂslo portu, kterĂ˝ se má pouĹľĂt pro koncovĂ˝ bod HTTP v souboru launchSettings.json.",
@@ -29,6 +30,10 @@
"symbols/IncludeSampleContent/displayName": "_Zahrnout ukázkové stránky",
"symbols/IncludeSampleContent/description": "Nastavuje, jestli se majĂ pĹ™idávat ukázkovĂ© stránky a styly pro demonstraci základnĂch vzorĹŻ pouĹľitĂ.",
"symbols/Empty/description": "Nastavuje, jestli se majĂ vynechat ukázkovĂ© stránky a styly, kterĂ© demonstrujĂ základnĂ vzory pouĹľitĂ.",
+ "symbols/auth/choices/None/description": "No authentication",
+ "symbols/auth/choices/Individual/description": "Individual authentication",
+ "symbols/auth/description": "The type of authentication to use",
+ "symbols/UseLocalDB/description": "Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual is specified.",
"symbols/AllInteractive/displayName": "_Povolit interaktivnà vykreslovánà globálně na celém webu",
"symbols/AllInteractive/description": "Konfiguruje, jestli se má kaĹľdá stránka nastavit jako interaktivnĂ, a to pouĹľitĂm interaktivnĂho reĹľimu vykreslovánĂ na nejvyššà úrovni. Pokud se nastavĂ na false, stránky budou ve vĂ˝chozĂm nastavenĂ pouĹľĂvat vykreslovánĂ statickĂ©ho serveru a dajĂ se oznaÄŤit jako interaktivnĂ pro jednotlivĂ© stránky nebo komponenty.",
"symbols/NoHttps/description": "UrÄŤuje, jestli se má protokol HTTPS vypnout. Tato moĹľnost platĂ jenom v pĹ™ĂpadÄ›, Ĺľe se pro --auth nepouĹľĂvajĂ Individual, IndividualB2C, SingleOrg ani MultiOrg.",
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.de.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.de.json
index 34edd5ff3e60..9372f5a4b0b0 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.de.json
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.de.json
@@ -4,6 +4,7 @@
"description": "Eine Projektvorlage zum Erstellen einer Blazor-Web-App, die sowohl serverseitiges Rendering als auch Clientinteraktivität unterstützt. Diese Vorlage kann für Web-Apps mit umfangreichen dynamischen Benutzeroberflächen (UIs) verwendet werden.",
"symbols/Framework/description": "Das Zielframework fĂĽr das Projekt.",
"symbols/Framework/choices/net8.0/description": "Ziel net8.0",
+ "symbols/UserSecretsId/description": "The ID to use for secrets (use with Individual auth).",
"symbols/skipRestore/description": "Wenn angegeben, wird die automatische Wiederherstellung des Projekts beim Erstellen ĂĽbersprungen.",
"symbols/ExcludeLaunchSettings/description": "Ob launchSettings.json aus der generierten Vorlage ausgeschlossen werden soll.",
"symbols/kestrelHttpPort/description": "Portnummer, die fĂĽr den HTTP Endpunkt in launchSettings.json verwendet werden soll.",
@@ -29,6 +30,10 @@
"symbols/IncludeSampleContent/displayName": "_Include Beispielseiten",
"symbols/IncludeSampleContent/description": "Konfiguriert, ob Beispielseiten und Stile hinzugefĂĽgt werden, um grundlegende Verwendungsmuster zu veranschaulichen.",
"symbols/Empty/description": "Konfiguriert, ob Beispielseiten und Formatierungen weggelassen werden sollen, die grundlegende Verwendungsmuster veranschaulichen.",
+ "symbols/auth/choices/None/description": "No authentication",
+ "symbols/auth/choices/Individual/description": "Individual authentication",
+ "symbols/auth/description": "The type of authentication to use",
+ "symbols/UseLocalDB/description": "Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual is specified.",
"symbols/AllInteractive/displayName": "_Aktivieren des interaktiven Renderings global auf der gesamten Website",
"symbols/AllInteractive/description": "Konfiguriert, ob jede Seite interaktiv werden soll, indem ein interaktiver Rendermodus auf der obersten Ebene angewendet wird. False gibt an, dass Seiten standardmäßig statisches Serverrendering verwenden und auf Seiten- oder Komponentenbasis als interaktiv markiert werden können.",
"symbols/NoHttps/description": "Ob HTTPS deaktiviert werden soll. Diese Option gilt nur, wenn Individual, IndividualB2C, SingleOrg oder MultiOrg nicht fĂĽr --auth verwendet werden.",
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.en.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.en.json
index 60d16e15ffd9..6db424a9f348 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.en.json
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.en.json
@@ -4,12 +4,13 @@
"description": "A project template for creating a Blazor web app that supports both server-side rendering and client interactivity. This template can be used for web apps with rich dynamic user interfaces (UIs).",
"symbols/Framework/description": "The target framework for the project.",
"symbols/Framework/choices/net8.0/description": "Target net8.0",
+ "symbols/UserSecretsId/description": "The ID to use for secrets (use with Individual auth).",
"symbols/skipRestore/description": "If specified, skips the automatic restore of the project on create.",
"symbols/ExcludeLaunchSettings/description": "Whether to exclude launchSettings.json from the generated template.",
"symbols/kestrelHttpPort/description": "Port number to use for the HTTP endpoint in launchSettings.json.",
- "symbols/kestrelHttpsPort/description": "Port number to use for the HTTPS endpoint in launchSettings.json. This option is only applicable when the parameter no-https is not used (no-https will be ignored if either IndividualAuth or OrganizationalAuth is used).",
+ "symbols/kestrelHttpsPort/description": "Port number to use for the HTTPS endpoint in launchSettings.json. This option is only applicable when the parameter no-https is not used (no-https will be ignored if Individual auth is used).",
"symbols/iisHttpPort/description": "Port number to use for the IIS Express HTTP endpoint in launchSettings.json.",
- "symbols/iisHttpsPort/description": "Port number to use for the IIS Express HTTPS endpoint in launchSettings.json. This option is only applicable when the parameter no-https is not used (no-https will be ignored if either IndividualAuth or OrganizationalAuth is used).",
+ "symbols/iisHttpsPort/description": "Port number to use for the IIS Express HTTPS endpoint in launchSettings.json. This option is only applicable when the parameter no-https is not used (no-https will be ignored if Individual auth is used).",
"symbols/InteractivityPlatform/displayName": "_Interactivity type",
"symbols/InteractivityPlatform/description": "Chooses which hosting platform to use for interactive components",
"symbols/InteractivityPlatform/choices/None/displayName": "None",
@@ -29,9 +30,13 @@
"symbols/IncludeSampleContent/displayName": "_Include sample pages",
"symbols/IncludeSampleContent/description": "Configures whether to add sample pages and styling to demonstrate basic usage patterns.",
"symbols/Empty/description": "Configures whether to omit sample pages and styling that demonstrate basic usage patterns.",
+ "symbols/auth/choices/None/description": "No authentication",
+ "symbols/auth/choices/Individual/description": "Individual authentication",
+ "symbols/auth/description": "The type of authentication to use",
+ "symbols/UseLocalDB/description": "Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual is specified.",
"symbols/AllInteractive/displayName": "_Enable interactive rendering globally throughout the site",
"symbols/AllInteractive/description": "Configures whether to make every page interactive by applying an interactive render mode at the top level. If false, pages will use static server rendering by default, and can be marked interactive on a per-page or per-component basis.",
- "symbols/NoHttps/description": "Whether to turn off HTTPS. This option only applies if Individual, IndividualB2C, SingleOrg, or MultiOrg aren't used for --auth.",
+ "symbols/NoHttps/description": "Whether to turn off HTTPS. This option only applies if Individual isn't used for --auth.",
"symbols/UseProgramMain/displayName": "Do not use _top-level statements",
"symbols/UseProgramMain/description": "Whether to generate an explicit Program class and Main method instead of top-level statements.",
"postActions/restore/description": "Restore NuGet packages required by this project.",
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.es.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.es.json
index 2837c19d8e89..f016bdcf31b1 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.es.json
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.es.json
@@ -4,6 +4,7 @@
"description": "Plantilla de proyecto para crear una aplicación web de Blazor que admita tanto la representación del lado del servidor como la interactividad del cliente. Esta plantilla se puede usar para las aplicaciones web con interfaces de usuario dinámicas enriquecidas.",
"symbols/Framework/description": "Marco de destino del proyecto.",
"symbols/Framework/choices/net8.0/description": "net8.0 de destino",
+ "symbols/UserSecretsId/description": "The ID to use for secrets (use with Individual auth).",
"symbols/skipRestore/description": "Si se especifica, se omite la restauración automática del proyecto durante la creación.",
"symbols/ExcludeLaunchSettings/description": "Indica si se va a excluir launchSettings.json de la plantilla generada.",
"symbols/kestrelHttpPort/description": "NĂşmero de puerto que se va a usar para el punto de conexiĂłn HTTP en launchSettings.json.",
@@ -29,6 +30,10 @@
"symbols/IncludeSampleContent/displayName": "_Incluir páginas de ejemplo",
"symbols/IncludeSampleContent/description": "Configura si se van a agregar páginas de ejemplo y estilos para mostrar patrones de uso básicos.",
"symbols/Empty/description": "Configura si se omiten las páginas de ejemplo y los estilos que muestran patrones de uso básicos.",
+ "symbols/auth/choices/None/description": "No authentication",
+ "symbols/auth/choices/Individual/description": "Individual authentication",
+ "symbols/auth/description": "The type of authentication to use",
+ "symbols/UseLocalDB/description": "Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual is specified.",
"symbols/AllInteractive/displayName": "_Enable representaciĂłn interactiva globalmente en todo el sitio",
"symbols/AllInteractive/description": "Configura si todas las páginas son interactivas aplicando un modo de representación interactivo en el nivel superior. Si es false, las páginas usarán la representación de servidor estático de forma predeterminada y se pueden marcar como interactivas por página o por componente.",
"symbols/NoHttps/description": "Si se va a desactivar HTTPS. Esta opciĂłn solo se aplica si Individual, IndividualB2C, SingleOrg o MultiOrg no se usan para --auth.",
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.fr.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.fr.json
index 69426fe1e68d..fa5a62f1e40c 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.fr.json
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.fr.json
@@ -4,6 +4,7 @@
"description": "Modèle de projet pour la création d’une application web Blazor qui prend en charge le rendu côté serveur et l’interactivité du client. Ce modèle peut être utilisé pour les applications web avec des interfaces utilisateur dynamiques enrichies.",
"symbols/Framework/description": "Framework cible du projet.",
"symbols/Framework/choices/net8.0/description": "Cible net8.0",
+ "symbols/UserSecretsId/description": "The ID to use for secrets (use with Individual auth).",
"symbols/skipRestore/description": "S’il est spécifié, ignore la restauration automatique du projet lors de la création.",
"symbols/ExcludeLaunchSettings/description": "Indique s’il faut exclure launchSettings.json du modèle généré.",
"symbols/kestrelHttpPort/description": "Numéro de port à utiliser pour le point de terminaison HTTP dans launchSettings.json.",
@@ -29,6 +30,10 @@
"symbols/IncludeSampleContent/displayName": "_Inclure des exemples de pages",
"symbols/IncludeSampleContent/description": "Configure s'il faut ajouter des exemples de pages et de style pour illustrer les modèles d'utilisation de base.",
"symbols/Empty/description": "Configure s'il faut omettre les exemples de pages et le style qui illustrent les modèles d'utilisation de base.",
+ "symbols/auth/choices/None/description": "No authentication",
+ "symbols/auth/choices/Individual/description": "Individual authentication",
+ "symbols/auth/description": "The type of authentication to use",
+ "symbols/UseLocalDB/description": "Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual is specified.",
"symbols/AllInteractive/displayName": "_Enable rendu interactif globalement sur l’ensemble du site",
"symbols/AllInteractive/description": "Configure si chaque page doit être interactive en appliquant un mode de rendu interactif au niveau supérieur. Si la valeur est False, les pages utilisent le rendu de serveur statique par défaut et peuvent être marquées comme interactives par page ou par composant.",
"symbols/NoHttps/description": "Indique s’il faut désactiver HTTPS. Cette option s’applique uniquement si Individual, IndividualB2C, SingleOrg ou MultiOrg ne sont pas utilisés pour --auth.",
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.it.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.it.json
index ce6ab9d14432..27be9e574b7c 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.it.json
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.it.json
@@ -4,6 +4,7 @@
"description": "Modello di progetto per la creazione di un'app Web Blazor che supporta sia il rendering lato server sia l'interattività client. Questo modello può essere usato per app Web con interfacce utente dinamiche avanzate.",
"symbols/Framework/description": "Il framework di destinazione per il progetto.",
"symbols/Framework/choices/net8.0/description": "Destinazione net8.0",
+ "symbols/UserSecretsId/description": "The ID to use for secrets (use with Individual auth).",
"symbols/skipRestore/description": "Se specificato, ignora il ripristino automatico del progetto durante la creazione.",
"symbols/ExcludeLaunchSettings/description": "Indica se escludere launchSettings.json dal modello generato.",
"symbols/kestrelHttpPort/description": "Numero di porta da usare per l'endpoint HTTP in launchSettings.json.",
@@ -29,6 +30,10 @@
"symbols/IncludeSampleContent/displayName": "_Include pagine di esempio",
"symbols/IncludeSampleContent/description": "Consente di configurare se aggiungere pagine di esempio e stile per mostrare modelli di utilizzo di base.",
"symbols/Empty/description": "Consente di configurare se omettere pagine di esempio e stile che mostrano modelli di utilizzo di base.",
+ "symbols/auth/choices/None/description": "No authentication",
+ "symbols/auth/choices/Individual/description": "Individual authentication",
+ "symbols/auth/description": "The type of authentication to use",
+ "symbols/UseLocalDB/description": "Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual is specified.",
"symbols/AllInteractive/displayName": "_Enable rendering interattivo a livello globale in tutto il sito",
"symbols/AllInteractive/description": "Configura se rendere interattiva ogni pagina applicando una modalitĂ di rendering interattiva al livello superiore. Se false, le pagine useranno il rendering statico del server per impostazione predefinita e possono essere contrassegnate come interattive per pagina o per componente.",
"symbols/NoHttps/description": "Indica se disattivare HTTPS. Questa opzione si applica solo se Individual, IndividualB2C, SingleOrg o MultiOrg non vengono usati per --auth.",
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.ja.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.ja.json
index f429a9116d95..5955f279e593 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.ja.json
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.ja.json
@@ -4,6 +4,7 @@
"description": "サăĽăăĽĺ´ă®ă¬ăłă€ăŞăłă‚°ă¨ă‚Żă©ă‚¤ă‚˘ăłăă®ĺŻľč©±ć©źč˝ă®ä¸ˇć–ąă‚’サăťăĽăă™ă‚‹ Blazor Web アă—ăŞă‚’作ćă™ă‚‹ăźă‚ă®ă—ăジェクă ă†ăłă—ă¬ăĽăă§ă™ă€‚ă“ă®ă†ăłă—ă¬ăĽăăŻă€ăŞăăăŞĺ‹•çš„ă¦ăĽă‚¶ăĽ イăłă‚żăĽă•ェイス (UI) ă‚’ćŚă¤ Web アă—ăŞă«ä˝żç”¨ă§ăŤăľă™ă€‚",
"symbols/Framework/description": "ă—ăジェクăă®ă‚żăĽă‚˛ăă ă•ă¬ăĽă ăŻăĽă‚Żă§ă™ă€‚",
"symbols/Framework/choices/net8.0/description": "タăĽă‚˛ăă net8.0",
+ "symbols/UserSecretsId/description": "The ID to use for secrets (use with Individual auth).",
"symbols/skipRestore/description": "指定ă—ăźĺ ´ĺă€ä˝ść時ă«ă—ăジェクăă®č‡Şĺ‹•ĺľ©ĺ…ăŚă‚ąă‚ăă—ă•れăľă™ă€‚",
"symbols/ExcludeLaunchSettings/description": "生ćă•れăźă†ăłă—ă¬ăĽăă‹ă‚‰ launchSettings.json を除外ă™ă‚‹ă‹ă©ă†ă‹ă€‚",
"symbols/kestrelHttpPort/description": "launchSettings.json ă® HTTP エăłă‰ăťă‚¤ăłăă«ä˝żç”¨ă™ă‚‹ăťăĽă番号。",
@@ -29,6 +30,10 @@
"symbols/IncludeSampleContent/displayName": "サăłă—ă« ăšăĽă‚¸ă‚’ĺ«ă‚ă‚‹(_I)",
"symbols/IncludeSampleContent/description": "基本的ăŞä˝żç”¨ă‘タăĽăłă‚’示ă™ă‚µăłă—ă« ăšăĽă‚¸ă¨ă‚ąă‚żă‚¤ă«ă‚’čż˝ĺŠ ă™ă‚‹ă‹ă©ă†ă‹ă‚’ć§‹ćă—ăľă™ă€‚",
"symbols/Empty/description": "基本的ăŞä˝żç”¨ă‘タăĽăłă‚’示ă™ă‚µăłă—ă« ăšăĽă‚¸ă¨ă‚ąă‚żă‚¤ă«ă‚’çśç•Ąă™ă‚‹ă‹ă©ă†ă‹ă‚’ć§‹ćă—ăľă™ă€‚",
+ "symbols/auth/choices/None/description": "No authentication",
+ "symbols/auth/choices/Individual/description": "Individual authentication",
+ "symbols/auth/description": "The type of authentication to use",
+ "symbols/UseLocalDB/description": "Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual is specified.",
"symbols/AllInteractive/displayName": "イăłă‚żă©ă‚Żă†ă‚Łă“ă†ă‚Ł ă¬ăłă€ăŞăłă‚°ă‚’サイă全体ă§ă‚°ăăĽăă«ă«ćś‰ĺŠąĺŚ–(_E)",
"symbols/AllInteractive/description": "最上位ă¬ă™ă«ă§ĺŻľč©±ĺž‹ă¬ăłă€ăŞăłă‚° ă˘ăĽă‰ă‚’é©ç”¨ă—ă¦ă€ă™ăąă¦ă®ăšăĽă‚¸ă‚’対話型ă«ă™ă‚‹ă‹ă©ă†ă‹ă‚’ć§‹ćă—ăľă™ă€‚false ă®ĺ ´ĺă€ăšăĽă‚¸ăŻć—˘ĺ®šă§éť™çš„サăĽă㼠ă¬ăłă€ăŞăłă‚°ă‚’使用ă—ă€ăšăĽă‚¸ĺŤä˝ŤăľăźăŻă‚łăłăťăĽăŤăłăĺŤä˝Ťă§ĺŻľč©±ĺž‹ă¨ă—ă¦ăžăĽă‚Żă§ăŤăľă™ă€‚",
"symbols/NoHttps/description": "HTTPS をオă•ă«ă™ă‚‹ă‹ă©ă†ă‹ă€‚ă“ă®ă‚Şă—ă‚·ă§ăłăŻă€Individuală€IndividualB2Că€SingleOrgă€ăľăźăŻ MultiOrg ㌠--auth ă«ä˝żç”¨ă•れă¦ă„ăŞă„ĺ ´ĺă«ă®ăżé©ç”¨ă•れăľă™ă€‚",
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.ko.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.ko.json
index e081f6b2a958..63fd70eb6815 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.ko.json
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.ko.json
@@ -4,6 +4,7 @@
"description": "서버 측 ë ŚëŤ”ë§ ë°Ź í´ëťĽěť´ě–¸íЏ ëŚ€í™”í• ěž‘ě—…ěť„ ëŞ¨ë‘ ě§€ě›í•는 Blazor 웹앱을 만들기 위한 í”„ëˇśě ťíŠ¸ 템플릿입ë‹ë‹¤. ěť´ 템플릿은 풍부한 동ě UI(ě‚¬ěš©ěž ěť¸í„°íŽěť´ěФ)ę°€ ěžëŠ” ě›ąě•±ě— ě‚¬ěš©í• ě ěžěеë‹ë‹¤.",
"symbols/Framework/description": "í”„ëˇśě ťíŠ¸ě— ëŚ€í•ś 대ě 프ë 임워í¬ěž…ë‹ë‹¤.",
"symbols/Framework/choices/net8.0/description": "대ě net8.0",
+ "symbols/UserSecretsId/description": "The ID to use for secrets (use with Individual auth).",
"symbols/skipRestore/description": "ě§€ě •ëś ę˛˝ěš°, í”„ëˇśě ťíŠ¸ ěťě„± 시 ěžëŹ™ ëłµě›ěť„ ę±´ë„ëśë‹ë‹¤.",
"symbols/ExcludeLaunchSettings/description": "ěťě„±ëś 템플릿ě—서 launchSettings.jsoněť„ ě śě™¸í• ě§€ 여부입ë‹ë‹¤.",
"symbols/kestrelHttpPort/description": "launchSettings.jsoněť HTTP ě—”ë“śíŹ¬ěť¸íŠ¸ě— ě‚¬ěš©í• íŹ¬íŠ¸ ë˛í¸ěž…ë‹ë‹¤.",
@@ -29,6 +30,10 @@
"symbols/IncludeSampleContent/displayName": "ě플 íŽěť´ě§€ 포함(_I)",
"symbols/IncludeSampleContent/description": "기본 사용 패턴을 보여주기 위해 ě플 íŽěť´ě§€ ë°Ź 스í€ěťĽěť„ ě¶”ę°€í• ě§€ 여부를 구성합ë‹ë‹¤.",
"symbols/Empty/description": "기본 사용 패턴을 보여주는 ě플 íŽěť´ě§€ ë°Ź 스í€ěťĽěť„ ěťëžµí• ě§€ 여부를 구성합ë‹ë‹¤.",
+ "symbols/auth/choices/None/description": "No authentication",
+ "symbols/auth/choices/Individual/description": "Individual authentication",
+ "symbols/auth/description": "The type of authentication to use",
+ "symbols/UseLocalDB/description": "Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual is specified.",
"symbols/AllInteractive/displayName": "_사이트 ě „ě˛´ě—서 ě „ě—ě 으로 인터랙티브 ë ŚëŤ”ë§ í™śě„±í™”",
"symbols/AllInteractive/description": "최ěěś„ ě준ě—서 ëŚ€í™”í• ë ŚëŤ”ë§ ëŞ¨ë“śëĄĽ ě ěš©í•ě—¬ ëŞ¨ë“ íŽěť´ě§€ëĄĽ 대화í•으로 ě„¤ě •í• ě§€ 여부를 구성합ë‹ë‹¤. ë§Śě•˝ falseěť´ë©´ íŽěť´ě§€ëŠ” 기본ě 으로 ě •ě 서버 ë ŚëŤ”ë§ěť„ 사용í•ë©° íŽěť´ě§€ëł„ ë는 구성 요소별로 대화í•으로 표시ë ě ěžěеë‹ë‹¤.",
"symbols/NoHttps/description": "HTTPS를 ëŚě§€ 여부입ë‹ë‹¤. ěť´ ěµě…은 Individual, IndividualB2C, SingleOrg ë는 MultiOrgę°€ --authě— ě‚¬ěš©ëě§€ 않는 경우ě—ë§Ś ě ěš©ë©ë‹ë‹¤.",
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.pl.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.pl.json
index 2ee8283fc2c4..f57d40906a60 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.pl.json
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.pl.json
@@ -4,6 +4,7 @@
"description": "Szablon projektu służący do tworzenia aplikacji internetowej platformy Blazor, która obsługuje renderowanie po stronie serwera i interakcyjność klienta. Ten szablon może być używany dla aplikacji internetowych z zaawansowanymi dynamicznymi interfejsami użytkownika.",
"symbols/Framework/description": "Platforma docelowa dla tego projektu.",
"symbols/Framework/choices/net8.0/description": "Docelowa platforma net8.0",
+ "symbols/UserSecretsId/description": "The ID to use for secrets (use with Individual auth).",
"symbols/skipRestore/description": "Jeśli ta opcja jest określona, pomija automatyczne przywracanie projektu podczas tworzenia.",
"symbols/ExcludeLaunchSettings/description": "Określa, czy wykluczyć plik launchSettings.json z wygenerowanego szablonu.",
"symbols/kestrelHttpPort/description": "Numer portu do użycia dla punktu końcowego HTTP w pliku launchSettings.json.",
@@ -29,6 +30,10 @@
"symbols/IncludeSampleContent/displayName": "_Dołącz przykładowe strony",
"symbols/IncludeSampleContent/description": "Konfiguruje, czy dodać przykładowe strony i style w celu zademonstrowania podstawowych wzorców użycia.",
"symbols/Empty/description": "Konfiguruje, czy pomijać przykładowe strony i style demonstrujące podstawowe wzorce użycia.",
+ "symbols/auth/choices/None/description": "No authentication",
+ "symbols/auth/choices/Individual/description": "Individual authentication",
+ "symbols/auth/description": "The type of authentication to use",
+ "symbols/UseLocalDB/description": "Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual is specified.",
"symbols/AllInteractive/displayName": "_Włącz globalnie renderowanie interakcyjne w całej witrynie",
"symbols/AllInteractive/description": "Konfiguruje, czy każda strona ma być interakcyjna, stosując interakcyjny tryb renderowania na najwyższym poziomie. W przypadku wartości false strony będą domyślnie używać statycznego renderowania serwera i mogą być oznaczone jako interakcyjne dla poszczególnych stron lub składników.",
"symbols/NoHttps/description": "Określa, czy wyłączyć protokół HTTPS. Ta opcja ma zastosowanie tylko wtedy, gdy dla uwierzytelniania --auth nie są używane elementy Individual, IndividualB2C, SingleOrg lub MultiOrg.",
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.pt-BR.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.pt-BR.json
index 0e631971af68..53defa558baa 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.pt-BR.json
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.pt-BR.json
@@ -4,6 +4,7 @@
"description": "Um modelo de projeto para criar um aplicativo Web Blazor que dá suporte à renderização do lado do servidor e à interatividade do cliente. Este modelo pode ser usado para aplicativos da Web com interfaces de usuário (UIs) dinâmicas avançadas.",
"symbols/Framework/description": "A estrutura de destino do projeto.",
"symbols/Framework/choices/net8.0/description": "net8.0 de destino",
+ "symbols/UserSecretsId/description": "The ID to use for secrets (use with Individual auth).",
"symbols/skipRestore/description": "Se especificado, ignora a restauração automática do projeto sendo criado.",
"symbols/ExcludeLaunchSettings/description": "Se deve excluir launchSettings.json do modelo gerado.",
"symbols/kestrelHttpPort/description": "NĂşmero da porta a ser usada para o ponto de extremidade HTTP em launchSettings.json.",
@@ -29,6 +30,10 @@
"symbols/IncludeSampleContent/displayName": "_Incluir páginas de amostra",
"symbols/IncludeSampleContent/description": "Configura se deseja adicionar páginas de amostra e estilo para demonstrar padrões de uso básicos.",
"symbols/Empty/description": "Configura a omissão de páginas de amostra e estilo que demonstram padrões básicos de uso.",
+ "symbols/auth/choices/None/description": "No authentication",
+ "symbols/auth/choices/Individual/description": "Individual authentication",
+ "symbols/auth/description": "The type of authentication to use",
+ "symbols/UseLocalDB/description": "Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual is specified.",
"symbols/AllInteractive/displayName": "_Habilitar renderização interativa globalmente no site",
"symbols/AllInteractive/description": "Configura se todas as páginas devem ser interativas aplicando um modo de renderização interativo no nĂvel superior. Se for falso, as páginas usarĂŁo a renderização do servidor estático por padrĂŁo e poderĂŁo ser marcadas como interativas por página ou por componente.",
"symbols/NoHttps/description": "Se o HTTPS deve ser desativado. Essa opção se aplica somente se Individual, IndividualB2C, SingleOrg ou MultiOrg não forem usados para --auth.",
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.ru.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.ru.json
index 4da19a19c1d5..8319cf95c2dd 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.ru.json
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.ru.json
@@ -4,6 +4,7 @@
"description": "Шаблон проекта для Ńоздания приложения Blazor, поддерживающего как отриŃĐľĐ˛ĐşŃ Đ˝Đ° Ńтороне Ńервера, так и интерактивные возможноŃти клиента. Đтот Ńаблон можно иŃпользовать для веб-приложений Ń ĐĽĐ˝ĐľĐłĐľŃ„Ńнкциональными динамичеŃкими пользовательŃкими интерфейŃами (UI).",
"symbols/Framework/description": "Целевая платформа для проекта.",
"symbols/Framework/choices/net8.0/description": "Целевая net8.0",
+ "symbols/UserSecretsId/description": "The ID to use for secrets (use with Individual auth).",
"symbols/skipRestore/description": "Đ•Ńли ŃŃтановлено, автоматичеŃкое воŃŃтановление проекта при Ńоздании пропŃŃкаетŃŃŹ.",
"symbols/ExcludeLaunchSettings/description": "СледŃет ли иŃключить launchSettings.json из Ńозданного Ńаблона.",
"symbols/kestrelHttpPort/description": "Номер порта, иŃпользŃемый для конечной точки HTTP в launchSettings.json.",
@@ -29,6 +30,10 @@
"symbols/IncludeSampleContent/displayName": "_Включить примеры Ńтраниц",
"symbols/IncludeSampleContent/description": "НаŃтраивает, ŃледŃет ли добавлять примеры Ńтраниц и Ńтили для демонŃтрации базовых Ńаблонов иŃпользования.",
"symbols/Empty/description": "НаŃтраивает, ŃледŃет ли пропŃŃкать примеры Ńтраниц и Ńтили, демонŃтрирŃющие базовые Ńаблоны иŃпользования.",
+ "symbols/auth/choices/None/description": "No authentication",
+ "symbols/auth/choices/Individual/description": "Individual authentication",
+ "symbols/auth/description": "The type of authentication to use",
+ "symbols/UseLocalDB/description": "Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual is specified.",
"symbols/AllInteractive/displayName": "_Включить интерактивнŃŃŽ отриŃĐľĐ˛ĐşŃ ĐżĐľ вŃĐµĐĽŃ ŃайтŃ",
"symbols/AllInteractive/description": "Определяет, делать ли каждŃŃŽ ŃŃ‚Ń€Đ°Đ˝Đ¸Ń†Ń Đ¸Đ˝Ń‚ĐµŃ€Đ°ĐşŃ‚Đ¸Đ˛Đ˝ĐľĐą, применяя интерактивный режим отриŃовки на верхнем Ńровне. Đ•Ńли ŃŃтановлено значение ЛОЖЬ, Ńтраницы по Ńмолчанию бŃĐ´ŃŃ‚ иŃпользовать ŃтатичеŃĐşŃŃŽ ŃервернŃŃŽ отриŃĐľĐ˛ĐşŃ Đ¸ могŃŃ‚ быть помечены как интерактивные для каждой Ńтраницы или для каждого компонента.",
"symbols/NoHttps/description": "СледŃет ли отключить HTTPS. Đтот параметр применяетŃŃŹ, только еŃли для --auth не иŃпользŃŃŽŃ‚ŃŃŹ Individual, IndividualB2C, SingleOrg или MultiOrg.",
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.tr.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.tr.json
index c48be5fb6517..3a754dfabdc6 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.tr.json
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.tr.json
@@ -4,6 +4,7 @@
"description": "Hem sunucu tarafı işlemeyi hem de istemci etkileşimini destekleyen bir Blazor web uygulaması oluşturmaya yönelik proje şablonu. Bu şablon, zengin dinamik kullanıcı arabirimlerine (UI) sahip web uygulamaları için kullanılabilir.",
"symbols/Framework/description": "Projenin hedef çerçevesi.",
"symbols/Framework/choices/net8.0/description": "Hedef net8.0",
+ "symbols/UserSecretsId/description": "The ID to use for secrets (use with Individual auth).",
"symbols/skipRestore/description": "Belirtilirse, oluşturma sırasında projenin otomatik geri yüklenmesini atlar.",
"symbols/ExcludeLaunchSettings/description": "launchSettings.json öğesinin oluşturulan şablondan dışlanıp dışlanmayacağı.",
"symbols/kestrelHttpPort/description": "launchSettings.json içinde HTTP uç noktası için kullanılacak bağlantı noktası numarası.",
@@ -29,6 +30,10 @@
"symbols/IncludeSampleContent/displayName": "Ă–rnek _sayfalar ekle",
"symbols/IncludeSampleContent/description": "Temel kullanım düzenlerini göstermek için örnek sayfaların ve stil oluşturma özelliklerinin eklenip eklenmeyeceğini yapılandırır.",
"symbols/Empty/description": "Temel kullanım düzenlerini gösteren örnek sayfaların ve stil oluşturma özelliklerinin atlanıp atlanmayacağını yapılandırır.",
+ "symbols/auth/choices/None/description": "No authentication",
+ "symbols/auth/choices/Individual/description": "Individual authentication",
+ "symbols/auth/description": "The type of authentication to use",
+ "symbols/UseLocalDB/description": "Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual is specified.",
"symbols/AllInteractive/displayName": "_Site genelinde etkileĹźimli iĹźlemeyi genel olarak etkinleĹźtirin",
"symbols/AllInteractive/description": "En üst düzeyde etkileşimli bir işleme modu uygulayarak her sayfanın etkileşimli olup olmayacağını yapılandırır. False ise sayfalar varsayılan olarak statik sunucu işleme kullanır ve sayfa başına veya bileşen başına temelinde etkileşimli olarak işaretlenebilir.",
"symbols/NoHttps/description": "HTTPS'nin kapatılıp kapatılmayacağı. Bu seçenek yalnızca Bireysel, IndividualB2C, SingleOrg veya MultiOrg -- auth için kullanılmazsa geçerlidir.",
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.zh-Hans.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.zh-Hans.json
index 2ae798e0ebdd..05fd5dde01ff 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.zh-Hans.json
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.zh-Hans.json
@@ -4,6 +4,7 @@
"description": "用于ĺ›ĺ»şć”ŻćŚćśŤĺŠˇĺ™¨ç«Żĺ‘现和客ć·ç«Żäş¤äş’çš„ Blazor Web 应用的项目模板。ć¤ć¨ˇćťżĺŹŻç”¨äşŽĺ…·ćś‰ä¸°ĺŻŚĺŠ¨ć€ç”¨ć·ç•Śéť˘ (UI) çš„ Web 应用。",
"symbols/Framework/description": "éˇąç›®çš„ç›®ć ‡ćˇ†ćž¶ă€‚",
"symbols/Framework/choices/net8.0/description": "ç›®ć ‡ net8.0",
+ "symbols/UserSecretsId/description": "The ID to use for secrets (use with Individual auth).",
"symbols/skipRestore/description": "如果指定,ĺ™ĺś¨ĺ›ĺ»şć—¶č·łčż‡éˇąç›®çš„自动čżĺŽźă€‚",
"symbols/ExcludeLaunchSettings/description": "ćŻĺ¦ä»Žç”źć的模板ä¸ćޒ除 launchSettings.json。",
"symbols/kestrelHttpPort/description": "č¦ç”¨äşŽ launchSettings.json ä¸ HTTP ç»ç»“点的端口号。",
@@ -29,6 +30,10 @@
"symbols/IncludeSampleContent/displayName": "包ĺ«ç¤şäľ‹éˇµ(_I)",
"symbols/IncludeSampleContent/description": "é…Ťç˝®ćŻĺ¦ć·»ĺŠ ç¤şäľ‹éˇµĺ’Ść ·ĺĽŹä»ĄćĽ”ç¤şĺźşćś¬ä˝żç”¨ć¨ˇĺĽŹă€‚",
"symbols/Empty/description": "é…Ťç˝®ćŻĺ¦ĺż˝ç•ĄćĽ”ç¤şĺźşćś¬ä˝żç”¨ć¨ˇĺĽŹçš„ç¤şäľ‹éˇµĺ’Ść ·ĺĽŹă€‚",
+ "symbols/auth/choices/None/description": "No authentication",
+ "symbols/auth/choices/Individual/description": "Individual authentication",
+ "symbols/auth/description": "The type of authentication to use",
+ "symbols/UseLocalDB/description": "Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual is specified.",
"symbols/AllInteractive/displayName": "_在整个网站全局ĺŻç”¨äş¤äş’式ĺ‘现",
"symbols/AllInteractive/description": "é…Ťç˝®ćŻĺ¦é€ščż‡ĺś¨éˇ¶ĺ±‚应用交互式ĺ‘现模式来使每个页面é˝äş¤äş’。如果为 false,ĺ™é»č®¤ć…况下,页面将使用静ć€ćśŤĺŠˇĺ™¨ĺ‘现,并且可以按每页ć–ćŻŹç»„ä»¶ć ‡č®°ä¸şäş¤äş’ă€‚",
"symbols/NoHttps/description": "ćŻĺ¦ç¦ç”¨ HTTPS。仅当 Individuală€IndividualB2Că€SingleOrg ć– MultiOrg 不用于 --auth 时,ć¤é€‰éˇąć‰Ťé€‚用。",
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.zh-Hant.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.zh-Hant.json
index cb0dcf1feec7..a80cdf669b9a 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.zh-Hant.json
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/localize/templatestrings.zh-Hant.json
@@ -4,6 +4,7 @@
"description": "用於建立ĺŚć™‚支援伺服器端轉čŻĺ’Śç”¨ć¶ç«Żäş’ĺ‹•çš„ Blazor Web 應用程式的ĺ°ćˇçŻ„ćś¬ă€‚ć¤çŻ„ćś¬ĺŹŻç”¨ć–Ľĺ…·ćś‰č±ĺŻŚĺ‹•ć…‹ä˝żç”¨č€…ä»‹éť˘ (UI) çš„ Web 應用程式。",
"symbols/Framework/description": "ĺ°ćˇçš„目標 Framework。",
"symbols/Framework/choices/net8.0/description": "目標 net8.0",
+ "symbols/UserSecretsId/description": "The ID to use for secrets (use with Individual auth).",
"symbols/skipRestore/description": "若指定,ćśĺś¨ĺ»şç«‹ć™‚č·łéŽĺ°ćˇçš„自動還原。",
"symbols/ExcludeLaunchSettings/description": "ćŻĺ¦č¦ĺľžç”˘ç”źçš„範本排除 launchSettings.json。",
"symbols/kestrelHttpPort/description": "launchSettings.json ä¸ HTTP 端點č¦ä˝żç”¨çš„é€ŁćŽĄĺź č™źç˘Ľă€‚",
@@ -29,6 +30,10 @@
"symbols/IncludeSampleContent/displayName": "包ĺ«çŻ„äľ‹é 面(_I)",
"symbols/IncludeSampleContent/description": "č¨ĺ®šćŻĺ¦č¦ć–°ĺ˘žçŻ„äľ‹é 面和樣式,以示範基本使用模式。",
"symbols/Empty/description": "č¨ĺ®šćŻĺ¦č¦çśç•ĄçŻ„äľ‹é 面和樣式,其示範基本使用模式。",
+ "symbols/auth/choices/None/description": "No authentication",
+ "symbols/auth/choices/Individual/description": "Individual authentication",
+ "symbols/auth/description": "The type of authentication to use",
+ "symbols/UseLocalDB/description": "Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual is specified.",
"symbols/AllInteractive/displayName": "_啟用整個網站的全域互動式轉čŻ",
"symbols/AllInteractive/description": "č¨ĺ®šćŻĺ¦č¦ĺś¨ćś€ä¸Šĺ±¤ĺĄ—用互動式轉čŻć¨ˇĺĽŹďĽŚč®“每個é 面é˝ć為互動式。如果為 false,則é 面é č¨ćśä˝żç”¨éťść…‹äĽşćśŤĺ™¨č˝‰čŻďĽŚč€Śä¸”可以以每é ć–每一ĺ…件為基礎標示為互動式。",
"symbols/NoHttps/description": "ćŻĺ¦č¦é—śé–‰ HTTPS。只有當 Individuală€IndividualB2Că€SingleOrg ć– MultiOrg 未用於 --auth 時,才é©ç”¨ć¤é¸é …。",
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/template.json
index 525989d38e39..1e36b02d8939 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/template.json
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/template.json
@@ -14,7 +14,8 @@
"guids": [
"4C26868E-5E7C-458D-82E3-040509D0C71F",
"5990939C-7E7B-4CFA-86FF-44CA5756498A",
- "650B3CE7-2E93-4CC4-9F46-466686815EAA"
+ "650B3CE7-2E93-4CC4-9F46-466686815EAA",
+ "53bc9b9d-9d6a-45d4-8429-2a2761773502"
],
"identity": "Microsoft.Web.Blazor.CSharp.9.0",
"thirdPartyNotices": "https://aka.ms/aspnetcore/9.0-third-party-notices",
@@ -108,6 +109,86 @@
"BlazorWeb-CSharp.Client/Pages/**",
"BlazorWeb-CSharp.Client/wwwroot/**"
]
+ },
+ {
+ "condition": "(!IndividualLocalAuth)",
+ "exclude": [
+ "BlazorWeb-CSharp/Components/Identity/**",
+ "BlazorWeb-CSharp/Components/Layout/ManageLayout.razor",
+ "BlazorWeb-CSharp/Components/Layout/ManageNavMenu.razor",
+ "BlazorWeb-CSharp/Components/Pages/Account/**",
+ "BlazorWeb-CSharp/Data/**",
+ "BlazorWeb-CSharp/Identity/**",
+ "BlazorWeb-CSharp.Client/PersistentAuthenticationStateProvider.cs",
+ "BlazorWeb-CSharp.Client/UserInfo.cs",
+ "BlazorWeb-CSharp.Client/Pages/Auth.razor"
+ ]
+ },
+ {
+ "condition": "(!(IndividualLocalAuth && !UseLocalDB))",
+ "exclude": [
+ "BlazorWeb-CSharp/app.db"
+ ]
+ },
+ {
+ "condition": "(!(IndividualLocalAuth && !UseWebAssembly))",
+ "exclude": [
+ "BlazorWeb-CSharp/Components/Pages/Auth.razor"
+ ]
+ },
+ {
+ "condition": "(!(IndividualLocalAuth && UseServer && UseWebAssembly))",
+ "exclude": [
+ "BlazorWeb-CSharp/Identity/PersistingRevalidatingAuthenticationStateProvider.cs"
+ ]
+ },
+ {
+ "condition": "(!(IndividualLocalAuth && UseServer && !UseWebAssembly))",
+ "exclude": [
+ "BlazorWeb-CSharp/Identity/IdentityRevalidatingAuthenticationStateProvider.cs"
+ ]
+ },
+ {
+ "condition": "(!(IndividualLocalAuth && !UseServer && UseWebAssembly))",
+ "exclude": [
+ "BlazorWeb-CSharp/Identity/PersistingServerAuthenticationStateProvider.cs"
+ ]
+ },
+ {
+ "condition": "(IndividualLocalAuth && UseLocalDB && UseWebAssembly)",
+ "rename": {
+ "BlazorWeb-CSharp/Data/SqlServer/": "BlazorWeb-CSharp/Data/Migrations/"
+ },
+ "exclude": [
+ "BlazorWeb-CSharp/Data/SqlLite/**"
+ ]
+ },
+ {
+ "condition": "(IndividualLocalAuth && UseLocalDB && !UseWebAssembly)",
+ "rename": {
+ "BlazorWeb-CSharp/Data/SqlServer/": "Data/Migrations/"
+ },
+ "exclude": [
+ "BlazorWeb-CSharp/Data/SqlLite/**"
+ ]
+ },
+ {
+ "condition": "(IndividualLocalAuth && !UseLocalDB && UseWebAssembly)",
+ "rename": {
+ "BlazorWeb-CSharp/Data/SqlLite/": "BlazorWeb-CSharp/Data/Migrations/"
+ },
+ "exclude": [
+ "BlazorWeb-CSharp/Data/SqlServer/**"
+ ]
+ },
+ {
+ "condition": "(IndividualLocalAuth && !UseLocalDB && !UseWebAssembly)",
+ "rename": {
+ "BlazorWeb-CSharp/Data/SqlLite/": "Data/Migrations/"
+ },
+ "exclude": [
+ "BlazorWeb-CSharp/Data/SqlServer/**"
+ ]
}
]
}
@@ -130,6 +211,13 @@
"type": "bind",
"binding": "HostIdentifier"
},
+ "UserSecretsId": {
+ "type": "parameter",
+ "datatype": "string",
+ "replaces": "aspnet-BlazorWeb-CSharp-53bc9b9d-9d6a-45d4-8429-2a2761773502",
+ "defaultValue": "aspnet-BlazorWeb-CSharp-53bc9b9d-9d6a-45d4-8429-2a2761773502",
+ "description": "The ID to use for secrets (use with Individual auth)."
+ },
"skipRestore": {
"type": "parameter",
"datatype": "bool",
@@ -167,7 +255,7 @@
"kestrelHttpsPort": {
"type": "parameter",
"datatype": "integer",
- "description": "Port number to use for the HTTPS endpoint in launchSettings.json. This option is only applicable when the parameter no-https is not used (no-https will be ignored if either IndividualAuth or OrganizationalAuth is used)."
+ "description": "Port number to use for the HTTPS endpoint in launchSettings.json. This option is only applicable when the parameter no-https is not used (no-https will be ignored if Individual auth is used)."
},
"kestrelHttpsPortGenerated": {
"type": "generated",
@@ -207,7 +295,7 @@
"iisHttpsPort": {
"type": "parameter",
"datatype": "integer",
- "description": "Port number to use for the IIS Express HTTPS endpoint in launchSettings.json. This option is only applicable when the parameter no-https is not used (no-https will be ignored if either IndividualAuth or OrganizationalAuth is used)."
+ "description": "Port number to use for the IIS Express HTTPS endpoint in launchSettings.json. This option is only applicable when the parameter no-https is not used (no-https will be ignored if Individual auth is used)."
},
"iisHttpsPortGenerated": {
"type": "generated",
@@ -261,7 +349,7 @@
"defaultValue": "InteractivePerPage",
"displayName": "_Interactivity location",
"description": "Chooses which components will have interactive rendering enabled",
- "isEnabled": "(InteractivityPlatform != \"None\")",
+ "isEnabled": "(InteractivityPlatform != \"None\" && auth == \"None\")",
"choices": [
{
"choice": "InteractivePerPage",
@@ -296,6 +384,28 @@
"defaultValue": "false",
"description": "Configures whether to omit sample pages and styling that demonstrate basic usage patterns."
},
+ "auth": {
+ "type": "parameter",
+ "datatype": "choice",
+ "choices": [
+ {
+ "choice": "None",
+ "description": "No authentication"
+ },
+ {
+ "choice": "Individual",
+ "description": "Individual authentication"
+ }
+ ],
+ "defaultValue": "None",
+ "description": "The type of authentication to use"
+ },
+ "UseLocalDB": {
+ "type": "parameter",
+ "datatype": "bool",
+ "defaultValue": "false",
+ "description": "Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual is specified."
+ },
"SampleContent": {
"type": "computed",
"value": "(((IncludeSampleContent && (HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\"))) || ((!Empty && (HostIdentifier == \"dotnetcli\" || HostIdentifier == \"dotnetcli-preview\"))))"
@@ -303,7 +413,7 @@
"AllInteractive": {
"type": "parameter",
"datatype": "bool",
- "isEnabled": "(InteractivityPlatform != \"None\")",
+ "isEnabled": "(InteractivityPlatform != \"None\" && auth == \"None\")",
"defaultValue": "false",
"displayName": "_Enable interactive rendering globally throughout the site",
"description": "Configures whether to make every page interactive by applying an interactive render mode at the top level. If false, pages will use static server rendering by default, and can be marked interactive on a per-page or per-component basis."
@@ -312,9 +422,13 @@
"type": "computed",
"value": "(InteractivityLocation == \"InteractiveGlobal\" || AllInteractive)"
},
+ "IndividualLocalAuth": {
+ "type": "computed",
+ "value": "(auth == \"Individual\")"
+ },
"RequiresHttps": {
"type": "computed",
- "value": "(OrganizationalAuth || IndividualAuth)"
+ "value": "(OrganizationalAuth || IndividualLocalAuth)"
},
"HasHttpProfile": {
"type": "computed",
@@ -328,7 +442,7 @@
"type": "parameter",
"datatype": "bool",
"defaultValue": "false",
- "description": "Whether to turn off HTTPS. This option only applies if Individual, IndividualB2C, SingleOrg, or MultiOrg aren't used for --auth."
+ "description": "Whether to turn off HTTPS. This option only applies if Individual isn't used for --auth."
},
"copyrightYear": {
"type": "generated",
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp.Client/Pages/Auth.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp.Client/Pages/Auth.razor
new file mode 100644
index 000000000000..ebeeeccea168
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp.Client/Pages/Auth.razor
@@ -0,0 +1,18 @@
+@page "/auth"
+
+@using Microsoft.AspNetCore.Authorization
+
+@attribute [Authorize]
+@*#if (UseServer && !InteractiveAtRoot)
+@attribute [RenderModeInteractiveAuto]
+##elseif (!InteractiveAtRoot)
+@attribute [RenderModeInteractiveWebAssembly]
+##endif*@
+
+Auth
+
+
You are authenticated
+
+
+ Hello @context.User.Identity?.Name!
+
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp.Client/PersistentAuthenticationStateProvider.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp.Client/PersistentAuthenticationStateProvider.cs
new file mode 100644
index 000000000000..4f8e698ea75d
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp.Client/PersistentAuthenticationStateProvider.cs
@@ -0,0 +1,29 @@
+using System.Security.Claims;
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Authorization;
+
+namespace BlazorWeb_CSharp.Client;
+
+public class PersistentAuthenticationStateProvider(PersistentComponentState persistentState) : AuthenticationStateProvider
+{
+ private static readonly Task _unauthenticatedTask =
+ Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())));
+
+ public override Task GetAuthenticationStateAsync()
+ {
+ if (!persistentState.TryTakeFromJson(nameof(UserInfo), out var userInfo) || userInfo is null)
+ {
+ return _unauthenticatedTask;
+ }
+
+ Claim[] claims = [
+ new Claim(ClaimTypes.NameIdentifier, userInfo.UserId),
+ new Claim(ClaimTypes.Name, userInfo.Email),
+ new Claim(ClaimTypes.Email, userInfo.Email) ];
+
+ return Task.FromResult(
+ new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims,
+ authenticationType: nameof(PersistentAuthenticationStateProvider)))));
+ }
+}
+
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp.Client/Program.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp.Client/Program.cs
index 519269f21bb8..600e37d36537 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp.Client/Program.cs
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp.Client/Program.cs
@@ -1,5 +1,15 @@
+#if (IndividualLocalAuth)
+using BlazorWeb_CSharp.Client;
+using Microsoft.AspNetCore.Components.Authorization;
+#endif
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
+#if (IndividualLocalAuth)
+builder.Services.AddAuthorizationCore();
+builder.Services.AddCascadingAuthenticationState();
+builder.Services.AddSingleton();
+
+#endif
await builder.Build().RunAsync();
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp.Client/UserInfo.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp.Client/UserInfo.cs
new file mode 100644
index 000000000000..236bbaa720da
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp.Client/UserInfo.cs
@@ -0,0 +1,7 @@
+namespace BlazorWeb_CSharp.Client;
+
+public class UserInfo
+{
+ public required string UserId { get; set; }
+ public required string Email { get; set; }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp.Client/_Imports.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp.Client/_Imports.razor
index 5268e26fd6aa..cd618a113368 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp.Client/_Imports.razor
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp.Client/_Imports.razor
@@ -1,5 +1,8 @@
@using System.Net.Http
@using System.Net.Http.Json
+@*#if (IndividualLocalAuth)
+@using Microsoft.AspNetCore.Components.Authorization
+##endif*@
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/ExternalLoginPicker.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/ExternalLoginPicker.razor
new file mode 100644
index 000000000000..5c33681f7021
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/ExternalLoginPicker.razor
@@ -0,0 +1,47 @@
+@using Microsoft.AspNetCore.Authentication
+@using Microsoft.AspNetCore.Identity
+@using BlazorWeb_CSharp.Components.Pages.Account
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject SignInManager SignInManager
+@inject IdentityRedirectManager RedirectManager
+
+@if ((_externalLogins?.Count ?? 0) == 0)
+{
+
+}
+else
+{
+
+}
+
+@code {
+ private IList? _externalLogins;
+
+ [SupplyParameterFromQuery]
+ private string ReturnUrl { get; set; } = default!;
+
+ protected override async Task OnInitializedAsync()
+ {
+ ReturnUrl ??= "/";
+
+ _externalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToList();
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/LogoutForm.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/LogoutForm.razor
new file mode 100644
index 000000000000..a08c78fc4cdc
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/LogoutForm.razor
@@ -0,0 +1,34 @@
+@using Microsoft.AspNetCore.Identity
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject SignInManager SignInManager
+@inject NavigationManager NavigationManager
+@inject IdentityRedirectManager RedirectManager
+
+
+
+@code {
+ [Parameter(CaptureUnmatchedValues = true)]
+ public IDictionary? AdditionalAttributes { get; set; }
+
+ [SupplyParameterFromForm]
+ private string? ReturnUrl { get; set; }
+
+ [CascadingParameter]
+ private HttpContext HttpContext { get; set; } = default!;
+
+ private async Task OnSubmitAsync()
+ {
+ var user = HttpContext.User;
+
+ if (SignInManager.IsSignedIn(user))
+ {
+ await SignInManager.SignOutAsync();
+ RedirectManager.RedirectTo(ReturnUrl ?? "/");
+ }
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/ShowRecoveryCodes.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/ShowRecoveryCodes.razor
new file mode 100644
index 000000000000..cebec61aef19
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/ShowRecoveryCodes.razor
@@ -0,0 +1,32 @@
+
+Recovery codes
+
+
+ Put these codes in a safe place.
+
+
+ If you lose your device and don't have the recovery codes you will lose access to your account.
+
+
+
+
+ @for (var row = 0; row < RecoveryCodes.Length; row += 2)
+ {
+ @RecoveryCodes[row]
+
+
+
+ @RecoveryCodes[row + 1]
+
+
+ }
+
+
+
+@code {
+ [Parameter]
+ public string[] RecoveryCodes { get; set; } = default!;
+
+ [Parameter]
+ public string StatusMessage { get; set; } = default!;
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/StatusMessage.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/StatusMessage.razor
new file mode 100644
index 000000000000..43bcdc0478b6
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Identity/StatusMessage.razor
@@ -0,0 +1,29 @@
+@using BlazorWeb_CSharp.Identity
+
+@{
+ var message = Message ?? MessageFromCookie;
+
+ if (MessageFromCookie is not null)
+ {
+ HttpContext.Response.Cookies.Delete(IdentityRedirectManager.StatusCookieName);
+ }
+}
+
+@if (!string.IsNullOrEmpty(message))
+{
+ var statusMessageClass = message.StartsWith("Error") ? "danger" : "success";
+
+
+ @message
+
+}
+
+@code {
+ [Parameter]
+ public string? Message { get; set; }
+
+ [CascadingParameter]
+ private HttpContext HttpContext { get; set; } = default!;
+
+ private string? MessageFromCookie => HttpContext.Request.Cookies[IdentityRedirectManager.StatusCookieName];
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/ManageLayout.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/ManageLayout.razor
new file mode 100644
index 000000000000..e4a7871bbc75
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/ManageLayout.razor
@@ -0,0 +1,17 @@
+@inherits LayoutComponentBase
+@layout MainLayout
+
+Manage your account
+
+
+
Change your account settings
+
+
+
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/ManageNavMenu.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/ManageNavMenu.razor
new file mode 100644
index 000000000000..8ffd7cd0a41e
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/ManageNavMenu.razor
@@ -0,0 +1,37 @@
+@using Microsoft.AspNetCore.Identity;
+@using BlazorWeb_CSharp.Data;
+
+@inject SignInManager SignInManager;
+
+
+
+ Profile
+
+
+ Email
+
+
+ Password
+
+ @if (_hasExternalLogins)
+ {
+
+ External logins
+
+ }
+
+ Two-factor authentication
+
+
+ Personal data
+
+
+
+@code {
+ private bool _hasExternalLogins;
+
+ protected override async Task OnInitializedAsync()
+ {
+ _hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any();
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/NavMenu.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/NavMenu.razor
index 5b141a54826b..5b38b21d63a7 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/NavMenu.razor
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/NavMenu.razor
@@ -1,4 +1,8 @@
-
+@*#if (IndividualLocalAuth)
+@using BlazorWeb_CSharp.Components.Identity
+
+##endif*@
+
@@ -27,5 +31,41 @@
Weather
+ @*#if (IndividualLocalAuth)
+
+
+
+ Auth Required
+
+
+
+
+
+
+
+ @context.User.Identity?.Name
+
+
+
+
+
+ Logout
+
+
+
+
+
+
+ Register
+
+
+
+
+ Login
+
+
+
+
+ ##endif*@
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/NavMenu.razor.css b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/NavMenu.razor.css
index 95fcc36e0baa..14bddcebe4cf 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/NavMenu.razor.css
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Layout/NavMenu.razor.css
@@ -46,6 +46,28 @@
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
}
+/*#if (IndividualLocalAuth)*/
+.bi-lock {
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath d='M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2zM5 8h6a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z'/%3E%3C/svg%3E");
+}
+
+.bi-person {
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-person' viewBox='0 0 16 16'%3E%3Cpath d='M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6Zm2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0Zm4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4Zm-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664h10Z'/%3E%3C/svg%3E");
+}
+
+.bi-person-badge {
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-person-badge' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 2a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1h-3zM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0z'/%3E%3Cpath d='M4.5 0A2.5 2.5 0 0 0 2 2.5V14a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2.5A2.5 2.5 0 0 0 11.5 0h-7zM3 2.5A1.5 1.5 0 0 1 4.5 1h7A1.5 1.5 0 0 1 13 2.5v10.795a4.2 4.2 0 0 0-.776-.492C11.392 12.387 10.063 12 8 12s-3.392.387-4.224.803a4.2 4.2 0 0 0-.776.492V2.5z'/%3E%3C/svg%3E");
+}
+
+.bi-person-fill {
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-person-fill' viewBox='0 0 16 16'%3E%3Cpath d='M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3Zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z'/%3E%3C/svg%3E");
+}
+
+.bi-arrow-bar-left {
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-arrow-bar-left' viewBox='0 0 16 16'%3E%3Cpath d='M12.5 15a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 1 0v13a.5.5 0 0 1-.5.5ZM10 8a.5.5 0 0 1-.5.5H3.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L3.707 7.5H9.5a.5.5 0 0 1 .5.5Z'/%3E%3C/svg%3E");
+}
+
+/*#endif*/
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ConfirmEmail.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ConfirmEmail.razor
new file mode 100644
index 000000000000..992c6c59694f
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ConfirmEmail.razor
@@ -0,0 +1,49 @@
+@page "/Account/ConfirmEmail"
+
+@using System.Text
+@using Microsoft.AspNetCore.Identity
+@using Microsoft.AspNetCore.WebUtilities
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject UserManager UserManager
+@inject IdentityRedirectManager RedirectManager
+
+Confirm email
+
+Confirm email
+
+
+@code {
+ string? statusMessage;
+
+ [SupplyParameterFromQuery]
+ public string? UserId { get; set; }
+
+ [SupplyParameterFromQuery]
+ public string? Code { get; set; }
+
+ protected override async Task OnInitializedAsync()
+ {
+ if (UserId == null || Code == null)
+ {
+ RedirectManager.RedirectTo("/");
+ }
+ else
+ {
+ var user = await UserManager.FindByIdAsync(UserId);
+ if (user == null)
+ {
+ // Need a way to trigger a 404 from Blazor: https://github.com/dotnet/aspnetcore/issues/45654
+ statusMessage = $"Error loading user with ID {UserId}";
+ }
+ else
+ {
+
+ var code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(Code));
+ var result = await UserManager.ConfirmEmailAsync(user, code);
+ statusMessage = result.Succeeded ? "Thank you for confirming your email." : "Error confirming your email.";
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ConfirmEmailChange.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ConfirmEmailChange.razor
new file mode 100644
index 000000000000..510fb2d0e93f
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ConfirmEmailChange.razor
@@ -0,0 +1,64 @@
+@page "/Account/ConfirmEmailChange"
+
+@using System.Text
+@using Microsoft.AspNetCore.Identity
+@using Microsoft.AspNetCore.WebUtilities
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject UserManager UserManager
+@inject SignInManager SignInManager
+@inject UserAccessor UserAccessor
+@inject IdentityRedirectManager RedirectManager
+
+Confirm email change
+
+Confirm email change
+
+
+
+@code {
+ private string? _message;
+ private ApplicationUser _user = default!;
+
+ [SupplyParameterFromQuery]
+ private string? UserId { get; set; }
+
+ [SupplyParameterFromQuery]
+ private string? Email { get; set; }
+
+ [SupplyParameterFromQuery]
+ private string? Code { get; set; }
+
+ protected override async Task OnInitializedAsync()
+ {
+ if (UserId is null || Email is null || Code is null)
+ {
+ RedirectManager.RedirectToWithStatus(
+ "/Account/Login", "Error: Invalid email change confirmation link.");
+ return;
+ }
+
+ _user = await UserAccessor.GetRequiredUserAsync();
+
+ var code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(Code));
+ var result = await UserManager.ChangeEmailAsync(_user, Email, code);
+ if (!result.Succeeded)
+ {
+ _message = "Error changing email.";
+ return;
+ }
+
+ // In our UI email and user name are one and the same, so when we update the email
+ // we need to update the user name.
+ var setUserNameResult = await UserManager.SetUserNameAsync(_user, Email);
+ if (!setUserNameResult.Succeeded)
+ {
+ _message = "Error changing user name.";
+ return;
+ }
+
+ await SignInManager.RefreshSignInAsync(_user);
+ _message = "Thank you for confirming your email change.";
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ExternalLogin.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ExternalLogin.razor
new file mode 100644
index 000000000000..51bfa81fc116
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ExternalLogin.razor
@@ -0,0 +1,213 @@
+@page "/Account/ExternalLogin"
+
+@using System.ComponentModel.DataAnnotations
+@using System.Security.Claims
+@using System.Text
+@using System.Text.Encodings.Web
+@using Microsoft.AspNetCore.Identity
+@using Microsoft.AspNetCore.Identity.UI.Services
+@using Microsoft.AspNetCore.WebUtilities
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject SignInManager SignInManager
+@inject UserManager UserManager
+@inject IUserStore UserStore
+@inject IEmailSender EmailSender
+@inject NavigationManager NavigationManager
+@inject IdentityRedirectManager RedirectManager
+@inject ILogger Logger
+
+@{
+ var providerDisplayName = _externalLoginInfo.ProviderDisplayName;
+}
+
+Register
+
+
+Register
+Associate your @providerDisplayName account.
+
+
+
+ You've successfully authenticated with @providerDisplayName .
+ Please enter an email address for this site below and click the Register button to finish
+ logging in.
+
+
+
+
+
+
+
+
+
+ Email
+
+
+ Register
+
+
+
+
+@code {
+ public const string LoginCallbackAction = "LoginCallback";
+
+ private string? _message;
+ private ExternalLoginInfo _externalLoginInfo = default!;
+ private IUserEmailStore _emailStore = default!;
+
+ [SupplyParameterFromQuery]
+ private string? RemoteError { get; set; }
+
+ [CascadingParameter]
+ public HttpContext HttpContext { get; set; } = default!;
+
+ [SupplyParameterFromForm]
+ private InputModel Input { get; set; } = default!;
+
+ [SupplyParameterFromQuery]
+ private string ReturnUrl { get; set; } = default!;
+
+ [SupplyParameterFromQuery]
+ private string? Action { get; set; } = default!;
+
+ protected override async Task OnInitializedAsync()
+ {
+ Input ??= new();
+ ReturnUrl ??= "/";
+
+ if (RemoteError is not null)
+ {
+ RedirectManager.RedirectToWithStatus("/Account/Login", "Error from external provider: " + RemoteError);
+ return;
+ }
+
+ var externalLoginInfo = await SignInManager.GetExternalLoginInfoAsync();
+ if (externalLoginInfo is null)
+ {
+ RedirectManager.RedirectToWithStatus("/Account/Login", "Error loading external login information.");
+ return;
+ }
+
+ _externalLoginInfo = externalLoginInfo;
+ _emailStore = GetEmailStore();
+
+ if (HttpMethods.IsGet(HttpContext.Request.Method))
+ {
+ if (Action == LoginCallbackAction)
+ {
+ await OnLoginCallbackAsync();
+ return;
+ }
+
+ // We should only reach this page via the login callback, so redirect back to
+ // the login page if we get here some other way.
+ RedirectManager.RedirectTo("/Account/Login");
+ return;
+ }
+ }
+
+ private async Task OnLoginCallbackAsync()
+ {
+ // Sign in the user with this external login provider if the user already has a login.
+ var result = await SignInManager.ExternalLoginSignInAsync(
+ _externalLoginInfo.LoginProvider,
+ _externalLoginInfo.ProviderKey,
+ isPersistent: false,
+ bypassTwoFactor: true);
+ if (result.Succeeded)
+ {
+ Logger.LogInformation(
+ "{Name} logged in with {LoginProvider} provider.",
+ _externalLoginInfo.Principal.Identity?.Name,
+ _externalLoginInfo.LoginProvider);
+ RedirectManager.RedirectTo(ReturnUrl);
+ return;
+ }
+
+ if (result.IsLockedOut)
+ {
+ RedirectManager.RedirectTo("/Account/Lockout");
+ return;
+ }
+
+ // If the user does not have an account, then ask the user to create an account.
+ if (_externalLoginInfo.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
+ {
+ Input.Email = _externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Email);
+ }
+ }
+
+ private async Task OnValidSubmitAsync()
+ {
+ var user = CreateUser();
+
+ await UserStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
+ await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
+
+ var result = await UserManager.CreateAsync(user);
+ if (result.Succeeded)
+ {
+ result = await UserManager.AddLoginAsync(user, _externalLoginInfo);
+ if (result.Succeeded)
+ {
+ Logger.LogInformation("User created an account using {Name} provider.", _externalLoginInfo.LoginProvider);
+
+ var userId = await UserManager.GetUserIdAsync(user);
+ var code = await UserManager.GenerateEmailConfirmationTokenAsync(user);
+ code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
+
+ var callbackUrl = NavigationManager.GetUriWithQueryParameters(
+ $"{NavigationManager.BaseUri}Account/ConfirmEmail",
+ new Dictionary { { "userId", userId }, { "code", code } });
+ await EmailSender.SendEmailAsync(Input.Email!, "Confirm your email",
+ $"Please confirm your account by clicking here .");
+
+ // If account confirmation is required, we need to show the link if we don't have a real email sender
+ if (UserManager.Options.SignIn.RequireConfirmedAccount)
+ {
+ RedirectManager.RedirectTo("/Account/RegisterConfirmation", new() { ["Email"] = Input.Email });
+ return;
+ }
+
+ await SignInManager.SignInAsync(user, isPersistent: false, _externalLoginInfo.LoginProvider);
+ RedirectManager.RedirectTo(ReturnUrl);
+ return;
+ }
+ }
+ else
+ {
+ _message = $"Error: {string.Join(",", result.Errors.Select(error => error.Description))}";
+ }
+ }
+
+ private ApplicationUser CreateUser()
+ {
+ try
+ {
+ return Activator.CreateInstance();
+ }
+ catch
+ {
+ throw new InvalidOperationException($"Can't create an instance of '{nameof(ApplicationUser)}'. " +
+ $"Ensure that '{nameof(ApplicationUser)}' is not an abstract class and has a parameterless constructor");
+ }
+ }
+
+ private IUserEmailStore GetEmailStore()
+ {
+ if (!UserManager.SupportsUserEmail)
+ {
+ throw new NotSupportedException("The default UI requires a user store with email support.");
+ }
+ return (IUserEmailStore)UserStore;
+ }
+
+ private sealed class InputModel
+ {
+ [Required]
+ [EmailAddress]
+ public string? Email { get; set; }
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ForgotPassword.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ForgotPassword.razor
new file mode 100644
index 000000000000..605a676daad5
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ForgotPassword.razor
@@ -0,0 +1,74 @@
+@page "/Account/ForgotPassword"
+
+@using System.ComponentModel.DataAnnotations
+@using System.Text
+@using System.Text.Encodings.Web
+@using Microsoft.AspNetCore.Identity
+@using Microsoft.AspNetCore.Identity.UI.Services
+@using Microsoft.AspNetCore.WebUtilities
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject NavigationManager NavigationManager
+@inject IdentityRedirectManager RedirectManager
+@inject UserManager UserManager
+@inject IEmailSender EmailSender
+
+Forgot your password?
+
+Forgot your password?
+Enter your email.
+
+
+
+
+
+
+
+
+
+ Email
+
+
+ Reset password
+
+
+
+
+@code {
+ [SupplyParameterFromForm]
+ private InputModel Input { get; set; } = new();
+
+ private async Task OnValidSubmitAsync()
+ {
+ var user = await UserManager.FindByEmailAsync(Input.Email);
+ if (user is null || !(await UserManager.IsEmailConfirmedAsync(user)))
+ {
+ // Don't reveal that the user does not exist or is not confirmed
+ RedirectManager.RedirectTo("/Account/ForgotPasswordConfirmation");
+ return;
+ }
+
+ // For more information on how to enable account confirmation and password reset please
+ // visit https://go.microsoft.com/fwlink/?LinkID=532713
+ var code = await UserManager.GeneratePasswordResetTokenAsync(user);
+ code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
+ var callbackUrl = NavigationManager.GetUriWithQueryParameters(
+ $"{NavigationManager.BaseUri}Account/ResetPassword",
+ new Dictionary { { "code", code } });
+
+ await EmailSender.SendEmailAsync(
+ Input.Email,
+ "Reset Password",
+ $"Please reset your password by clicking here .");
+
+ RedirectManager.RedirectTo("/Account/ForgotPasswordConfirmation");
+ }
+
+ private sealed class InputModel
+ {
+ [Required]
+ [EmailAddress]
+ public string Email { get; set; } = default!;
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ForgotPasswordConfirmation.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ForgotPasswordConfirmation.razor
new file mode 100644
index 000000000000..38de01d1ec0c
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ForgotPasswordConfirmation.razor
@@ -0,0 +1,8 @@
+@page "/Account/ForgotPasswordConfirmation"
+
+Forgot password confirmation
+
+Forgot password confirmation
+
+ Please check your email to reset your password.
+
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/InvalidPasswordReset.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/InvalidPasswordReset.razor
new file mode 100644
index 000000000000..509578bbf82c
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/InvalidPasswordReset.razor
@@ -0,0 +1,8 @@
+@page "/Account/InvalidPasswordReset"
+
+Invalid password reset
+
+Invalid password reset
+
+ The password reset link is invalid.
+
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/InvalidUser.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/InvalidUser.razor
new file mode 100644
index 000000000000..e61fe5def569
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/InvalidUser.razor
@@ -0,0 +1,7 @@
+@page "/Account/InvalidUser"
+
+Invalid user
+
+Invalid user
+
+
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Lockout.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Lockout.razor
new file mode 100644
index 000000000000..a8d1e0afc7ca
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Lockout.razor
@@ -0,0 +1,8 @@
+@page "/Account/Lockout"
+
+Locked out
+
+
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Login.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Login.razor
new file mode 100644
index 000000000000..f49edad1beb7
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Login.razor
@@ -0,0 +1,133 @@
+@page "/Account/Login"
+
+@using System.ComponentModel.DataAnnotations
+@using System.Text
+@using Microsoft.AspNetCore.Authentication
+@using Microsoft.AspNetCore.Identity
+@using Microsoft.AspNetCore.WebUtilities
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject SignInManager SignInManager
+@inject ILogger Logger
+@inject NavigationManager NavigationManager
+@inject IdentityRedirectManager RedirectManager
+
+Log in
+
+Log in
+
+
+
+
+
+ Use a local account to log in.
+
+
+
+
+ Email
+
+
+
+
+ Password
+
+
+
+
+
+ Remember me
+
+
+
+ Log in
+
+
+
+
+
+
+
+ Use another service to log in.
+
+
+
+
+
+
+@code {
+ string? errorMessage;
+
+ [CascadingParameter]
+ public HttpContext HttpContext { get; set; } = default!;
+
+ [SupplyParameterFromForm]
+ public InputModel Input { get; set; } = default!;
+
+ [SupplyParameterFromQuery]
+ public string ReturnUrl { get; set; } = "";
+
+ public class InputModel
+ {
+ [Required]
+ [EmailAddress]
+ public string Email { get; set; } = null!;
+
+ [Required]
+ [DataType(DataType.Password)]
+ public string Password { get; set; } = null!;
+
+ [Display(Name = "Remember me?")]
+ public bool RememberMe { get; set; } = false;
+ }
+
+ protected override async Task OnInitializedAsync()
+ {
+ Input ??= new();
+ ReturnUrl ??= "/";
+
+ if (HttpMethods.IsGet(HttpContext.Request.Method))
+ {
+ // Clear the existing external cookie to ensure a clean login process
+ await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
+ }
+ }
+
+ public async Task LoginUser()
+ {
+ // This doesn't count login failures towards account lockout
+ // To enable password failures to trigger account lockout, set lockoutOnFailure: true
+ var result = await SignInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
+ if (result.Succeeded)
+ {
+ Logger.LogInformation("User logged in.");
+ RedirectManager.RedirectTo(ReturnUrl);
+ }
+ if (result.RequiresTwoFactor)
+ {
+ RedirectManager.RedirectTo(
+ "/Account/LoginWith2fa",
+ new() { ["ReturnUrl"] = ReturnUrl, ["RememberMe"] = Input.RememberMe });
+ }
+ if (result.IsLockedOut)
+ {
+ Logger.LogWarning("User account locked out.");
+ RedirectManager.RedirectTo("/Account/Lockout");
+ }
+ else
+ {
+ errorMessage = "Error: Invalid login attempt.";
+ }
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/LoginWith2fa.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/LoginWith2fa.razor
new file mode 100644
index 000000000000..b1a650544608
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/LoginWith2fa.razor
@@ -0,0 +1,109 @@
+@page "/Account/LoginWith2fa"
+
+@using System.ComponentModel.DataAnnotations
+@using Microsoft.AspNetCore.Identity
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject SignInManager SignInManager
+@inject UserManager UserManager
+@inject IdentityRedirectManager RedirectManager
+@inject ILogger Logger
+
+Two-factor authentication
+
+Two-factor authentication
+
+
+Your login is protected with an authenticator app. Enter your authenticator code below.
+
+
+
+
+
+
+
+
+
+ Authenticator code
+
+
+
+
+
+ Remember this machine
+
+
+
+ Log in
+
+
+
+
+
+ Don't have access to your authenticator device? You can
+ log in with a recovery code .
+
+
+@code {
+ private string? _message;
+ private ApplicationUser _user = default!;
+
+ [SupplyParameterFromForm]
+ private InputModel Input { get; set; } = default!;
+
+ [SupplyParameterFromQuery]
+ private string? ReturnUrl { get; set; }
+
+ [SupplyParameterFromQuery]
+ private bool RememberMe { get; set; }
+
+ protected override async Task OnInitializedAsync()
+ {
+ Input ??= new();
+ ReturnUrl ??= "/";
+
+ var user = await SignInManager.GetTwoFactorAuthenticationUserAsync();
+ if (user is null)
+ {
+ throw new InvalidOperationException($"Unable to load two-factor authentication user.");
+ }
+
+ _user = user;
+ }
+
+ private async Task OnValidSubmitAsync()
+ {
+ var authenticatorCode = Input.TwoFactorCode!.Replace(" ", string.Empty).Replace("-", string.Empty);
+ var result = await SignInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, RememberMe, Input.RememberMachine);
+ var userId = await UserManager.GetUserIdAsync(_user);
+
+ if (result.Succeeded)
+ {
+ Logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", _user.Id);
+ RedirectManager.RedirectTo(ReturnUrl ?? "/");
+ }
+ else if (result.IsLockedOut)
+ {
+ Logger.LogWarning("User with ID '{UserId}' account locked out.", _user.Id);
+ RedirectManager.RedirectTo("/Account/Lockout");
+ }
+ else
+ {
+ Logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", _user.Id);
+ _message = "Error: Invalid authenticator code.";
+ }
+ }
+
+ private sealed class InputModel
+ {
+ [Required]
+ [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
+ [DataType(DataType.Text)]
+ [Display(Name = "Authenticator code")]
+ public string? TwoFactorCode { get; set; }
+
+ [Display(Name = "Remember this machine")]
+ public bool RememberMachine { get; set; }
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/LoginWithRecoveryCode.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/LoginWithRecoveryCode.razor
new file mode 100644
index 000000000000..41d5d3660810
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/LoginWithRecoveryCode.razor
@@ -0,0 +1,94 @@
+@page "/Account/LoginWithRecoveryCode"
+
+@using System.ComponentModel.DataAnnotations
+@using Microsoft.AspNetCore.Identity
+@using Microsoft.AspNetCore.Mvc
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject SignInManager SignInManager
+@inject UserManager UserManager
+@inject IdentityRedirectManager RedirectManager
+@inject ILogger Logger
+
+Recovery code verification
+
+Recovery code verification
+
+
+
+ You have requested to log in with a recovery code. This login will not be remembered until you provide
+ an authenticator app code at log in or disable 2FA and log in again.
+
+
+
+
+
+
+
+
+ Recovery Code
+
+
+ Log in
+
+
+
+
+@code {
+ private string? _message;
+ private ApplicationUser _user = default!;
+
+ [SupplyParameterFromForm]
+ private InputModel Input { get; set; } = default!;
+
+ [SupplyParameterFromQuery]
+ private string? ReturnUrl { get; set; }
+
+ protected override async Task OnInitializedAsync()
+ {
+ Input ??= new();
+
+ // Ensure the user has gone through the username & password screen first
+ var user = await SignInManager.GetTwoFactorAuthenticationUserAsync();
+ if (user is null)
+ {
+ throw new InvalidOperationException($"Unable to load two-factor authentication user.");
+ }
+
+ _user = user;
+ }
+
+ private async Task OnValidSubmitAsync()
+ {
+ var recoveryCode = Input.RecoveryCode!.Replace(" ", string.Empty);
+
+ var result = await SignInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
+
+ var userId = await UserManager.GetUserIdAsync(_user);
+
+ if (result.Succeeded)
+ {
+ Logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", _user.Id);
+ RedirectManager.RedirectTo(ReturnUrl ?? "/");
+ }
+ if (result.IsLockedOut)
+ {
+ Logger.LogWarning("User account locked out.");
+ RedirectManager.RedirectTo("/Account/Lockout");
+ }
+ else
+ {
+ Logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", _user.Id);
+ _message = "Error: Invalid recovery code entered.";
+ }
+ }
+
+ private sealed class InputModel
+ {
+ [Required]
+ [DataType(DataType.Text)]
+ [Display(Name = "Recovery Code")]
+ public string? RecoveryCode { get; set; }
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/ChangePassword.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/ChangePassword.razor
new file mode 100644
index 000000000000..aabf71983a62
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/ChangePassword.razor
@@ -0,0 +1,97 @@
+@page "/Account/Manage/ChangePassword"
+
+@using System.ComponentModel.DataAnnotations
+@using Microsoft.AspNetCore.Identity
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject UserManager UserManager
+@inject SignInManager SignInManager
+@inject UserAccessor UserAccessor
+@inject IdentityRedirectManager RedirectManager
+@inject ILogger Logger
+
+Change password
+
+Change password
+
+
+
+
+
+
+
+
+ Old password
+
+
+
+
+ New password
+
+
+
+
+ Confirm password
+
+
+ Update password
+
+
+
+
+@code {
+ private string? _message;
+ private ApplicationUser _user = default!;
+ private bool _hasPassword;
+
+ [SupplyParameterFromForm]
+ private InputModel Input { get; set; } = default!;
+
+ protected override async Task OnInitializedAsync()
+ {
+ Input ??= new();
+
+ _user = await UserAccessor.GetRequiredUserAsync();
+ _hasPassword = await UserManager.HasPasswordAsync(_user);
+ if (!_hasPassword)
+ {
+ RedirectManager.RedirectTo("/Account/Manage/SetPassword");
+ return;
+ }
+ }
+
+ private async Task OnValidSubmitAsync()
+ {
+ var changePasswordResult = await UserManager.ChangePasswordAsync(_user, Input.OldPassword!, Input.NewPassword!);
+ if (!changePasswordResult.Succeeded)
+ {
+ _message = $"Error: {string.Join(",", changePasswordResult.Errors.Select(error => error.Description))}";
+ return;
+ }
+
+ await SignInManager.RefreshSignInAsync(_user);
+ Logger.LogInformation("User changed their password successfully.");
+
+ RedirectManager.RedirectToCurrentPageWithStatus("Your password has been changed");
+ }
+
+ private sealed class InputModel
+ {
+ [Required]
+ [DataType(DataType.Password)]
+ [Display(Name = "Current password")]
+ public string? OldPassword { get; set; }
+
+ [Required]
+ [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
+ [DataType(DataType.Password)]
+ [Display(Name = "New password")]
+ public string? NewPassword { get; set; }
+
+ [DataType(DataType.Password)]
+ [Display(Name = "Confirm new password")]
+ [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
+ public string? ConfirmPassword { get; set; }
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/DeletePersonalData.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/DeletePersonalData.razor
new file mode 100644
index 000000000000..c3ed9c38635a
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/DeletePersonalData.razor
@@ -0,0 +1,85 @@
+@page "/Account/Manage/DeletePersonalData"
+
+@using System.ComponentModel.DataAnnotations
+@using Microsoft.AspNetCore.Identity
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject UserManager UserManager
+@inject SignInManager SignInManager
+@inject UserAccessor UserAccessor
+@inject IdentityRedirectManager RedirectManager
+@inject ILogger Logger
+
+Delete Personal Data
+
+
+
+Delete Personal Data
+
+
+
+ Deleting this data will permanently remove your account, and this cannot be recovered.
+
+
+
+
+
+
+
+ @if (_requirePassword)
+ {
+
+
+ Password
+
+
+ }
+ Delete data and close my account
+
+
+
+@code {
+ private string? _message;
+ private ApplicationUser _user = default!;
+ private bool _requirePassword;
+
+ [SupplyParameterFromForm]
+ private InputModel Input { get; set; } = default!;
+
+ protected override async Task OnInitializedAsync()
+ {
+ Input ??= new();
+
+ _user = await UserAccessor.GetRequiredUserAsync();
+ _requirePassword = await UserManager.HasPasswordAsync(_user);
+ }
+
+ private async Task OnValidSubmitAsync()
+ {
+ if (_requirePassword && !await UserManager.CheckPasswordAsync(_user, Input.Password!))
+ {
+ _message = "Error: Incorrect password.";
+ return;
+ }
+
+ var result = await UserManager.DeleteAsync(_user);
+ var userId = await UserManager.GetUserIdAsync(_user);
+ if (!result.Succeeded)
+ {
+ throw new InvalidOperationException($"Unexpected error occurred deleting user.");
+ }
+
+ await SignInManager.SignOutAsync();
+
+ Logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId);
+
+ RedirectManager.RedirectToCurrentPage();
+ }
+
+ private sealed class InputModel
+ {
+ [DataType(DataType.Password)]
+ public string? Password { get; set; }
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/Disable2fa.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/Disable2fa.razor
new file mode 100644
index 000000000000..562b5ca26577
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/Disable2fa.razor
@@ -0,0 +1,68 @@
+@page "/Account/Manage/Disable2fa"
+
+@using Microsoft.AspNetCore.Identity
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject UserManager UserManager
+@inject UserAccessor UserAccessor
+@inject IdentityRedirectManager RedirectManager
+@inject ILogger Logger
+
+Disable two-factor authentication (2FA)
+
+
+Disable two-factor authentication (2FA)
+
+
+
+ This action only disables 2FA.
+
+
+ Disabling 2FA does not change the keys used in authenticator apps. If you wish to change the key
+ used in an authenticator app you should reset your authenticator keys.
+
+
+
+
+
+@code {
+ private ApplicationUser _user = default!;
+
+ [CascadingParameter]
+ private HttpContext HttpContext { get; set; } = default!;
+
+ protected override async Task OnInitializedAsync()
+ {
+ _user = await UserAccessor.GetRequiredUserAsync();
+
+ if (HttpMethods.IsGet(HttpContext.Request.Method))
+ {
+ if (!await UserManager.GetTwoFactorEnabledAsync(_user))
+ {
+ throw new InvalidOperationException($"Cannot disable 2FA for user as it's not currently enabled.");
+ }
+ return;
+ }
+ }
+
+ private async Task OnSubmitAsync()
+ {
+ var disable2faResult = await UserManager.SetTwoFactorEnabledAsync(_user, false);
+ if (!disable2faResult.Succeeded)
+ {
+ throw new InvalidOperationException($"Unexpected error occurred disabling 2FA.");
+ }
+
+ var userId = await UserManager.GetUserIdAsync(_user);
+ Logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", userId);
+ RedirectManager.RedirectToWithStatus(
+ "/Account/Manage/TwoFactorAuthentication",
+ "2fa has been disabled. You can reenable 2fa when you setup an authenticator app");
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/Email.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/Email.razor
new file mode 100644
index 000000000000..c629d0a8ee8e
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/Email.razor
@@ -0,0 +1,123 @@
+@page "/Account/Manage/Email"
+
+@using System.ComponentModel.DataAnnotations
+@using System.Text
+@using System.Text.Encodings.Web
+@using Microsoft.AspNetCore.Identity
+@using Microsoft.AspNetCore.Identity.UI.Services
+@using Microsoft.AspNetCore.WebUtilities
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject UserManager UserManager
+@inject UserAccessor UserAccessor
+@inject IEmailSender EmailSender
+@inject NavigationManager NavigationManager
+@inject IdentityRedirectManager RedirectManager
+
+Manage email
+
+Manage email
+
+
+
+
+
+
+
+
+ @if (_isEmailConfirmed)
+ {
+
+ }
+ else
+ {
+
+
+ Email
+ Send verification email
+
+ }
+
+
+ New email
+
+
+ Change email
+
+
+
+
+@code {
+ private ApplicationUser _user = default!;
+ private string? _email;
+ private bool _isEmailConfirmed;
+
+ [SupplyParameterFromForm]
+ private InputModel Input { get; set; } = default!;
+
+ protected override async Task OnInitializedAsync()
+ {
+ Input ??= new();
+
+ _user = await UserAccessor.GetRequiredUserAsync();
+ _email = await UserManager.GetEmailAsync(_user);
+ _isEmailConfirmed = await UserManager.IsEmailConfirmedAsync(_user);
+
+ Input.NewEmail ??= _email;
+ }
+
+ private async Task OnValidSubmitAsync()
+ {
+ if (Input.NewEmail != _email)
+ {
+ var userId = await UserManager.GetUserIdAsync(_user);
+ var code = await UserManager.GenerateChangeEmailTokenAsync(_user, Input.NewEmail!);
+ code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
+ var callbackUrl = NavigationManager.GetUriWithQueryParameters(
+ $"{NavigationManager.BaseUri}Account/ConfirmEmailChange",
+ new Dictionary { { "userId", userId }, { "email", Input.NewEmail }, { "code", code } });
+ await EmailSender.SendEmailAsync(
+ Input.NewEmail!,
+ "Confirm your email",
+ $"Please confirm your account by clicking here .");
+
+ RedirectManager.RedirectToCurrentPageWithStatus("Confirmation link to change email sent. Please check your email.");
+ return;
+ }
+
+ RedirectManager.RedirectToCurrentPageWithStatus("Your email is unchanged.");
+ }
+
+ private async Task OnSendEmailVerificationAsync()
+ {
+ var userId = await UserManager.GetUserIdAsync(_user);
+ var code = await UserManager.GenerateEmailConfirmationTokenAsync(_user);
+ code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
+ var callbackUrl = NavigationManager.GetUriWithQueryParameters(
+ $"{NavigationManager.BaseUri}Account/ConfirmEmail",
+ new Dictionary { { "userId", userId }, { "code", code } });
+ await EmailSender.SendEmailAsync(
+ _email!,
+ "Confirm your email",
+ $"Please confirm your account by clicking here .");
+
+ RedirectManager.RedirectToCurrentPageWithStatus("Verification email sent. Please check your email.");
+ }
+
+ private sealed class InputModel
+ {
+ [Required]
+ [EmailAddress]
+ [Display(Name = "New email")]
+ public string? NewEmail { get; set; }
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/EnableAuthenticator.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/EnableAuthenticator.razor
new file mode 100644
index 000000000000..52b3f0d68c5a
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/EnableAuthenticator.razor
@@ -0,0 +1,174 @@
+@page "/Account/Manage/EnableAuthenticator"
+
+@using System.ComponentModel.DataAnnotations
+@using System.Globalization
+@using System.Text
+@using System.Text.Encodings.Web
+@using Microsoft.AspNetCore.Identity
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject UserManager UserManager
+@inject UserAccessor UserAccessor
+@inject UrlEncoder UrlEncoder
+@inject IdentityRedirectManager RedirectManager
+@inject ILogger Logger
+
+Configure authenticator app
+
+@if (_recoveryCodes is not null)
+{
+
+}
+else
+{
+
+ Configure authenticator app
+
+
To use an authenticator app go through the following steps:
+
+
+
+ Download a two-factor authenticator app like Microsoft Authenticator for
+ Android and
+ iOS or
+ Google Authenticator for
+ Android and
+ iOS .
+
+
+
+ Scan the QR Code or enter this key @_sharedKey into your two factor authenticator app. Spaces and casing do not matter.
+
+
+
+
+
+
+ Once you have scanned the QR code or input the key above, your two factor authentication app will provide you
+ with a unique code. Enter the code in the confirmation box below.
+
+
+
+
+
+
+
+ Verification Code
+
+
+ Verify
+
+
+
+
+
+
+
+}
+
+@code {
+ private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
+
+ private ApplicationUser _user = default!;
+ private string? _sharedKey;
+ private string? _authenticatorUri;
+
+ private IEnumerable? _recoveryCodes;
+ private string? _message;
+
+ [SupplyParameterFromForm]
+ private InputModel Input { get; set; } = default!;
+
+ protected override async Task OnInitializedAsync()
+ {
+ Input ??= new();
+
+ _user = await UserAccessor.GetRequiredUserAsync();
+
+ await LoadSharedKeyAndQrCodeUriAsync(_user);
+ }
+
+ private async Task OnValidSubmitAsync()
+ {
+ // Strip spaces and hyphens
+ var verificationCode = Input.Code!.Replace(" ", string.Empty).Replace("-", string.Empty);
+
+ var is2faTokenValid = await UserManager.VerifyTwoFactorTokenAsync(
+ _user, UserManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
+
+ if (!is2faTokenValid)
+ {
+ await LoadSharedKeyAndQrCodeUriAsync(_user);
+ RedirectManager.RedirectToCurrentPageWithStatus("Error: Verification code is invalid.");
+ return;
+ }
+
+ await UserManager.SetTwoFactorEnabledAsync(_user, true);
+ var userId = await UserManager.GetUserIdAsync(_user);
+ Logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", userId);
+
+ _message = "Your authenticator app has been verified.";
+
+ if (await UserManager.CountRecoveryCodesAsync(_user) == 0)
+ {
+ _recoveryCodes = await UserManager.GenerateNewTwoFactorRecoveryCodesAsync(_user, 10);
+ }
+ else
+ {
+ RedirectManager.RedirectToWithStatus("/Account/Manage/TwoFactorAuthentication", _message);
+ }
+ }
+
+ private async ValueTask LoadSharedKeyAndQrCodeUriAsync(ApplicationUser user)
+ {
+ // Load the authenticator key & QR code URI to display on the form
+ var unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user);
+ if (string.IsNullOrEmpty(unformattedKey))
+ {
+ await UserManager.ResetAuthenticatorKeyAsync(user);
+ unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user);
+ }
+
+ _sharedKey = FormatKey(unformattedKey!);
+
+ var email = await UserManager.GetEmailAsync(user);
+ _authenticatorUri = GenerateQrCodeUri(email!, unformattedKey!);
+ }
+
+ private string FormatKey(string unformattedKey)
+ {
+ var result = new StringBuilder();
+ int currentPosition = 0;
+ while (currentPosition + 4 < unformattedKey.Length)
+ {
+ result.Append(unformattedKey.AsSpan(currentPosition, 4)).Append(' ');
+ currentPosition += 4;
+ }
+ if (currentPosition < unformattedKey.Length)
+ {
+ result.Append(unformattedKey.AsSpan(currentPosition));
+ }
+
+ return result.ToString().ToLowerInvariant();
+ }
+
+ private string GenerateQrCodeUri(string email, string unformattedKey)
+ {
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ AuthenticatorUriFormat,
+ UrlEncoder.Encode("Microsoft.AspNetCore.Identity.UI"),
+ UrlEncoder.Encode(email),
+ unformattedKey);
+ }
+
+ private sealed class InputModel
+ {
+ [Required]
+ [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
+ [DataType(DataType.Text)]
+ [Display(Name = "Verification Code")]
+ public string? Code { get; set; }
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/ExternalLogins.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/ExternalLogins.razor
new file mode 100644
index 000000000000..4ff1ec38044a
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/ExternalLogins.razor
@@ -0,0 +1,152 @@
+@page "/Account/Manage/ExternalLogins"
+
+@using Microsoft.AspNetCore.Authentication
+@using Microsoft.AspNetCore.Identity
+@using Microsoft.AspNetCore.Mvc.ViewFeatures
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject UserManager UserManager
+@inject SignInManager SignInManager
+@inject UserAccessor UserAccessor
+@inject IUserStore UserStore
+@inject IdentityRedirectManager RedirectManager
+
+Manage your external logins
+
+
+@if (_currentLogins?.Count > 0)
+{
+ Registered Logins
+
+
+ @foreach (var login in _currentLogins)
+ {
+
+ @login.ProviderDisplayName
+
+ @if (_showRemoveButton)
+ {
+
+ }
+ else
+ {
+ @:
+ }
+
+
+ }
+
+
+}
+@if (_otherLogins?.Count > 0)
+{
+ Add another service to log in.
+
+
+}
+
+@code {
+ public const string LinkLoginCallbackAction = "LinkLoginCallback";
+
+ private ApplicationUser _user = default!;
+ private IList? _currentLogins;
+ private IList? _otherLogins;
+ private bool _showRemoveButton;
+
+ [SupplyParameterFromForm]
+ private string? LoginProvider { get; set; }
+
+ [SupplyParameterFromForm]
+ private string? ProviderKey { get; set; }
+
+ [SupplyParameterFromQuery]
+ private string? Action { get; set; }
+
+ [CascadingParameter]
+ private HttpContext HttpContext { get; set; } = default!;
+
+ protected override async Task OnInitializedAsync()
+ {
+ _user = await UserAccessor.GetRequiredUserAsync();
+ _currentLogins = await UserManager.GetLoginsAsync(_user);
+ _otherLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync())
+ .Where(auth => _currentLogins.All(ul => auth.Name != ul.LoginProvider))
+ .ToList();
+
+ string? passwordHash = null;
+ if (UserStore is IUserPasswordStore userPasswordStore)
+ {
+ passwordHash = await userPasswordStore.GetPasswordHashAsync(_user, HttpContext.RequestAborted);
+ }
+
+ _showRemoveButton = passwordHash is not null || _currentLogins.Count > 1;
+
+ if (HttpMethods.IsGet(HttpContext.Request.Method) && Action == LinkLoginCallbackAction)
+ {
+ await OnGetLinkLoginCallbackAsync();
+ return;
+ }
+ }
+
+ private async Task OnSubmitAsync()
+ {
+ var result = await UserManager.RemoveLoginAsync(_user, LoginProvider!, ProviderKey!);
+ if (!result.Succeeded)
+ {
+ RedirectManager.RedirectToCurrentPageWithStatus("The external login was not removed.");
+ return;
+ }
+
+ await SignInManager.RefreshSignInAsync(_user);
+ RedirectManager.RedirectToCurrentPageWithStatus("The external login was removed.");
+ }
+
+ private async Task OnGetLinkLoginCallbackAsync()
+ {
+ var userId = await UserManager.GetUserIdAsync(_user);
+ var info = await SignInManager.GetExternalLoginInfoAsync(userId);
+ if (info == null)
+ {
+ RedirectManager.RedirectToCurrentPageWithStatus("Unexpected error occurred loading external login info.");
+ return;
+ }
+
+ var result = await UserManager.AddLoginAsync(_user, info);
+ if (!result.Succeeded)
+ {
+ RedirectManager.RedirectToCurrentPageWithStatus("The external login was not added. External logins can only be associated with one account.");
+ return;
+ }
+
+ // Clear the existing external cookie to ensure a clean login process
+ await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
+
+ RedirectManager.RedirectToCurrentPageWithStatus("The external login was added.");
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/GenerateRecoveryCodes.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/GenerateRecoveryCodes.razor
new file mode 100644
index 000000000000..3de6f69e9c40
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/GenerateRecoveryCodes.razor
@@ -0,0 +1,67 @@
+@page "/Account/Manage/GenerateRecoveryCodes"
+
+@using Microsoft.AspNetCore.Identity
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject UserManager UserManager
+@inject UserAccessor UserAccessor
+@inject IdentityRedirectManager RedirectManager
+@inject ILogger Logger
+
+Generate two-factor authentication (2FA) recovery codes
+
+@if (_recoveryCodes is not null)
+{
+
+}
+else
+{
+ Generate two-factor authentication (2FA) recovery codes
+
+
+
+ Put these codes in a safe place.
+
+
+ If you lose your device and don't have the recovery codes you will lose access to your account.
+
+
+ Generating new recovery codes does not change the keys used in authenticator apps. If you wish to change the key
+ used in an authenticator app you should reset your authenticator keys.
+
+
+
+}
+
+@code {
+ private ApplicationUser _user = default!;
+
+ private IEnumerable? _recoveryCodes;
+ private string? _message;
+
+ protected override async Task OnInitializedAsync()
+ {
+ _user = await UserAccessor.GetRequiredUserAsync();
+
+ var isTwoFactorEnabled = await UserManager.GetTwoFactorEnabledAsync(_user);
+ if (!isTwoFactorEnabled)
+ {
+ throw new InvalidOperationException($"Cannot generate recovery codes for user because they do not have 2FA enabled.");
+ }
+ }
+
+ private async Task OnSubmitAsync()
+ {
+ var userId = await UserManager.GetUserIdAsync(_user);
+ _recoveryCodes = await UserManager.GenerateNewTwoFactorRecoveryCodesAsync(_user, 10);
+ _message = "You have generated new recovery codes.";
+
+ Logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId);
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/Index.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/Index.razor
new file mode 100644
index 000000000000..7e59b2732987
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/Index.razor
@@ -0,0 +1,80 @@
+@page "/Account/Manage"
+
+@using System.ComponentModel.DataAnnotations;
+@using System.Security.Claims
+@using Microsoft.AspNetCore.Identity;
+@using BlazorWeb_CSharp.Data;
+@using BlazorWeb_CSharp.Identity
+
+@inject AuthenticationStateProvider AuthenticationStateProvider
+@inject UserManager UserManager
+@inject SignInManager SignInManager
+@inject UserAccessor UserAccessor;
+@inject IdentityRedirectManager RedirectManager
+
+Profile
+
+Profile
+
+
+
+
+
+
+
+
+
+ Username
+
+
+
+ Phone number
+
+
+ Save
+
+
+
+
+@code {
+ private ApplicationUser _user = default!;
+ private string? _username;
+ private string? _phoneNumber;
+
+ [SupplyParameterFromForm]
+ private InputModel Input { get; set; } = default!;
+
+ protected override async Task OnInitializedAsync()
+ {
+ Input ??= new();
+
+ _user = await UserAccessor.GetRequiredUserAsync();
+ _username = await UserManager.GetUserNameAsync(_user);
+ _phoneNumber = await UserManager.GetPhoneNumberAsync(_user);
+
+ Input.PhoneNumber ??= _phoneNumber;
+ }
+
+ private async Task OnValidSubmitAsync()
+ {
+ if (Input.PhoneNumber != _phoneNumber)
+ {
+ var setPhoneResult = await UserManager.SetPhoneNumberAsync(_user, Input.PhoneNumber);
+ if (!setPhoneResult.Succeeded)
+ {
+ RedirectManager.RedirectToCurrentPageWithStatus("Unexpected error when trying to set phone number.");
+ return;
+ }
+ }
+
+ await SignInManager.RefreshSignInAsync(_user);
+ RedirectManager.RedirectToCurrentPageWithStatus("Your profile has been updated");
+ }
+
+ private sealed class InputModel
+ {
+ [Phone]
+ [Display(Name = "Phone number")]
+ public string? PhoneNumber { get; set; }
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/PersonalData.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/PersonalData.razor
new file mode 100644
index 000000000000..b9b21c6990f0
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/PersonalData.razor
@@ -0,0 +1,35 @@
+@page "/Account/Manage/PersonalData"
+
+@using System.Text.Json
+@using Microsoft.AspNetCore.Identity
+@using BlazorWeb_CSharp.Data
+
+@inject UserAccessor UserAccessor
+
+Personal Data
+
+
+Personal Data
+
+
+
+
Your account contains personal data that you have given us. This page allows you to download or delete that data.
+
+ Deleting this data will permanently remove your account, and this cannot be recovered.
+
+
+
+ Delete
+
+
+
+
+@code {
+ protected override async Task OnInitializedAsync()
+ {
+ _ = await UserAccessor.GetRequiredUserAsync();
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/ResetAuthenticator.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/ResetAuthenticator.razor
new file mode 100644
index 000000000000..dd951c7d2410
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/ResetAuthenticator.razor
@@ -0,0 +1,55 @@
+@page "/Account/Manage/ResetAuthenticator"
+
+@using Microsoft.AspNetCore.Identity
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject UserManager UserManager
+@inject SignInManager SignInManager
+@inject UserAccessor UserAccessor
+@inject IdentityRedirectManager RedirectManager
+@inject ILogger Logger
+
+Reset authenticator key
+
+
+Reset authenticator key
+
+
+
+ If you reset your authenticator key your authenticator app will not work until you reconfigure it.
+
+
+ This process disables 2FA until you verify your authenticator app.
+ If you do not complete your authenticator app configuration you may lose access to your account.
+
+
+
+
+@code {
+ private ApplicationUser _user = default!;
+
+ protected override async Task OnInitializedAsync()
+ {
+ _user = await UserAccessor.GetRequiredUserAsync();
+ }
+
+ private async Task OnSubmitAsync()
+ {
+ await UserManager.SetTwoFactorEnabledAsync(_user, false);
+ await UserManager.ResetAuthenticatorKeyAsync(_user);
+ var userId = await UserManager.GetUserIdAsync(_user);
+ Logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", _user.Id);
+
+ await SignInManager.RefreshSignInAsync(_user);
+
+ RedirectManager.RedirectToWithStatus(
+ "/Account/Manage/EnableAuthenticator",
+ "Your authenticator app key has been reset, you will need to configure your authenticator app using the new key.");
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/SetPassword.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/SetPassword.razor
new file mode 100644
index 000000000000..5f1aff4403ad
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/SetPassword.razor
@@ -0,0 +1,88 @@
+@page "/Account/Manage/SetPassword"
+
+@using System.ComponentModel.DataAnnotations
+@using Microsoft.AspNetCore.Identity
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject UserManager UserManager
+@inject SignInManager SignInManager
+@inject UserAccessor UserAccessor
+@inject IdentityRedirectManager RedirectManager
+
+Set password
+
+Set your password
+
+
+ You do not have a local username/password for this site. Add a local
+ account so you can log in without an external login.
+
+
+
+
+
+
+
+
+ New password
+
+
+
+
+ Confirm password
+
+
+ Set password
+
+
+
+
+@code {
+ private string? _message;
+ private ApplicationUser _user = default!;
+
+ [SupplyParameterFromForm]
+ private InputModel Input { get; set; } = default!;
+
+ protected override async Task OnInitializedAsync()
+ {
+ Input ??= new();
+
+ _user = await UserAccessor.GetRequiredUserAsync();
+
+ var hasPassword = await UserManager.HasPasswordAsync(_user);
+ if (hasPassword)
+ {
+ RedirectManager.RedirectTo("/Account/Manage/ChangePassword");
+ return;
+ }
+ }
+
+ private async Task OnValidSubmitAsync()
+ {
+ var addPasswordResult = await UserManager.AddPasswordAsync(_user, Input.NewPassword!);
+ if (!addPasswordResult.Succeeded)
+ {
+ _message = $"Error: {string.Join(",", addPasswordResult.Errors.Select(error => error.Description))}";
+ return;
+ }
+
+ await SignInManager.RefreshSignInAsync(_user);
+ RedirectManager.RedirectToCurrentPageWithStatus("Your password has been set.");
+ }
+
+ private sealed class InputModel
+ {
+ [Required]
+ [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
+ [DataType(DataType.Password)]
+ [Display(Name = "New password")]
+ public string? NewPassword { get; set; }
+
+ [DataType(DataType.Password)]
+ [Display(Name = "Confirm new password")]
+ [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
+ public string? ConfirmPassword { get; set; }
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/TwoFactorAuthentication.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/TwoFactorAuthentication.razor
new file mode 100644
index 000000000000..fbb2f32eddbd
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/TwoFactorAuthentication.razor
@@ -0,0 +1,101 @@
+@page "/Account/Manage/TwoFactorAuthentication"
+
+@using Microsoft.AspNetCore.Http.Features
+@using Microsoft.AspNetCore.Identity
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject UserManager UserManager
+@inject SignInManager SignInManager
+@inject UserAccessor UserAccessor
+@inject IdentityRedirectManager RedirectManager
+
+Two-factor authentication (2FA)
+
+
+Two-factor authentication (2FA)
+@if (_consentFeature?.CanTrack ?? true)
+{
+ if (_is2faEnabled)
+ {
+ if (_recoveryCodesLeft == 0)
+ {
+
+ }
+ else if (_recoveryCodesLeft == 1)
+ {
+
+ }
+ else if (_recoveryCodesLeft <= 3)
+ {
+
+ }
+
+ if (_isMachineRemembered)
+ {
+
+ }
+
+ Disable 2FA
+ Reset recovery codes
+ }
+
+ Authenticator app
+ @if (!_hasAuthenticator)
+ {
+ Add authenticator app
+ }
+ else
+ {
+ Set up authenticator app
+ Reset authenticator app
+ }
+}
+else
+{
+
+
Privacy and cookie policy have not been accepted.
+
You must accept the policy before you can enable two factor authentication.
+
+}
+
+@code {
+ private ApplicationUser? _user;
+ private ITrackingConsentFeature? _consentFeature;
+ private bool _hasAuthenticator;
+ private int _recoveryCodesLeft;
+ private bool _is2faEnabled;
+ private bool _isMachineRemembered;
+
+ [CascadingParameter]
+ private HttpContext HttpContext { get; set; } = default!;
+
+ protected override async Task OnInitializedAsync()
+ {
+ _user = await UserAccessor.GetRequiredUserAsync();
+ _consentFeature = HttpContext.Features.Get();
+ _hasAuthenticator = await UserManager.GetAuthenticatorKeyAsync(_user) is not null;
+ _is2faEnabled = await UserManager.GetTwoFactorEnabledAsync(_user);
+ _isMachineRemembered = await SignInManager.IsTwoFactorClientRememberedAsync(_user);
+ _recoveryCodesLeft = await UserManager.CountRecoveryCodesAsync(_user);
+ }
+
+ private async Task OnSubmitForgetBrowserAsync()
+ {
+ await SignInManager.ForgetTwoFactorClientAsync();
+
+ RedirectManager.RedirectToCurrentPageWithStatus(
+ "The current browser has been forgotten. When you login again from this browser you will be prompted for your 2fa code.");
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/_Imports.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/_Imports.razor
new file mode 100644
index 000000000000..d605aec1f504
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Manage/_Imports.razor
@@ -0,0 +1,2 @@
+@layout BlazorWeb_CSharp.Components.Layout.ManageLayout
+@attribute [Microsoft.AspNetCore.Authorization.Authorize]
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Register.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Register.razor
new file mode 100644
index 000000000000..822a95b69460
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/Register.razor
@@ -0,0 +1,176 @@
+@page "/Account/Register"
+
+@using System.ComponentModel.DataAnnotations
+@using System.Text
+@using System.Text.Encodings.Web
+@using Microsoft.AspNetCore.Authentication
+@using Microsoft.AspNetCore.Identity
+@using Microsoft.AspNetCore.Identity.UI.Services
+@using Microsoft.AspNetCore.WebUtilities
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject UserManager UserManager
+@inject IUserStore UserStore
+@inject SignInManager SignInManager
+@inject ILogger Logger
+@inject IEmailSender EmailSender
+@inject NavigationManager NavigationManager
+@inject IdentityRedirectManager RedirectManager
+
+Register
+
+Register
+
+
+
+
+
+
+ Create a new account.
+
+
+
+
+ Email
+
+
+
+
+ Password
+
+
+
+
+ Confirm Password
+
+
+ Register
+
+
+
+
+ Use another service to register.
+
+
+
+
+
+
+@code {
+ [SupplyParameterFromForm]
+ public InputModel Input { get; set; } = default!;
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [SupplyParameterFromQuery]
+ public string ReturnUrl { get; set; } = "";
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class InputModel
+ {
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [Required]
+ [EmailAddress]
+ [Display(Name = "Email")]
+ public string Email { get; set; } = null!;
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [Required]
+ [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
+ [DataType(DataType.Password)]
+ [Display(Name = "Password")]
+ public string Password { get; set; } = null!;
+
+ ///
+ /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ [DataType(DataType.Password)]
+ [Display(Name = "Confirm password")]
+ [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
+ public string ConfirmPassword { get; set; } = null!;
+ }
+
+ IEnumerable? identityErrors;
+ string? Message => identityErrors is null ? null : "Error: " + string.Join(", ", identityErrors.Select(error => error.Description));
+
+ protected override void OnInitialized()
+ {
+ Input ??= new();
+ }
+
+ public async Task RegisterUser(EditContext editContext)
+ {
+ var user = CreateUser();
+
+ await UserStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
+ var emailStore = GetEmailStore();
+ await emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
+ var result = await UserManager.CreateAsync(user, Input.Password);
+
+ if (result.Succeeded)
+ {
+ Logger.LogInformation("User created a new account with password.");
+
+ var userId = await UserManager.GetUserIdAsync(user);
+ var code = await UserManager.GenerateEmailConfirmationTokenAsync(user);
+ code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
+ var callbackUrl = NavigationManager.GetUriWithQueryParameters(
+ NavigationManager.ToAbsoluteUri("/Account/ConfirmEmail").AbsoluteUri,
+ new Dictionary { { "userId", userId }, { "code", code }, { "returnUrl", ReturnUrl } });
+
+ await EmailSender.SendEmailAsync(Input.Email, "Confirm your email",
+ $"Please confirm your account by clicking here .");
+
+ if (UserManager.Options.SignIn.RequireConfirmedAccount)
+ {
+ RedirectManager.RedirectTo(
+ "/Account/RegisterConfirmation",
+ new() { ["Email"] = Input.Email, ["ReturnUrl"] = ReturnUrl });
+ }
+ else
+ {
+ await SignInManager.SignInAsync(user, isPersistent: false);
+ RedirectManager.RedirectTo(ReturnUrl);
+ }
+ }
+ else
+ {
+ identityErrors = result.Errors;
+ }
+ }
+
+ private ApplicationUser CreateUser()
+ {
+ try
+ {
+ return Activator.CreateInstance();
+ }
+ catch
+ {
+ throw new InvalidOperationException($"Can't create an instance of '{nameof(ApplicationUser)}'. " +
+ $"Ensure that '{nameof(ApplicationUser)}' is not an abstract class and has a parameterless constructor.");
+ }
+ }
+
+ private IUserEmailStore GetEmailStore()
+ {
+ if (!UserManager.SupportsUserEmail)
+ {
+ throw new NotSupportedException("The default UI requires a user store with email support.");
+ }
+ return (IUserEmailStore) UserStore;
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/RegisterConfirmation.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/RegisterConfirmation.razor
new file mode 100644
index 000000000000..5f54beb59e9a
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/RegisterConfirmation.razor
@@ -0,0 +1,67 @@
+@page "/Account/RegisterConfirmation"
+
+@using System.Text
+@using Microsoft.AspNetCore.Identity
+@using Microsoft.AspNetCore.Identity.UI.Services
+@using Microsoft.AspNetCore.WebUtilities
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject UserManager UserManager
+@inject IEmailSender EmailSender
+@inject NavigationManager NavigationManager
+@inject IdentityRedirectManager RedirectManager
+
+Register confirmation
+
+Register confirmation
+
+
+
+@if (emailConfirmationLink is not null)
+{
+
+ This app does not currently have a real email sender registered, see these docs for how to configure a real email sender.
+ Normally this would be emailed: Click here to confirm your account
+
+}
+else
+{
+ Please check your email to confirm your account.
+}
+
+@code {
+ string? emailConfirmationLink;
+ string? statusMessage;
+
+ [SupplyParameterFromQuery] public string? Email { get; set; }
+ [SupplyParameterFromQuery] public string ReturnUrl { get; set; } = "/";
+
+ protected override async Task OnInitializedAsync()
+ {
+ if (Email == null)
+ {
+ RedirectManager.RedirectTo("/");
+ }
+ else
+ {
+
+ var user = await UserManager.FindByEmailAsync(Email);
+ if (user == null)
+ {
+ // Need a way to trigger a 404 from Blazor: https://github.com/dotnet/aspnetcore/issues/45654
+ statusMessage = $"Error finding user for unspecified email";
+ }
+ else if (EmailSender is NoOpEmailSender)
+ {
+ // Once you add a real email sender, you should remove this code that lets you confirm the account
+ var userId = await UserManager.GetUserIdAsync(user);
+ var code = await UserManager.GenerateEmailConfirmationTokenAsync(user);
+ code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
+ emailConfirmationLink = NavigationManager.GetUriWithQueryParameters(
+ NavigationManager.ToAbsoluteUri("/Account/ConfirmEmail").AbsoluteUri,
+ new Dictionary { { "userId", userId }, { "code", code }, { "returnUrl", ReturnUrl } });
+ }
+ }
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ResendEmailConfirmation.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ResendEmailConfirmation.razor
new file mode 100644
index 000000000000..fa5f9d1dbafe
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ResendEmailConfirmation.razor
@@ -0,0 +1,78 @@
+@page "/Account/ResendEmailConfirmation"
+
+@using System.ComponentModel.DataAnnotations
+@using System.Text
+@using System.Text.Encodings.Web
+@using Microsoft.AspNetCore.Identity
+@using Microsoft.AspNetCore.Identity.UI.Services
+@using Microsoft.AspNetCore.WebUtilities
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject UserManager UserManager
+@inject IEmailSender EmailSender
+@inject NavigationManager NavigationManager
+@inject IdentityRedirectManager RedirectManager
+
+Resend email confirmation
+
+Resend email confirmation
+Enter your email.
+
+
+
+
+
+
+
+
+
+ Email
+
+
+ Resend
+
+
+
+
+@code {
+ private string? _message;
+
+ [SupplyParameterFromForm]
+ private InputModel Input { get; set; } = default!;
+
+ protected override void OnInitialized()
+ {
+ Input ??= new();
+ }
+
+ private async Task OnValidSubmitAsync()
+ {
+ var user = await UserManager.FindByEmailAsync(Input.Email!);
+ if (user is null)
+ {
+ _message = "Verification email sent. Please check your email.";
+ return;
+ }
+
+ var userId = await UserManager.GetUserIdAsync(user);
+ var code = await UserManager.GenerateEmailConfirmationTokenAsync(user);
+ code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
+ var callbackUrl = NavigationManager.GetUriWithQueryParameters(
+ $"{NavigationManager.BaseUri}Account/ConfirmEmail",
+ new Dictionary { ["userId"] = userId, ["code"] = code });
+ await EmailSender.SendEmailAsync(
+ Input.Email!,
+ "Confirm your email",
+ $"Please confirm your account by clicking here .");
+
+ _message = "Verification email sent. Please check your email.";
+ }
+
+ private sealed class InputModel
+ {
+ [Required]
+ [EmailAddress]
+ public string? Email { get; set; }
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ResetPassword.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ResetPassword.razor
new file mode 100644
index 000000000000..214c42643eb4
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ResetPassword.razor
@@ -0,0 +1,108 @@
+@page "/Account/ResetPassword"
+
+@using System.ComponentModel.DataAnnotations
+@using System.Text
+@using Microsoft.AspNetCore.Http
+@using Microsoft.AspNetCore.Identity
+@using Microsoft.AspNetCore.WebUtilities
+@using BlazorWeb_CSharp.Data
+@using BlazorWeb_CSharp.Identity
+
+@inject IdentityRedirectManager RedirectManager
+@inject UserManager UserManager
+
+Reset password
+
+Reset password
+Reset your password.
+
+
+
+
+
+
+
+
+
+
+
+ Email
+
+
+
+
+ Password
+
+
+
+
+ Confirm password
+
+
+ Reset
+
+
+
+
+@code {
+ [SupplyParameterFromForm]
+ private InputModel Input { get; set; } = new();
+
+ [SupplyParameterFromQuery]
+ private string? Code { get; set; }
+
+ IEnumerable? identityErrors;
+ private string? Message => identityErrors is null ? null : "Error: " + string.Join(", ", identityErrors.Select(error => error.Description));
+
+ protected override void OnInitialized()
+ {
+ if (Code is null)
+ {
+ RedirectManager.RedirectTo("/Account/InvalidPasswordReset");
+ }
+ else
+ {
+ Input.Code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(Code));
+ }
+ }
+
+ private async Task OnValidSubmitAsync()
+ {
+ var user = await UserManager.FindByEmailAsync(Input.Email);
+ if (user is null)
+ {
+ // Don't reveal that the user does not exist
+ RedirectManager.RedirectTo("/Account/ResetPasswordConfirmation");
+ return;
+ }
+
+ var result = await UserManager.ResetPasswordAsync(user, Input.Code, Input.Password);
+ if (result.Succeeded)
+ {
+ RedirectManager.RedirectTo("/Account/ResetPasswordConfirmation");
+ return;
+ }
+
+ identityErrors = result.Errors;
+ }
+
+ private sealed class InputModel
+ {
+ [Required]
+ [EmailAddress]
+ public string Email { get; set; } = default!;
+
+ [Required]
+ [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
+ [DataType(DataType.Password)]
+ public string Password { get; set; } = default!;
+
+ [DataType(DataType.Password)]
+ [Display(Name = "Confirm password")]
+ [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
+ public string ConfirmPassword { get; set; } = default!;
+
+ [Required]
+ public string Code { get; set; } = default!;
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ResetPasswordConfirmation.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ResetPasswordConfirmation.razor
new file mode 100644
index 000000000000..273c8247bd14
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/ResetPasswordConfirmation.razor
@@ -0,0 +1,7 @@
+@page "/Account/ResetPasswordConfirmation"
+Reset password confirmation
+
+Reset password confirmation
+
+ Your password has been reset. Please click here to log in .
+
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/_Imports.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/_Imports.razor
new file mode 100644
index 000000000000..dfb627bc86ad
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Account/_Imports.razor
@@ -0,0 +1 @@
+@using BlazorWeb_CSharp.Components.Identity
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Auth.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Auth.razor
new file mode 100644
index 000000000000..b7bbe6eb3d98
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Pages/Auth.razor
@@ -0,0 +1,13 @@
+@page "/auth"
+
+@using Microsoft.AspNetCore.Authorization
+
+@attribute [Authorize]
+
+Auth
+
+You are authenticated
+
+
+ Hello @context.User.Identity?.Name!
+
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Routes.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Routes.razor
index 25a5e5887bfe..1426521804d1 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Routes.razor
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/Routes.razor
@@ -4,7 +4,11 @@
##endif*@
+ @*#if (IndividualLocalAuth)
+
+ ##else
+ ##endif*@
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/_Imports.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/_Imports.razor
index 68ab14b7c1e6..bb52859cae6d 100644
--- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/_Imports.razor
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Components/_Imports.razor
@@ -1,5 +1,8 @@
@using System.Net.Http
@using System.Net.Http.Json
+@*#if (IndividualLocalAuth)
+@using Microsoft.AspNetCore.Components.Authorization
+##endif*@
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Data/ApplicationDbContext.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Data/ApplicationDbContext.cs
new file mode 100644
index 000000000000..2ce33e233a9e
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Data/ApplicationDbContext.cs
@@ -0,0 +1,8 @@
+using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+
+namespace BlazorWeb_CSharp.Data;
+
+public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext(options)
+{
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Data/ApplicationUser.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Data/ApplicationUser.cs
new file mode 100644
index 000000000000..831bb92a4a57
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Data/ApplicationUser.cs
@@ -0,0 +1,9 @@
+using Microsoft.AspNetCore.Identity;
+
+namespace BlazorWeb_CSharp.Data;
+
+// Add profile data for application users by adding properties to the ApplicationUser class
+public class ApplicationUser : IdentityUser
+{
+}
+
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs
new file mode 100644
index 000000000000..ba5b97c07dff
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs
@@ -0,0 +1,268 @@
+//
+using System;
+using BlazorWeb_CSharp.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace BlazorWeb_CSharp.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("00000000000000_CreateIdentitySchema")]
+ partial class CreateIdentitySchema
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
+
+ modelBuilder.Entity("BlazorWeb_CSharp.Data.ApplicationUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("PasswordHash")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("TEXT");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ClaimType")
+ .HasColumnType("TEXT");
+
+ b.Property("ClaimValue")
+ .HasColumnType("TEXT");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ClaimType")
+ .HasColumnType("TEXT");
+
+ b.Property("ClaimValue")
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("TEXT");
+
+ b.Property("ProviderKey")
+ .HasColumnType("TEXT");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.Property("RoleId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.Property("LoginProvider")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("BlazorWeb_CSharp.Data.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("BlazorWeb_CSharp.Data.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("BlazorWeb_CSharp.Data.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.HasOne("BlazorWeb_CSharp.Data.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.cs
new file mode 100644
index 000000000000..bf83fc9a9f27
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.cs
@@ -0,0 +1,222 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace BlazorWeb_CSharp.Migrations
+{
+ ///
+ public partial class CreateIdentitySchema : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "AspNetRoles",
+ columns: table => new
+ {
+ Id = table.Column(type: "TEXT", nullable: false),
+ Name = table.Column(type: "TEXT", maxLength: 256, nullable: true),
+ NormalizedName = table.Column(type: "TEXT", maxLength: 256, nullable: true),
+ ConcurrencyStamp = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetRoles", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUsers",
+ columns: table => new
+ {
+ Id = table.Column(type: "TEXT", nullable: false),
+ UserName = table.Column(type: "TEXT", maxLength: 256, nullable: true),
+ NormalizedUserName = table.Column(type: "TEXT", maxLength: 256, nullable: true),
+ Email = table.Column(type: "TEXT", maxLength: 256, nullable: true),
+ NormalizedEmail = table.Column(type: "TEXT", maxLength: 256, nullable: true),
+ EmailConfirmed = table.Column(type: "INTEGER", nullable: false),
+ PasswordHash = table.Column(type: "TEXT", nullable: true),
+ SecurityStamp = table.Column(type: "TEXT", nullable: true),
+ ConcurrencyStamp = table.Column(type: "TEXT", nullable: true),
+ PhoneNumber = table.Column(type: "TEXT", nullable: true),
+ PhoneNumberConfirmed = table.Column(type: "INTEGER", nullable: false),
+ TwoFactorEnabled = table.Column(type: "INTEGER", nullable: false),
+ LockoutEnd = table.Column(type: "TEXT", nullable: true),
+ LockoutEnabled = table.Column(type: "INTEGER", nullable: false),
+ AccessFailedCount = table.Column(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUsers", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetRoleClaims",
+ columns: table => new
+ {
+ Id = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ RoleId = table.Column(type: "TEXT", nullable: false),
+ ClaimType = table.Column(type: "TEXT", nullable: true),
+ ClaimValue = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
+ table.ForeignKey(
+ name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
+ column: x => x.RoleId,
+ principalTable: "AspNetRoles",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserClaims",
+ columns: table => new
+ {
+ Id = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ UserId = table.Column(type: "TEXT", nullable: false),
+ ClaimType = table.Column(type: "TEXT", nullable: true),
+ ClaimValue = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
+ table.ForeignKey(
+ name: "FK_AspNetUserClaims_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserLogins",
+ columns: table => new
+ {
+ LoginProvider = table.Column(type: "TEXT", nullable: false),
+ ProviderKey = table.Column(type: "TEXT", nullable: false),
+ ProviderDisplayName = table.Column(type: "TEXT", nullable: true),
+ UserId = table.Column(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
+ table.ForeignKey(
+ name: "FK_AspNetUserLogins_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserRoles",
+ columns: table => new
+ {
+ UserId = table.Column(type: "TEXT", nullable: false),
+ RoleId = table.Column(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
+ table.ForeignKey(
+ name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
+ column: x => x.RoleId,
+ principalTable: "AspNetRoles",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_AspNetUserRoles_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserTokens",
+ columns: table => new
+ {
+ UserId = table.Column(type: "TEXT", nullable: false),
+ LoginProvider = table.Column(type: "TEXT", nullable: false),
+ Name = table.Column(type: "TEXT", nullable: false),
+ Value = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
+ table.ForeignKey(
+ name: "FK_AspNetUserTokens_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetRoleClaims_RoleId",
+ table: "AspNetRoleClaims",
+ column: "RoleId");
+
+ migrationBuilder.CreateIndex(
+ name: "RoleNameIndex",
+ table: "AspNetRoles",
+ column: "NormalizedName",
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetUserClaims_UserId",
+ table: "AspNetUserClaims",
+ column: "UserId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetUserLogins_UserId",
+ table: "AspNetUserLogins",
+ column: "UserId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetUserRoles_RoleId",
+ table: "AspNetUserRoles",
+ column: "RoleId");
+
+ migrationBuilder.CreateIndex(
+ name: "EmailIndex",
+ table: "AspNetUsers",
+ column: "NormalizedEmail");
+
+ migrationBuilder.CreateIndex(
+ name: "UserNameIndex",
+ table: "AspNetUsers",
+ column: "NormalizedUserName",
+ unique: true);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "AspNetRoleClaims");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserClaims");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserLogins");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserRoles");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserTokens");
+
+ migrationBuilder.DropTable(
+ name: "AspNetRoles");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUsers");
+ }
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Data/SqlLite/ApplicationDbContextModelSnapshot.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Data/SqlLite/ApplicationDbContextModelSnapshot.cs
new file mode 100644
index 000000000000..930cebc1857d
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Data/SqlLite/ApplicationDbContextModelSnapshot.cs
@@ -0,0 +1,265 @@
+//
+using System;
+using BlazorWeb_CSharp.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace BlazorWeb_CSharp.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ partial class ApplicationDbContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
+
+ modelBuilder.Entity("BlazorWeb_CSharp.Data.ApplicationUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("PasswordHash")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("TEXT");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ClaimType")
+ .HasColumnType("TEXT");
+
+ b.Property("ClaimValue")
+ .HasColumnType("TEXT");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ClaimType")
+ .HasColumnType("TEXT");
+
+ b.Property("ClaimValue")
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("TEXT");
+
+ b.Property("ProviderKey")
+ .HasColumnType("TEXT");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.Property("RoleId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.Property("LoginProvider")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("BlazorWeb_CSharp.Data.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("BlazorWeb_CSharp.Data.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("BlazorWeb_CSharp.Data.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.HasOne("BlazorWeb_CSharp.Data.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs
new file mode 100644
index 000000000000..34d2b6df1a30
--- /dev/null
+++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs
@@ -0,0 +1,279 @@
+//
+using System;
+using BlazorWeb_CSharp.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace BlazorWeb_CSharp.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("00000000000000_CreateIdentitySchema")]
+ partial class CreateIdentitySchema
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.0")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("BlazorWeb_CSharp.Data.ApplicationUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("int");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("bit");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("bit");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("PasswordHash")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("bit");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("bit");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex")
+ .HasFilter("[NormalizedUserName] IS NOT NULL");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex")
+ .HasFilter("[NormalizedName] IS NOT NULL");
+
+ b.ToTable("AspNetRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ClaimValue")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ClaimValue")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ProviderKey")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("RoleId")
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property