[Blazor][Wasm] Template changes to support auth in Blazor wasm (#18933)

* [Blazor] Adds a project template option for individual auth
* Handles hosted scenarios with Identity Server.
* Handles non-hosted scenarios with oidc-client.js.
* Handles AAD and B2C scenarios with an MSAL library (disabled for now).
This commit is contained in:
Javier Calvarro Nelson 2020-02-17 17:27:21 -08:00 committed by GitHub
parent 0dbb01bd8c
commit 07098b3d97
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 3060 additions and 31 deletions

View File

@ -157,6 +157,8 @@ and are generated based on the last package release.
<LatestPackageReference Include="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="$(MicrosoftAspNetCoreApiAuthorizationIdentityServerPackageVersion)" />
<LatestPackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="$(MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion)" />
<LatestPackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="$(MicrosoftAspNetCoreIdentityUIPackageVersion)" />
<LatestPackageReference Include="Microsoft.AspNetCore.Authentication.AzureADB2C.UI" Version="$(MicrosoftAspNetCoreAuthenticationAzureADB2CUIPackageVersion)" />
<LatestPackageReference Include="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="$(MicrosoftAspNetCoreAuthenticationAzureADUIPackageVersion)" />
<LatestPackageReference Include="Microsoft.AspNetCore.Server.IntegrationTesting" Version="$(MicrosoftAspNetCoreServerIntegrationTestingPackageVersion)" />
</ItemGroup>

View File

@ -206,6 +206,8 @@
<SystemThreadingTasksExtensionsPackageVersion>4.5.2</SystemThreadingTasksExtensionsPackageVersion>
<!-- Packages developed by @aspnet, but manually updated as necessary. -->
<LibuvPackageVersion>1.10.0</LibuvPackageVersion>
<MicrosoftAspNetCoreAuthenticationAzureADUIPackageVersion>3.1.0</MicrosoftAspNetCoreAuthenticationAzureADUIPackageVersion>
<MicrosoftAspNetCoreAuthenticationAzureADB2CUIPackageVersion>3.1.0</MicrosoftAspNetCoreAuthenticationAzureADB2CUIPackageVersion>
<MicrosoftAspNetCoreIdentityUIPackageVersion>3.1.0</MicrosoftAspNetCoreIdentityUIPackageVersion>
<MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion>3.1.0</MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion>
<MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion>3.1.0</MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion>
@ -281,6 +283,8 @@
<MicrosoftAspNetCoreApiAuthorizationIdentityServerPackageVersion>$(MicrosoftAspNetCoreApiAuthorizationIdentityServerPackageVersion)</MicrosoftAspNetCoreApiAuthorizationIdentityServerPackageVersion>
<MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion>$(MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion)</MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion>
<MicrosoftAspNetCoreIdentityUIPackageVersion>$(MicrosoftAspNetCoreIdentityUIPackageVersion)</MicrosoftAspNetCoreIdentityUIPackageVersion>
<MicrosoftAspNetCoreAuthenticationAzureADUIPackageVersion>$(MicrosoftAspNetCoreAuthenticationAzureADUIPackageVersion)</MicrosoftAspNetCoreAuthenticationAzureADUIPackageVersion>
<MicrosoftAspNetCoreAuthenticationAzureADB2CUIPackageVersion>$(MicrosoftAspNetCoreAuthenticationAzureADB2CUIPackageVersion)</MicrosoftAspNetCoreAuthenticationAzureADB2CUIPackageVersion>
</PropertyGroup>
<!-- Restore feeds -->
<PropertyGroup Label="Restore feeds">

View File

@ -1,6 +1,6 @@
@inject NavigationManager Navigation
@using Microsoft.Extensions.Options
@inject IOptions<RemoteAuthenticationOptions<OidcProviderOptions>> Options
@inject IOptions<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>> Options
@code {
protected override void OnInitialized()

View File

@ -11,6 +11,8 @@
<PackageReference Include="Microsoft.AspNetCore.Blazor.Build" Version="${MicrosoftAspNetCoreBlazorBuildPackageVersion}" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.DevServer" Version="${MicrosoftAspNetCoreBlazorDevServerPackageVersion}" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.HttpClient" Version="${MicrosoftAspNetCoreBlazorHttpClientPackageVersion}" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="${MicrosoftAspNetCoreComponentsWebAssemblyAuthenticationPackageVersion}" Condition="'$(IndividualLocalAuth)' == 'true'" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="${MicrosoftAspNetCoreComponentsWebAssemblyAuthenticationPackageVersion}" Condition="'$(OrganizationalAuth)' == 'true' OR '$(IndividualB2CAuth)' == 'true'" />
</ItemGroup>
<!--#if Hosted -->

View File

@ -3,6 +3,11 @@
<PropertyGroup>
<TargetFramework>${DefaultNetCoreTargetFramework}</TargetFramework>
<DisableImplicitComponentsAnalyzers>true</DisableImplicitComponentsAnalyzers>
<UserSecretsId Condition="'$(IndividualAuth)' == 'True' OR '$(OrganizationalAuth)' == 'True'">BlazorWasm-CSharp.Server-53bc9b9d-9d6a-45d4-8429-2a2761773502</UserSecretsId>
<WebProject_DirectoryAccessLevelKey Condition="'$(OrganizationalAuth)' == 'True' AND '$(OrgReadAccess)' != 'True'">0</WebProject_DirectoryAccessLevelKey>
<WebProject_DirectoryAccessLevelKey Condition="'$(OrganizationalAuth)' == 'True' AND '$(OrgReadAccess)' == 'True'">1</WebProject_DirectoryAccessLevelKey>
<NoDefaultLaunchSettingsFile Condition="'$(ExcludeLaunchSettings)' == 'True'">True</NoDefaultLaunchSettingsFile>
<RootNamespace Condition="'$(name)' != '$(name{-VALUE-FORMS-}safe_namespace)'">BlazorWasm-CSharp.Server</RootNamespace>
</PropertyGroup>
<ItemGroup>
@ -14,4 +19,28 @@
<ProjectReference Include="..\Shared\BlazorWasm-CSharp.Shared.csproj" />
</ItemGroup>
<!--#if (IndividualLocalAuth && !UseLocalDB) -->
<ItemGroup>
<None Update="app.db" CopyToOutputDirectory="PreserveNewest" ExcludeFromSingleFile="true" />
</ItemGroup>
<!--#endif -->
<!--#if (IndividualLocalAuth) -->
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="${MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion}" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="${MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion}" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="${MicrosoftAspNetCoreIdentityUIPackageVersion}" />
<PackageReference Include="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="${MicrosoftAspNetCoreApiAuthorizationIdentityServerPackageVersion}" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="${MicrosoftEntityFrameworkCoreSqlServerPackageVersion}" Condition="'$(UseLocalDB)' == 'True'" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="${MicrosoftEntityFrameworkCoreSqlitePackageVersion}" Condition="'$(UseLocalDB)' != 'True'" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="${MicrosoftEntityFrameworkCoreToolsPackageVersion}" />
</ItemGroup>
<!--#endif -->
<!--#if (OrganizationalAuth || IndividualB2CAuth) -->
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="${MicrosoftAspNetCoreAuthenticationAzureADUIPackageVersion}" Condition="'$(OrganizationalAuth)' == 'True'" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.AzureADB2C.UI" Version="${MicrosoftAspNetCoreAuthenticationAzureADB2CUIPackageVersion}" Condition="'$(IndividualB2CAuth)' == 'True'" />
</ItemGroup>
<!--#endif -->
</Project>

View File

@ -18,20 +18,31 @@
MicrosoftAspNetCoreBlazorBuildPackageVersion=$(MicrosoftAspNetCoreBlazorBuildPackageVersion);
MicrosoftAspNetCoreBlazorDevServerPackageVersion=$(MicrosoftAspNetCoreBlazorDevServerPackageVersion);
MicrosoftAspNetCoreBlazorHttpClientPackageVersion=$(MicrosoftAspNetCoreBlazorHttpClientPackageVersion);
MonoWebAssemblyInteropPackageVersion=$(MonoWebAssemblyInteropPackageVersion);
MicrosoftEntityFrameworkCoreSqlServerPackageVersion=$(MicrosoftEntityFrameworkCoreSqlServerPackageVersion);
MicrosoftAspNetCoreBlazorServerPackageVersion=$(MicrosoftAspNetCoreBlazorServerPackageVersion);
MicrosoftAspNetCoreComponentsAuthorizationPackageVersion=$(MicrosoftAspNetCoreComponentsAuthorizationPackageVersion);
MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion=$(MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion);
MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion=$(MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion);
MicrosoftAspNetCoreAuthenticationAzureADUIPackageVersion=$(MicrosoftAspNetCoreAuthenticationAzureADUIPackageVersion);
MicrosoftAspNetCoreAuthenticationAzureADB2CUIPackageVersion=$(MicrosoftAspNetCoreAuthenticationAzureADB2CUIPackageVersion);
MicrosoftAspNetCoreIdentityUIPackageVersion=$(MicrosoftAspNetCoreIdentityUIPackageVersion);
MicrosoftAspNetCoreApiAuthorizationIdentityServerPackageVersion=$(MicrosoftAspNetCoreApiAuthorizationIdentityServerPackageVersion);
MicrosoftEntityFrameworkCoreSqlServerPackageVersion=$(MicrosoftEntityFrameworkCoreSqlServerPackageVersion);
MicrosoftEntityFrameworkCoreSqlitePackageVersion=$(MicrosoftEntityFrameworkCoreSqlitePackageVersion);
MicrosoftEntityFrameworkCoreToolsPackageVersion=$(MicrosoftEntityFrameworkCoreToolsPackageVersion);
MonoWebAssemblyInteropPackageVersion=$(MonoWebAssemblyInteropPackageVersion);
</GeneratedContentProperties>
</PropertyGroup>
<ItemGroup>
<!-- These projects product packages that the templates depend on. See GenerateContent.targets -->
<PackageVersionVariableReference Include="$(BlazorProjectsRoot)Blazor\src\Microsoft.AspNetCore.Blazor.csproj" />
<PackageVersionVariableReference Include="$(BlazorProjectsRoot)Blazor\src\Microsoft.AspNetCore.Blazor.csproj" />
<PackageVersionVariableReference Include="$(BlazorProjectsRoot)Build\src\Microsoft.AspNetCore.Blazor.Build.csproj" />
<PackageVersionVariableReference Include="$(BlazorProjectsRoot)DevServer\src\Microsoft.AspNetCore.Blazor.DevServer.csproj" />
<PackageVersionVariableReference Include="$(BlazorProjectsRoot)Http\src\Microsoft.AspNetCore.Blazor.HttpClient.csproj" />
<PackageVersionVariableReference Include="$(BlazorProjectsRoot)Mono.WebAssembly.Interop\src\Mono.WebAssembly.Interop.csproj" />
<PackageVersionVariableReference Include="$(BlazorProjectsRoot)Server\src\Microsoft.AspNetCore.Blazor.Server.csproj" />
<PackageVersionVariableReference Include="$(BlazorProjectsRoot)WebAssembly.Authentication\src\Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -13,6 +13,78 @@
},
"Framework": {
"longName": "framework"
},
"UseLocalDB": {
"longName": "use-local-db"
},
"AADInstance": {
"longName": "aad-instance",
"shortName": ""
},
"AAdB2CInstance": {
"longName": "aad-b2c-instance",
"shortName": "",
"isHidden": true
},
"SignUpSignInPolicyId": {
"longName": "susi-policy-id",
"shortName": "ssp",
"isHidden": true
},
"OrgReadAccess": {
"longName": "org-read-access",
"shortName": "r",
"isHidden": true
},
"ClientId": {
"longName": "client-id",
"shortName": ""
},
"AppIDUri": {
"longName": "app-id-uri",
"shortName": "",
"isHidden": true
},
"APIClientId": {
"longName": "api-client-id",
"shortName": "",
"isHidden": true
},
"Domain": {
"longName": "domain",
"shortName": "",
"isHidden": true
},
"TenantId": {
"longName": "tenant-id",
"shortName": "",
"isHidden": true
},
"DefaultScope": {
"longName": "default-scope",
"shortName": "s",
"isHidden": true
},
"Authority": {
"longName": "authority",
"shortName": ""
},
"HttpPort": {
"isHidden": true
},
"HttpsPort": {
"isHidden": true
},
"ExcludeLaunchSettings": {
"longName": "exclude-launch-settings",
"shortName": ""
},
"UserSecretsId": {
"isHidden": true
},
"NoHttps": {
"longName": "no-https",
"shortName": ""
}
}
}
}

View File

@ -15,7 +15,9 @@
"4C26868E-5E7C-458D-82E3-040509D0C71F",
"5990939C-7E7B-4CFA-86FF-44CA5756498A",
"650B3CE7-2E93-4CC4-9F46-466686815EAA",
"0AFFA7FD-4E37-4636-AB91-3753E746DB98"
"0AFFA7FD-4E37-4636-AB91-3753E746DB98",
"09732173-2cef-46b7-83db-1334bcb079d3", // Tenant ID
"53bc9b9d-9d6a-45d4-8429-2a2761773502" // Client ID
],
"identity": "Microsoft.Web.Blazor.Wasm.CSharp",
"preferNameDirectory": true,
@ -82,6 +84,125 @@
"Client/wwwroot/manifest.json",
"Client/wwwroot/icon-512.png"
]
},
{
"condition": "(!IndividualLocalAuth || UseLocalDB)",
"exclude": [
"Server/app.db"
]
},
{
"condition": "(!IndividualLocalAuth)",
"exclude": [
"Server/Data/SqlLite/**",
"Server/Data/SqlServer/**",
"Server/Data/ApplicationDbContext.cs",
"Server/Areas/**"
]
},
{
"condition": "(IndividualLocalAuth && UseLocalDB)",
"rename": {
"Server/Data/SqlServer/": "Server/Data/Migrations/"
},
"exclude": [
"Server/Data/SqlLite/**"
]
},
{
"condition": "(IndividualLocalAuth && !UseLocalDB)",
"rename": {
"Server/Data/SqlLite/": "Server/Data/Migrations/"
},
"exclude": [
"Server/Data/SqlServer/**"
]
},
{
"condition": "(Hosted && NoAuth)",
"rename": {
"Client/Shared/MainLayout.NoAuth.razor": "Client/Shared/MainLayout.razor"
},
"exclude": [
"Client/Pages/Authentication.razor",
"Client/Shared/LoginDisplay.*.razor",
"Client/Shared/MainLayout.Auth.razor",
"Client/Shared/RedirectToLogin.razor"
]
},
{
"condition": "(Hosted && !NoAuth)",
"rename": {
"Client/Shared/MainLayout.Auth.razor": "Client/Shared/MainLayout.razor"
},
"exclude": [
"Client/Shared/MainLayout.NoAuth.razor"
]
},
{
"condition": "(Hosted && IndividualLocalAuth)",
"rename": {
"Client/Shared/LoginDisplay.IndividualLocalAuth.razor": "Client/Shared/LoginDisplay.razor"
},
"exclude": [
"Client/Shared/LoginDisplay.IndividualMsalAuth.razor"
]
},
{
"condition": "(Hosted && (IndividualB2CAuth || OrganizationalAuth))",
"rename": {
"Client/Shared/LoginDisplay.IndividualMsalAuth.razor": "Client/Shared/LoginDisplay.razor"
},
"exclude": [
"Client/Shared/LoginDisplay.IndividualLocalAuth.razor"
]
},
{
"condition": "(!Hosted && NoAuth)",
"rename": {
"Client/Shared/MainLayout.NoAuth.razor": "Shared/MainLayout.razor"
},
"exclude": [
"Client/Pages/Authentication.razor",
"Client/Shared/LoginDisplay.*.razor",
"Client/Shared/MainLayout.Auth.razor",
"Client/Shared/RedirectToLogin.razor"
]
},
{
"condition": "(!Hosted && !NoAuth)",
"rename": {
"Client/Shared/MainLayout.Auth.razor": "Shared/MainLayout.razor"
},
"exclude": [
"Client/Shared/MainLayout.NoAuth.razor"
]
},
{
"condition": "(!Hosted && IndividualLocalAuth)",
"rename": {
"Client/Shared/LoginDisplay.IndividualLocalAuth.razor": "Shared/LoginDisplay.razor"
},
"exclude": [
"Client/Shared/LoginDisplay.IndividualMsalAuth.razor"
]
},
{
"condition": "(!Hosted && (IndividualB2CAuth || OrganizationalAuth))",
"rename": {
"Client/Shared/LoginDisplay.IndividualMsalAuth.razor": "Shared/LoginDisplay.razor"
},
"exclude": [
"Client/Shared/LoginDisplay.IndividualLocalAuth.razor"
]
},
{
"condition": "(!IndividualLocalAuth || !Hosted)",
"exclude": [
"Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml",
"Server/Controllers/OidcConfigurationController.cs",
"Server/Models/ApplicationUser.cs"
]
}
]
}
@ -116,6 +237,112 @@
"defaultValue": "false",
"description": "If specified, includes an ASP.NET Core host for the Blazor app."
},
"auth": {
"type": "parameter",
"datatype": "choice",
"choices": [
{
"choice": "None",
"description": "No authentication"
},
{
"choice": "Individual",
"description": "Individual authentication"
},
{
"choice": "IndividualB2C",
"description": "Individual authentication with Azure AD B2C"
},
{
"choice": "SingleOrg",
"description": "Organizational authentication for a single tenant"
}
],
"defaultValue": "None",
"description": "The type of authentication to use"
},
"Authority": {
"type": "parameter",
"datatype": "string",
"replaces": "https://login.microsoftonline.com/",
"description": "The authority of the OIDC provider (use with standalone Individual auth)."
},
"AAdB2CInstance": {
"type": "parameter",
"datatype": "string",
"replaces": "https:////aadB2CInstance.b2clogin.com/",
"description": "The Azure Active Directory B2C instance to connect to (use with IndividualB2C auth)."
},
"SignUpSignInPolicyId": {
"type": "parameter",
"datatype": "string",
"defaultValue": "",
"replaces": "MySignUpSignInPolicyId",
"description": "The sign-in and sign-up policy ID for this project (use with IndividualB2C auth)."
},
"AADInstance": {
"type": "parameter",
"datatype": "string",
"defaultValue": "https://login.microsoftonline.com/",
"replaces": "https:////login.microsoftonline.com/",
"description": "The Azure Active Directory instance to connect to (use with SingleOrg)."
},
"ClientId": {
"type": "parameter",
"datatype": "string",
"replaces": "33333333-3333-3333-33333333333333333",
"description": "The Client ID for this project (use with IndividualB2C, SingleOrg or Individual auth in standalone scenarios)."
},
"Domain": {
"type": "parameter",
"datatype": "string",
"replaces": "qualified.domain.name",
"description": "The domain for the directory tenant (use with SingleOrg or IndividualB2C auth)."
},
"AppIDUri": {
"type": "parameter",
"datatype": "string",
"replaces": "api.id.uri",
"description": "The App ID Uri for the server API we want to call (use with SingleOrg or IndividualB2C auth)."
},
"APIClientId": {
"type": "parameter",
"datatype": "string",
"replaces": "11111111-1111-1111-11111111111111111",
"description": "The Client ID for the API that the server hosts (use with IndividualB2C, SingleOrg)."
},
"DefaultScope": {
"type": "parameter",
"datatype": "string",
"replaces": "api-scope",
"defaultValue": "user_impersonation",
"description": "The API scope the client needs to request to provision an access token. (use with IndividualB2C, SingleOrg)."
},
"TenantId": {
"type": "parameter",
"datatype": "string",
"replaces": "22222222-2222-2222-2222-222222222222",
"description": "The TenantId ID of the directory to connect to (use with SingleOrg auth)."
},
"OrgReadAccess": {
"type": "parameter",
"datatype": "bool",
"defaultValue": "false",
"description": "Whether or not to allow this application read access to the directory (only applies to SingleOrg)."
},
"UserSecretsId": {
"type": "parameter",
"datatype": "string",
"replaces": "aspnet-BlazorServerWeb-CSharp-53bc9b9d-9d6a-45d4-8429-2a2761773502",
"defaultValue": "aspnet-BlazorServerWeb-CSharp-53bc9b9d-9d6a-45d4-8429-2a2761773502",
"description": "The ID to use for secrets (use with OrgReadAccess or Individual auth)."
},
"ExcludeLaunchSettings": {
"type": "parameter",
"datatype": "bool",
"defaultValue": "false",
"description": "Whether to exclude launchSettings.json from the generated template."
},
"HttpPort": {
"type": "parameter",
"datatype": "integer",
@ -161,6 +388,58 @@
"datatype": "bool",
"defaultValue": "false",
"description": "If specified, produces a Progressive Web Application (PWA) supporting installation and offline use."
},
"OrganizationalAuth": {
"type": "computed",
"value": "(auth == \"SingleOrg\" || auth == \"MultiOrg\")"
},
"MultiOrgAuth": {
"type": "computed",
"value": "(auth == \"MultiOrg\")"
},
"SingleOrgAuth": {
"type": "computed",
"value": "(auth == \"SingleOrg\")"
},
"IndividualLocalAuth": {
"type": "computed",
"value": "(auth == \"Individual\")"
},
"IndividualAuth": {
"type": "computed",
"value": "(auth == \"Individual\" || auth == \"IndividualB2C\")"
},
"IndividualB2CAuth": {
"type": "computed",
"value": "(auth == \"IndividualB2C\")"
},
"NoAuth": {
"type": "computed",
"value": "(!(IndividualAuth || OrganizationalAuth))"
},
"RequiresHttps": {
"type": "computed",
"value": "(OrganizationalAuth || IndividualAuth || !NoHttps)"
},
"NoHttps": {
"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."
},
"UseLocalDB": {
"type": "parameter",
"datatype": "bool",
"defaultValue": "false",
"description": "Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual or --auth IndividualB2C is specified."
},
"copyrightYear": {
"type": "generated",
"generator": "now",
"replaces": "copyrightYear",
"parameters": {
"format": "yyyy"
}
}
},
"tags": {

View File

@ -18,6 +18,30 @@
"additionalWizardParameters": {
"$isMultiProjectTemplate$": "true"
},
"supportedAuthentications": [
{
"auth": "None",
"authenticationType": "NoAuth",
"allowUnsecured": true
},
{
"auth": "Individual",
"authenticationType": "IndividualAuth",
"b2cAuthenticationOptions": "Local"
}
],
"ports": [
{
"name": "HttpPort",
"useHttps": false
},
{
"name": "HttpsPort",
"useHttps": true
}
],
"excludeLaunchSettings": false,
"disableHttpsSymbol": "NoHttps",
"symbolInfo": [
{
"id": "Hosted",

View File

@ -1,4 +1,5 @@
<Router AppAssembly="@typeof(Program).Assembly">
@*#if (NoAuth)
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
@ -8,3 +9,21 @@
</LayoutView>
</NotFound>
</Router>
#else
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
<RedirectToLogin />
</NotAuthorized>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
#endif*@

View File

@ -0,0 +1,7 @@
@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="@Action" />
@code{
[Parameter] public string Action { get; set; }
}

View File

@ -1,8 +1,19 @@
@page "/fetchdata"
@*#if (!NoAuth && Hosted)
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider AuthenticationService
@inject NavigationManager Navigation
#endif*@
@*#if (Hosted)
@using BlazorWasm_CSharp.Shared
#endif*@
@*#if (!NoAuth && Hosted)
@attribute [Authorize]
#endif*@
@*#if (NoAuth || !Hosted)
@inject HttpClient Http
#endif*@
<h1>Weather forecast</h1>
@ -43,7 +54,25 @@ else
protected override async Task OnInitializedAsync()
{
@*#if (Hosted)
@*#if (!NoAuth)
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(Navigation.BaseUri);
var tokenResult = await AuthenticationService.RequestAccessToken();
if (tokenResult.TryGetToken(out var token))
{
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.Value}");
forecasts = await httpClient.GetJsonAsync<WeatherForecast[]>("WeatherForecast");
}
else
{
Navigation.NavigateTo(tokenResult.RedirectUrl);
}
#else
forecasts = await Http.GetJsonAsync<WeatherForecast[]>("WeatherForecast");
#endif*@
#else
forecasts = await Http.GetJsonAsync<WeatherForecast[]>("sample-data/weather.json");
#endif*@

View File

@ -18,6 +18,42 @@ namespace BlazorWasm_CSharp
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
// use builder.Services to configure application services.
#if (IndividualLocalAuth)
#if (Hosted)
builder.Services.AddApiAuthorization();
#else
builder.Services.AddOidcAuthentication(options =>
{
options.ProviderOptions.Authority = "https://login.microsoftonline.com/";
options.ProviderOptions.ClientId = "33333333-3333-3333-33333333333333333";
});
#endif
#endif
#if (IndividualB2CAuth)
builder.Services.AddMsalSpaAuthentication(options =>
{
var authentication = options.ProviderOptions.Authentication;
authentication.Authority = "https:////aadB2CInstance.b2clogin.com/qualified.domain.name/MySignUpSignInPolicyId";
authentication.ClientId = "33333333-3333-3333-33333333333333333";
authentication.ValidateAuthority = false;
#if (Hosted)
options.ProviderOptions.DefaultAccessTokenScopes.Add("https://qualified.domain.name/api.id.uri/api-scope");
#endif
});
#endif
#if(OrganizationalAuth)
builder.Services.AddMsalSpaAuthentication(options =>
{
var authentication = options.ProviderOptions.Authentication;
authentication.Authority = "https://login.microsoftonline.com/22222222-2222-2222-2222-222222222222";
authentication.ClientId = "33333333-3333-3333-33333333333333333";
#if (Hosted)
options.ProviderOptions.DefaultAccessTokenScopes.Add("api://api.id.uri/api-scope");
#endif
});
#endif
await builder.Build().RunAsync();
}
}

View File

@ -0,0 +1,34 @@
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject NavigationManager Navigation
@inject SignOutSessionStateManager SignOutManager
<AuthorizeView>
@*#if (Hosted)
<Authorized>
<a href="authentication/profile">Hello, @context.User.Identity.Name!</a>
<button class="nav-link btn btn-link" @onclick="BeginSignOut">Log out</button>
</Authorized>
<NotAuthorized>
<a href="authentication/register">Register</a>
<a href="authentication/login">Log in</a>
</NotAuthorized>
#else
<Authorized>
Hello, @context.User.Identity.Name!
<button class="nav-link btn btn-link" @onclick="BeginSignOut">Log out</button>
</Authorized>
<NotAuthorized>
<a href="authentication/login">Log in</a>
</NotAuthorized>
##endif*@
</AuthorizeView>
@code{
private async Task BeginSignOut(MouseEventArgs args)
{
await SignOutManager.SetSignOutState();
Navigation.NavigateTo("authentication/logout");
}
}

View File

@ -0,0 +1,23 @@
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject NavigationManager Navigation
@inject SignOutSessionStateManager SignOutManager
<AuthorizeView>
<Authorized>
Hello, @context.User.Identity.Name!
<button class="nav-link btn btn-link" @onclick="BeginLogout">Log out</button>
</Authorized>
<NotAuthorized>
<a href="authentication/login">Log in</a>
</NotAuthorized>
</AuthorizeView>
@code{
private async Task BeginLogout(MouseEventArgs args)
{
await SignOutManager.SetSignOutState();
Navigation.NavigateTo("authentication/logout");
}
}

View File

@ -0,0 +1,16 @@
@inherits LayoutComponentBase
<div class="sidebar">
<NavMenu />
</div>
<div class="main">
<div class="top-row px-4 auth">
<LoginDisplay />
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>
<div class="content px-4">
@Body
</div>
</div>

View File

@ -0,0 +1,8 @@
@inject NavigationManager Navigation
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@code {
protected override void OnInitialized()
{
Navigation.NavigateTo($"authentication/login?returnUrl={Navigation.Uri}");
}
}

View File

@ -1,4 +1,7 @@
@using System.Net.Http
@*#if (!NoAuth)
@using Microsoft.AspNetCore.Components.Authorization
#endif*@
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -21,6 +21,12 @@
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<!--#if (IndividualLocalAuth) -->
<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>
<!--#endif -->
<!--#if (IndividualB2CAuth || OrganizationalAuth) -->
<script src="_content/Blazor.Msal/AuthenticationService.js"></script>
<!--#endif -->
<script src="_framework/blazor.webassembly.js"></script>
<!--#if PWA -->
<script>navigator.serviceWorker.register('service-worker.js');</script>

View File

@ -0,0 +1,35 @@
@using Microsoft.AspNetCore.Identity
@using BlazorWasm_CSharp.Server.Models
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
var returnUrl = "/";
if (Context.Request.Query.TryGetValue("returnUrl", out var existingUrl)) {
returnUrl = existingUrl;
}
}
<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity.Name!</a>
</li>
<li class="nav-item">
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="/" method="post">
<button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
</form>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register" asp-route-returnUrl="@returnUrl">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login" asp-route-returnUrl="@returnUrl">Login</a>
</li>
}
</ul>

View File

@ -0,0 +1,26 @@
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace BlazorWasm_CSharp.Server.Controllers
{
public class OidcConfigurationController : Controller
{
private readonly ILogger<OidcConfigurationController> _logger;
public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider, ILogger<OidcConfigurationController> logger)
{
ClientRequestParametersProvider = clientRequestParametersProvider;
_logger = logger;
}
public IClientRequestParametersProvider ClientRequestParametersProvider { get; }
[HttpGet("_configuration/{clientId}")]
public IActionResult GetClientRequestParameters([FromRoute]string clientId)
{
var parameters = ClientRequestParametersProvider.GetClientParameters(HttpContext, clientId);
return Ok(parameters);
}
}
}

View File

@ -3,11 +3,17 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
#if (!NoAuth)
using Microsoft.AspNetCore.Authorization;
#endif
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace BlazorWasm_CSharp.Server.Controllers
{
#if (!NoAuth)
[Authorize]
#endif
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase

View File

@ -0,0 +1,21 @@
using BlazorWasm_CSharp.Server.Models;
using IdentityServer4.EntityFramework.Options;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BlazorWasm_CSharp.Server.Data
{
public class ApplicationDbContext : ApiAuthorizationDbContext<ApplicationUser>
{
public ApplicationDbContext(
DbContextOptions options,
IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options, operationalStoreOptions)
{
}
}
}

View File

@ -0,0 +1,352 @@
// <auto-generated />
using System;
using BlazorWasm_CSharp.Server.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace BlazorWasm_CSharp.Server.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("00000000000000_CreateIdentitySchema")]
partial class CreateIdentitySchema
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.0.0-rc1.19455.8");
modelBuilder.Entity("BlazorWasm_CSharp.Server.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<string>("NormalizedEmail")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
b.Property<string>("UserName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
{
b.Property<string>("UserCode")
.HasColumnType("TEXT")
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(200);
b.Property<DateTime>("CreationTime")
.HasColumnType("TEXT");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(50000);
b.Property<string>("DeviceCode")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(200);
b.Property<DateTime?>("Expiration")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("SubjectId")
.HasColumnType("TEXT")
.HasMaxLength(200);
b.HasKey("UserCode");
b.HasIndex("DeviceCode")
.IsUnique();
b.HasIndex("Expiration");
b.ToTable("DeviceCodes");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT")
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(200);
b.Property<DateTime>("CreationTime")
.HasColumnType("TEXT");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(50000);
b.Property<DateTime?>("Expiration")
.HasColumnType("TEXT");
b.Property<string>("SubjectId")
.HasColumnType("TEXT")
.HasMaxLength(200);
b.Property<string>("Type")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(50);
b.HasKey("Key");
b.HasIndex("Expiration");
b.HasIndex("SubjectId", "ClientId", "Type");
b.ToTable("PersistedGrants");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("TEXT")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasColumnType("TEXT")
.HasMaxLength(128);
b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.HasColumnType("TEXT");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("LoginProvider")
.HasColumnType("TEXT")
.HasMaxLength(128);
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasMaxLength(128);
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("BlazorWasm_CSharp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("BlazorWasm_CSharp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("BlazorWasm_CSharp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("BlazorWasm_CSharp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,278 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace BlazorWasm_CSharp.Server.Data.Migrations
{
public partial class CreateIdentitySchema : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AspNetRoles",
columns: table => new
{
Id = table.Column<string>(nullable: false),
Name = table.Column<string>(maxLength: 256, nullable: true),
NormalizedName = table.Column<string>(maxLength: 256, nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetUsers",
columns: table => new
{
Id = table.Column<string>(nullable: false),
UserName = table.Column<string>(maxLength: 256, nullable: true),
NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true),
Email = table.Column<string>(maxLength: 256, nullable: true),
NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),
EmailConfirmed = table.Column<bool>(nullable: false),
PasswordHash = table.Column<string>(nullable: true),
SecurityStamp = table.Column<string>(nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true),
PhoneNumber = table.Column<string>(nullable: true),
PhoneNumberConfirmed = table.Column<bool>(nullable: false),
TwoFactorEnabled = table.Column<bool>(nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(nullable: true),
LockoutEnabled = table.Column<bool>(nullable: false),
AccessFailedCount = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
});
migrationBuilder.CreateTable(
name: "DeviceCodes",
columns: table => new
{
UserCode = table.Column<string>(maxLength: 200, nullable: false),
DeviceCode = table.Column<string>(maxLength: 200, nullable: false),
SubjectId = table.Column<string>(maxLength: 200, nullable: true),
ClientId = table.Column<string>(maxLength: 200, nullable: false),
CreationTime = table.Column<DateTime>(nullable: false),
Expiration = table.Column<DateTime>(nullable: false),
Data = table.Column<string>(maxLength: 50000, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DeviceCodes", x => x.UserCode);
});
migrationBuilder.CreateTable(
name: "PersistedGrants",
columns: table => new
{
Key = table.Column<string>(maxLength: 200, nullable: false),
Type = table.Column<string>(maxLength: 50, nullable: false),
SubjectId = table.Column<string>(maxLength: 200, nullable: true),
ClientId = table.Column<string>(maxLength: 200, nullable: false),
CreationTime = table.Column<DateTime>(nullable: false),
Expiration = table.Column<DateTime>(nullable: true),
Data = table.Column<string>(maxLength: 50000, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PersistedGrants", x => x.Key);
});
migrationBuilder.CreateTable(
name: "AspNetRoleClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
RoleId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(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<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
UserId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(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<string>(maxLength: 128, nullable: false),
ProviderKey = table.Column<string>(maxLength: 128, nullable: false),
ProviderDisplayName = table.Column<string>(nullable: true),
UserId = table.Column<string>(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<string>(nullable: false),
RoleId = table.Column<string>(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<string>(nullable: false),
LoginProvider = table.Column<string>(maxLength: 128, nullable: false),
Name = table.Column<string>(maxLength: 128, nullable: false),
Value = table.Column<string>(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);
migrationBuilder.CreateIndex(
name: "IX_DeviceCodes_DeviceCode",
table: "DeviceCodes",
column: "DeviceCode",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_DeviceCodes_Expiration",
table: "DeviceCodes",
column: "Expiration");
migrationBuilder.CreateIndex(
name: "IX_PersistedGrants_Expiration",
table: "PersistedGrants",
column: "Expiration");
migrationBuilder.CreateIndex(
name: "IX_PersistedGrants_SubjectId_ClientId_Type",
table: "PersistedGrants",
columns: new[] { "SubjectId", "ClientId", "Type" });
}
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: "DeviceCodes");
migrationBuilder.DropTable(
name: "PersistedGrants");
migrationBuilder.DropTable(
name: "AspNetRoles");
migrationBuilder.DropTable(
name: "AspNetUsers");
}
}
}

View File

@ -0,0 +1,350 @@
// <auto-generated />
using System;
using BlazorWasm_CSharp.Server.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace BlazorWasm_CSharp.Server.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.0.0-rc1.19455.8");
modelBuilder.Entity("BlazorWasm_CSharp.Server.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<string>("NormalizedEmail")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
b.Property<string>("UserName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
{
b.Property<string>("UserCode")
.HasColumnType("TEXT")
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(200);
b.Property<DateTime>("CreationTime")
.HasColumnType("TEXT");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(50000);
b.Property<string>("DeviceCode")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(200);
b.Property<DateTime?>("Expiration")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("SubjectId")
.HasColumnType("TEXT")
.HasMaxLength(200);
b.HasKey("UserCode");
b.HasIndex("DeviceCode")
.IsUnique();
b.HasIndex("Expiration");
b.ToTable("DeviceCodes");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT")
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(200);
b.Property<DateTime>("CreationTime")
.HasColumnType("TEXT");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(50000);
b.Property<DateTime?>("Expiration")
.HasColumnType("TEXT");
b.Property<string>("SubjectId")
.HasColumnType("TEXT")
.HasMaxLength(200);
b.Property<string>("Type")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(50);
b.HasKey("Key");
b.HasIndex("Expiration");
b.HasIndex("SubjectId", "ClientId", "Type");
b.ToTable("PersistedGrants");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("TEXT")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasColumnType("TEXT")
.HasMaxLength(128);
b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.HasColumnType("TEXT");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("LoginProvider")
.HasColumnType("TEXT")
.HasMaxLength(128);
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasMaxLength(128);
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("BlazorWasm_CSharp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("BlazorWasm_CSharp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("BlazorWasm_CSharp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("BlazorWasm_CSharp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,359 @@
// <auto-generated />
using System;
using BlazorWasm_CSharp.Server.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace BlazorWasm_CSharp.Server.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("00000000000000_CreateIdentitySchema")]
partial class CreateIdentitySchema
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.0.0-rc1.19455.8")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("BlazorWasm_CSharp.Server.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasColumnType("nvarchar(256)")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasColumnType("nvarchar(256)")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasColumnType("nvarchar(256)")
.HasMaxLength(256);
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("UserName")
.HasColumnType("nvarchar(256)")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
{
b.Property<string>("UserCode")
.HasColumnType("nvarchar(200)")
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasColumnType("nvarchar(200)")
.HasMaxLength(200);
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasMaxLength(50000);
b.Property<string>("DeviceCode")
.IsRequired()
.HasColumnType("nvarchar(200)")
.HasMaxLength(200);
b.Property<DateTime?>("Expiration")
.IsRequired()
.HasColumnType("datetime2");
b.Property<string>("SubjectId")
.HasColumnType("nvarchar(200)")
.HasMaxLength(200);
b.HasKey("UserCode");
b.HasIndex("DeviceCode")
.IsUnique();
b.HasIndex("Expiration");
b.ToTable("DeviceCodes");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
{
b.Property<string>("Key")
.HasColumnType("nvarchar(200)")
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasColumnType("nvarchar(200)")
.HasMaxLength(200);
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasMaxLength(50000);
b.Property<DateTime?>("Expiration")
.HasColumnType("datetime2");
b.Property<string>("SubjectId")
.HasColumnType("nvarchar(200)")
.HasMaxLength(200);
b.Property<string>("Type")
.IsRequired()
.HasColumnType("nvarchar(50)")
.HasMaxLength(50);
b.HasKey("Key");
b.HasIndex("Expiration");
b.HasIndex("SubjectId", "ClientId", "Type");
b.ToTable("PersistedGrants");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasColumnType("nvarchar(256)")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasColumnType("nvarchar(256)")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(128)")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasColumnType("nvarchar(128)")
.HasMaxLength(128);
b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("RoleId")
.HasColumnType("nvarchar(450)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(128)")
.HasMaxLength(128);
b.Property<string>("Name")
.HasColumnType("nvarchar(128)")
.HasMaxLength(128);
b.Property<string>("Value")
.HasColumnType("nvarchar(max)");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("BlazorWasm_CSharp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("BlazorWasm_CSharp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("BlazorWasm_CSharp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("BlazorWasm_CSharp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,280 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace BlazorWasm_CSharp.Server.Data.Migrations
{
public partial class CreateIdentitySchema : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AspNetRoles",
columns: table => new
{
Id = table.Column<string>(nullable: false),
Name = table.Column<string>(maxLength: 256, nullable: true),
NormalizedName = table.Column<string>(maxLength: 256, nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetUsers",
columns: table => new
{
Id = table.Column<string>(nullable: false),
UserName = table.Column<string>(maxLength: 256, nullable: true),
NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true),
Email = table.Column<string>(maxLength: 256, nullable: true),
NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),
EmailConfirmed = table.Column<bool>(nullable: false),
PasswordHash = table.Column<string>(nullable: true),
SecurityStamp = table.Column<string>(nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true),
PhoneNumber = table.Column<string>(nullable: true),
PhoneNumberConfirmed = table.Column<bool>(nullable: false),
TwoFactorEnabled = table.Column<bool>(nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(nullable: true),
LockoutEnabled = table.Column<bool>(nullable: false),
AccessFailedCount = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
});
migrationBuilder.CreateTable(
name: "DeviceCodes",
columns: table => new
{
UserCode = table.Column<string>(maxLength: 200, nullable: false),
DeviceCode = table.Column<string>(maxLength: 200, nullable: false),
SubjectId = table.Column<string>(maxLength: 200, nullable: true),
ClientId = table.Column<string>(maxLength: 200, nullable: false),
CreationTime = table.Column<DateTime>(nullable: false),
Expiration = table.Column<DateTime>(nullable: false),
Data = table.Column<string>(maxLength: 50000, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DeviceCodes", x => x.UserCode);
});
migrationBuilder.CreateTable(
name: "PersistedGrants",
columns: table => new
{
Key = table.Column<string>(maxLength: 200, nullable: false),
Type = table.Column<string>(maxLength: 50, nullable: false),
SubjectId = table.Column<string>(maxLength: 200, nullable: true),
ClientId = table.Column<string>(maxLength: 200, nullable: false),
CreationTime = table.Column<DateTime>(nullable: false),
Expiration = table.Column<DateTime>(nullable: true),
Data = table.Column<string>(maxLength: 50000, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PersistedGrants", x => x.Key);
});
migrationBuilder.CreateTable(
name: "AspNetRoleClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
RoleId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(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<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
UserId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(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<string>(maxLength: 128, nullable: false),
ProviderKey = table.Column<string>(maxLength: 128, nullable: false),
ProviderDisplayName = table.Column<string>(nullable: true),
UserId = table.Column<string>(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<string>(nullable: false),
RoleId = table.Column<string>(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<string>(nullable: false),
LoginProvider = table.Column<string>(maxLength: 128, nullable: false),
Name = table.Column<string>(maxLength: 128, nullable: false),
Value = table.Column<string>(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,
filter: "[NormalizedName] IS NOT NULL");
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,
filter: "[NormalizedUserName] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_DeviceCodes_DeviceCode",
table: "DeviceCodes",
column: "DeviceCode",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_DeviceCodes_Expiration",
table: "DeviceCodes",
column: "Expiration");
migrationBuilder.CreateIndex(
name: "IX_PersistedGrants_Expiration",
table: "PersistedGrants",
column: "Expiration");
migrationBuilder.CreateIndex(
name: "IX_PersistedGrants_SubjectId_ClientId_Type",
table: "PersistedGrants",
columns: new[] { "SubjectId", "ClientId", "Type" });
}
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: "DeviceCodes");
migrationBuilder.DropTable(
name: "PersistedGrants");
migrationBuilder.DropTable(
name: "AspNetRoles");
migrationBuilder.DropTable(
name: "AspNetUsers");
}
}
}

View File

@ -0,0 +1,357 @@
// <auto-generated />
using System;
using BlazorWasm_CSharp.Server.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace BlazorWasm_CSharp.Server.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.0.0-rc1.19455.8")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("BlazorWasm_CSharp.Server.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasColumnType("nvarchar(256)")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasColumnType("nvarchar(256)")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasColumnType("nvarchar(256)")
.HasMaxLength(256);
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("UserName")
.HasColumnType("nvarchar(256)")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
{
b.Property<string>("UserCode")
.HasColumnType("nvarchar(200)")
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasColumnType("nvarchar(200)")
.HasMaxLength(200);
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasMaxLength(50000);
b.Property<string>("DeviceCode")
.IsRequired()
.HasColumnType("nvarchar(200)")
.HasMaxLength(200);
b.Property<DateTime?>("Expiration")
.IsRequired()
.HasColumnType("datetime2");
b.Property<string>("SubjectId")
.HasColumnType("nvarchar(200)")
.HasMaxLength(200);
b.HasKey("UserCode");
b.HasIndex("DeviceCode")
.IsUnique();
b.HasIndex("Expiration");
b.ToTable("DeviceCodes");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
{
b.Property<string>("Key")
.HasColumnType("nvarchar(200)")
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasColumnType("nvarchar(200)")
.HasMaxLength(200);
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasMaxLength(50000);
b.Property<DateTime?>("Expiration")
.HasColumnType("datetime2");
b.Property<string>("SubjectId")
.HasColumnType("nvarchar(200)")
.HasMaxLength(200);
b.Property<string>("Type")
.IsRequired()
.HasColumnType("nvarchar(50)")
.HasMaxLength(50);
b.HasKey("Key");
b.HasIndex("Expiration");
b.HasIndex("SubjectId", "ClientId", "Type");
b.ToTable("PersistedGrants");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasColumnType("nvarchar(256)")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasColumnType("nvarchar(256)")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(128)")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasColumnType("nvarchar(128)")
.HasMaxLength(128);
b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("RoleId")
.HasColumnType("nvarchar(450)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(128)")
.HasMaxLength(128);
b.Property<string>("Name")
.HasColumnType("nvarchar(128)")
.HasMaxLength(128);
b.Property<string>("Value")
.HasColumnType("nvarchar(max)");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("BlazorWasm_CSharp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("BlazorWasm_CSharp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("BlazorWasm_CSharp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("BlazorWasm_CSharp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BlazorWasm_CSharp.Server.Models
{
public class ApplicationUser : IdentityUser
{
}
}

View File

@ -1,6 +1,11 @@
using Microsoft.AspNetCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace BlazorWasm_CSharp.Server
{
@ -8,15 +13,15 @@ namespace BlazorWasm_CSharp.Server
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
CreateHostBuilder(args).Build().Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseConfiguration(new ConfigurationBuilder()
.AddCommandLine(args)
.Build())
.UseStartup<Startup>()
.Build();
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}

View File

@ -1,19 +1,76 @@
#if (OrganizationalAuth || IndividualB2CAuth || IndividualLocalAuth)
using Microsoft.AspNetCore.Authentication;
#endif
using Microsoft.AspNetCore.Builder;
#if (IndividualLocalAuth)
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
#endif
#if (RequiresHttps)
using Microsoft.AspNetCore.HttpsPolicy;
#endif
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.ResponseCompression;
#if (IndividualLocalAuth)
using Microsoft.EntityFrameworkCore;
#endif
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Linq;
#if (IndividualLocalAuth)
using BlazorWasm_CSharp.Server.Data;
using BlazorWasm_CSharp.Server.Models;
#endif
namespace BlazorWasm_CSharp.Server
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
#if (IndividualLocalAuth)
services.AddDbContext<ApplicationDbContext>(options =>
#if (UseLocalDB)
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
#else
options.UseSqlite(
Configuration.GetConnectionString("DefaultConnection")));
#endif
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddAuthentication()
.AddIdentityServerJwt();
#endif
#if (OrganizationalAuth)
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
#elif (IndividualB2CAuth)
services.AddAuthentication(AzureADB2CDefaults.BearerAuthenticationScheme)
.AddAzureADB2CBearer(options => Configuration.Bind("AzureAdB2C", options));
#endif
services.AddControllersWithViews();
#if (IndividualLocalAuth)
services.AddRazorPages();
#endif
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
@ -29,17 +86,46 @@ namespace BlazorWasm_CSharp.Server
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
#if (IndividualLocalAuth)
app.UseDatabaseErrorPage();
#endif
app.UseBlazorDebugging();
}
else
{
app.UseExceptionHandler("/Error");
#if (RequiresHttps)
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
#else
}
#endif
app.UseStaticFiles();
app.UseClientSideBlazorFiles<Client.Program>();
app.UseRouting();
#if (OrganizationalAuth || IndividualAuth)
app.UseAuthentication();
#endif
#if (IndividualLocalAuth)
app.UseIdentityServer();
#endif
#if (!NoAuth)
app.UseAuthorization();
#endif
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
#if (IndividualLocalAuth)
endpoints.MapRazorPages();
#endif
endpoints.MapControllers();
endpoints.MapFallbackToClientSideBlazor<Client.Program>("index.html");
});
}

View File

@ -0,0 +1,18 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
////#if (IndividualLocalAuth)
// },
// "IdentityServer": {
// "Key": {
// "Type": "Development"
// }
// }
////#else
// }
////#endif
}

View File

@ -0,0 +1,41 @@
{
////#if (IndividualLocalAuth)
// "ConnectionStrings": {
////#if (UseLocalDB)
// "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-BlazorWasm_CSharp.Server-53bc9b9d-9d6a-45d4-8429-2a2761773502;Trusted_Connection=True;MultipleActiveResultSets=true"
////#else
// "DefaultConnection": "DataSource=app.db"
////#endif
// },
////#elseif (IndividualB2CAuth)
// "AzureAdB2C": {
// "Instance": "https:////aadB2CInstance.b2clogin.com/",
// "ClientId": "11111111-1111-1111-11111111111111111",
// "Domain": "qualified.domain.name",
// "SignUpSignInPolicyId": "MySignUpSignInPolicyId"
// },
////#elseif (OrganizationalAuth)
// "AzureAd": {
// "Instance": "https:////login.microsoftonline.com/",
// "Domain": "qualified.domain.name",
// "TenantId": "22222222-2222-2222-2222-222222222222",
// },
////#endif
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
////#if (IndividualLocalAuth)
// "IdentityServer": {
// "Clients": {
// "BlazorWasm_CSharp.Client": {
// "Profile": "IdentityServerSPA"
// }
// }
// },
////#endif
"AllowedHosts": "*"
}

View File

@ -3,12 +3,16 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.E2ETesting;
using Microsoft.Extensions.CommandLineUtils;
using Newtonsoft.Json.Linq;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.Extensions;
using Templates.Test.Helpers;
using Xunit;
using Xunit.Abstractions;
@ -47,17 +51,23 @@ namespace Templates.Test
await BuildAndRunTest(project.ProjectName, project);
using var serveProcess = RunPublishedStandaloneBlazorProject(project);
}
private ProcessEx RunPublishedStandaloneBlazorProject(Project project)
{
var publishDir = Path.Combine(project.TemplatePublishDir, project.ProjectName, "dist");
AspNetProcess.EnsureDevelopmentCertificates();
Output.WriteLine("Running dotnet serve on published output...");
using var serveProcess = ProcessEx.Run(Output, publishDir, DotNetMuxer.MuxerPathOrDefault(), "serve -S");
var serveProcess = ProcessEx.Run(Output, publishDir, DotNetMuxer.MuxerPathOrDefault(), "serve -S");
// Todo: Use dynamic port assignment: https://github.com/natemcmaster/dotnet-serve/pull/40/files
var listeningUri = "https://localhost:8080";
Output.WriteLine($"Opening browser at {listeningUri}...");
Browser.Navigate().GoToUrl(listeningUri);
TestBasicNavigation(project.ProjectName);
return serveProcess;
}
[Fact]
@ -114,7 +124,6 @@ namespace Templates.Test
await BuildAndRunTest(project.ProjectName, project);
var publishDir = Path.Combine(project.TemplatePublishDir, project.ProjectName, "dist");
AspNetProcess.EnsureDevelopmentCertificates();
// When publishing the PWA template, we generate an assets manifest
// and move service-worker.published.js to overwrite service-worker.js
@ -122,26 +131,131 @@ namespace Templates.Test
Assert.True(File.Exists(Path.Combine(publishDir, "service-worker.js")), "service-worker.js should be published");
Assert.True(File.Exists(Path.Combine(publishDir, "service-worker-assets.js")), "service-worker-assets.js should be published");
using (var serverProcess = RunPublishedStandaloneBlazorProject(project))
{
// We want to use this form to ensure that it gets disposed right after the test.
}
// Todo: Use dynamic port assignment: https://github.com/natemcmaster/dotnet-serve/pull/40/files
var listeningUri = "https://localhost:8080";
Output.WriteLine("Running dotnet serve on published output...");
using (var serveProcess = ProcessEx.Run(Output, publishDir, DotNetMuxer.MuxerPathOrDefault(), "serve -S"))
{
Output.WriteLine($"Opening browser at {listeningUri}...");
Browser.Navigate().GoToUrl(listeningUri);
TestBasicNavigation(project.ProjectName);
}
// The PWA template supports offline use. By now, the browser should have cached everything it needs,
// so we can continue working even without the server.
ValidateAppWorksOffline(project, listeningUri);
}
private void ValidateAppWorksOffline(Project project, string listeningUri)
{
Browser.Navigate().GoToUrl("about:blank"); // Be sure we're really reloading
Output.WriteLine($"Opening browser without corresponding server at {listeningUri}...");
Browser.Navigate().GoToUrl(listeningUri);
TestBasicNavigation(project.ProjectName);
}
protected async Task BuildAndRunTest(string appName, Project project)
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task BlazorWasmHostedTemplate_IndividualAuth_Works(bool useLocalDb)
{
var project = await ProjectFactory.GetOrCreateProject("blazorhostedindividual" + (useLocalDb ? "uld" : ""), Output);
var createResult = await project.RunDotNetNewAsync("blazorwasm", args: new[] { "--hosted", "-au", "Individual", useLocalDb ? "-uld" : "" });
Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult));
var serverProject = GetSubProject(project, "Server", $"{project.ProjectName}.Server");
var serverProjectFileContents = ReadFile(serverProject.TemplateOutputDir, $"{serverProject.ProjectName}.csproj");
if (!useLocalDb)
{
Assert.Contains(".db", serverProjectFileContents);
}
var appSettings = ReadFile(serverProject.TemplateOutputDir, "appSettings.json");
var element = JsonSerializer.Deserialize<JsonElement>(appSettings);
var clientsProperty = element.GetProperty("IdentityServer").EnumerateObject().Single().Value.EnumerateObject().Single();
var replacedSection = element.GetRawText().Replace(clientsProperty.Name, serverProject.ProjectName.Replace(".Server", ".Client"));
var appSettingsPath = Path.Combine(serverProject.TemplateOutputDir, "appSettings.json");
File.WriteAllText(appSettingsPath, replacedSection);
var publishResult = await serverProject.RunDotNetPublishAsync();
Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", serverProject, publishResult));
// Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release
// The output from publish will go into bin/Release/netcoreappX.Y/publish and won't be affected by calling build
// later, while the opposite is not true.
var buildResult = await serverProject.RunDotNetBuildAsync();
Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", serverProject, buildResult));
var migrationsResult = await serverProject.RunDotNetEfCreateMigrationAsync("blazorwasm");
Assert.True(0 == migrationsResult.ExitCode, ErrorMessages.GetFailedProcessMessage("run EF migrations", serverProject, migrationsResult));
serverProject.AssertEmptyMigration("blazorwasm");
if (useLocalDb)
{
using var dbUpdateResult = await serverProject.RunDotNetEfUpdateDatabaseAsync();
Assert.True(0 == dbUpdateResult.ExitCode, ErrorMessages.GetFailedProcessMessage("update database", serverProject, dbUpdateResult));
}
await BuildAndRunTest(project.ProjectName, serverProject, usesAuth: true);
UpdatePublishedSettings(serverProject);
using var aspNetProcess = serverProject.StartPublishedProjectAsync();
Assert.False(
aspNetProcess.Process.HasExited,
ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", serverProject, aspNetProcess.Process));
await aspNetProcess.AssertStatusCode("/", HttpStatusCode.OK, "text/html");
if (BrowserFixture.IsHostAutomationSupported())
{
aspNetProcess.VisitInBrowser(Browser);
TestBasicNavigation(project.ProjectName, usesAuth: true);
}
else
{
BrowserFixture.EnforceSupportedConfigurations();
}
}
[Fact]
public async Task BlazorWasmStandaloneTemplate_IndividualAuth_Works()
{
var project = await ProjectFactory.GetOrCreateProject("blazorstandaloneindividual", Output);
project.TargetFramework = "netstandard2.1";
var createResult = await project.RunDotNetNewAsync("blazorwasm", args: new[] {
"-au",
"Individual",
"--authority",
"https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration",
"--client-id",
"sample-client-id"
});
Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult));
var publishResult = await project.RunDotNetPublishAsync();
Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", project, publishResult));
// Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release
// The output from publish will go into bin/Release/netcoreappX.Y/publish and won't be affected by calling build
// later, while the opposite is not true.
var buildResult = await project.RunDotNetBuildAsync();
Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", project, buildResult));
// We don't want to test the auth flow as we don't have the required settings to talk to a third-party IdP
// but we want to make sure that we are able to run the app without errors.
// That will at least test that we are able to initialize and retrieve the configuration from the IdP
// for that, we use the common microsoft tenant.
await BuildAndRunTest(project.ProjectName, project, usesAuth: false);
using var serveProcess = RunPublishedStandaloneBlazorProject(project);
}
protected async Task BuildAndRunTest(string appName, Project project, bool usesAuth = false)
{
using var aspNetProcess = project.StartBuiltProjectAsync();
@ -153,7 +267,7 @@ namespace Templates.Test
if (BrowserFixture.IsHostAutomationSupported())
{
aspNetProcess.VisitInBrowser(Browser);
TestBasicNavigation(appName);
TestBasicNavigation(appName, usesAuth);
}
else
{
@ -161,8 +275,17 @@ namespace Templates.Test
}
}
private void TestBasicNavigation(string appName)
private void TestBasicNavigation(string appName, bool usesAuth = false)
{
// Start fresh always
if (usesAuth)
{
Browser.ExecuteJavaScript("sessionStorage.clear()");
Browser.ExecuteJavaScript("localStorage.clear()");
Browser.Manage().Cookies.DeleteAllCookies();
Browser.Navigate().Refresh();
}
// Give components.server enough time to load so that it can replace
// the prerendered content before we start making assertions.
Thread.Sleep(5000);
@ -184,6 +307,40 @@ namespace Templates.Test
Browser.FindElement(By.CssSelector("p+button")).Click();
Browser.Equal("Current count: 1", () => Browser.FindElement(By.CssSelector("h1 + p")).Text);
if (usesAuth)
{
Browser.FindElement(By.PartialLinkText("Log in")).Click();
Browser.Contains("/Identity/Account/Login", () => Browser.Url);
Browser.FindElement(By.PartialLinkText("Register as a new user")).Click();
var userName = $"{Guid.NewGuid()}@example.com";
var password = $"!Test.Password1$";
Browser.Exists(By.Name("Input.Email"));
Browser.FindElement(By.Name("Input.Email")).SendKeys(userName);
Browser.FindElement(By.Name("Input.Password")).SendKeys(password);
Browser.FindElement(By.Name("Input.ConfirmPassword")).SendKeys(password);
Browser.FindElement(By.Id("registerSubmit")).Click();
// We will be redirected to the RegisterConfirmation
Browser.Contains("/Identity/Account/RegisterConfirmation", () => Browser.Url);
Browser.FindElement(By.PartialLinkText("Click here to confirm your account")).Click();
// We will be redirected to the ConfirmEmail
Browser.Contains("/Identity/Account/ConfirmEmail", () => Browser.Url);
// Now we can login
Browser.FindElement(By.PartialLinkText("Login")).Click();
Browser.Exists(By.Name("Input.Email"));
Browser.FindElement(By.Name("Input.Email")).SendKeys(userName);
Browser.FindElement(By.Name("Input.Password")).SendKeys(password);
Browser.FindElement(By.Id("login-submit")).Click();
// Need to navigate to fetch page
Browser.Navigate().GoToUrl(new Uri(Browser.Url).GetLeftPart(UriPartial.Authority));
Browser.Equal(appName.Trim(), () => Browser.Title.Trim());
}
// Can navigate to the 'fetch data' page
Browser.FindElement(By.PartialLinkText("Fetch data")).Click();
Browser.Contains("fetchdata", () => Browser.Url);
@ -194,6 +351,15 @@ namespace Templates.Test
Browser.Equal(5, () => Browser.FindElements(By.CssSelector("p+table>tbody>tr")).Count);
}
private string ReadFile(string basePath, string path)
{
var fullPath = Path.Combine(basePath, path);
var doesExist = File.Exists(fullPath);
Assert.True(doesExist, $"Expected file to exist, but it doesn't: {path}");
return File.ReadAllText(Path.Combine(basePath, path));
}
private Project GetSubProject(Project project, string projectDirectory, string projectName)
{
var subProjectDirectory = Path.Combine(project.TemplateOutputDir, projectDirectory);
@ -214,5 +380,25 @@ namespace Templates.Test
return subProject;
}
private void UpdatePublishedSettings(Project serverProject)
{
// Hijack here the config file to use the development key during publish.
var appSettings = JObject.Parse(File.ReadAllText(Path.Combine(serverProject.TemplateOutputDir, "appsettings.json")));
var appSettingsDevelopment = JObject.Parse(File.ReadAllText(Path.Combine(serverProject.TemplateOutputDir, "appsettings.Development.json")));
((JObject)appSettings["IdentityServer"]).Merge(appSettingsDevelopment["IdentityServer"]);
((JObject)appSettings["IdentityServer"]).Merge(new
{
IdentityServer = new
{
Key = new
{
FilePath = "./tempkey.json"
}
}
});
var testAppSettings = appSettings.ToString();
File.WriteAllText(Path.Combine(serverProject.TemplatePublishDir, "appsettings.json"), testAppSettings);
}
}
}

View File

@ -48,7 +48,7 @@ namespace Templates.Test.Helpers
NodeLock = NodeLock,
Output = outputHelper,
DiagnosticsMessageSink = DiagnosticsMessageSink,
ProjectGuid = Path.GetRandomFileName().Replace(".", string.Empty)
ProjectGuid = Path.GetRandomFileName().Replace(".", string.Empty).Replace("-", "").Replace("_", "")
};
project.ProjectName = $"AspNet.{key}.{project.ProjectGuid}";

View File

@ -40,6 +40,14 @@
IsImplicitlyDefined="true" />
</ItemGroup>
<!-- Temporarily remove the components analyzer due to build issues -->
<Target Name="RemoveComponentsAnalyzer" BeforeTargets="CoreCompile">
<ItemGroup>
<_AnalyzerToRemove Include="@(Analyzer)" Condition="'%(Analyzer.NuGetPackageId)' == 'Microsoft.AspNetCore.Components.Analyzers'" />
<Analyzer Remove="@(_AnalyzerToRemove)" />
</ItemGroup>
</Target>
<ItemGroup Condition="'$(UsingMicrosoftNETSdkWeb)' == 'true' OR '$(RazorSdkCurrentVersionProps)' != ''">
<!--
Use the Razor SDK as a package reference. The version of the .NET Core SDK we build with often contains a version of the Razor SDK

View File

@ -25,6 +25,7 @@
"wwwroot/css/open-iconic/ICON-LICENSE",
"wwwroot/css/open-iconic/README.md",
"wwwroot/css/site.css",
"wwwroot/favicon.ico",
"wwwroot/index.html",
"wwwroot/sample-data/weather.json",
"_Imports.razor"
@ -55,8 +56,11 @@
"Client/wwwroot/css/open-iconic/ICON-LICENSE",
"Client/wwwroot/css/open-iconic/README.md",
"Client/wwwroot/css/site.css",
"Client/wwwroot/favicon.ico",
"Client/wwwroot/index.html",
"Client/_Imports.razor",
"Server/appsettings.json",
"Server/appsettings.Development.json",
"Server/Controllers/WeatherForecastController.cs",
"Server/Program.cs",
"Server/Properties/launchSettings.json",
@ -89,6 +93,7 @@
"wwwroot/css/open-iconic/ICON-LICENSE",
"wwwroot/css/open-iconic/README.md",
"wwwroot/css/site.css",
"wwwroot/favicon.ico",
"wwwroot/icon-512.png",
"wwwroot/index.html",
"wwwroot/manifest.json",