Support for ApiAuth using Identity Server

This commit is contained in:
Javier Calvarro Nelson 2018-10-29 13:10:29 -07:00
parent da9318f431
commit 5e10eb1d1a
95 changed files with 27787 additions and 933 deletions

View File

@ -45,8 +45,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Directory.Build.targets = Directory.Build.targets
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeWPFClient", "samples\NativeWPFClient\NativeWPFClient.csproj", "{39AA4E4D-5E62-4213-8641-BF8012D45DE4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentitySample.DefaultUI", "samples\IdentitySample.DefaultUI\IdentitySample.DefaultUI.csproj", "{ACC75F4F-EA7D-49E0-A64C-9D4A3DFD5B8A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Identity.UI", "src\UI\Microsoft.AspNetCore.Identity.UI.csproj", "{894E102D-56D4-4B02-8F13-8781F4324C3E}"
@ -58,6 +56,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Identi
{894E102D-56D4-4B02-8F13-8781F4324C3E} = {894E102D-56D4-4B02-8F13-8781F4324C3E}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiAuthSample", "samples\ApiAuthSample\ApiAuthSample.csproj", "{7FA90737-4A2D-4BBB-8245-F6564D462FCB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.ApiAuthorization.IdentityServer", "src\ApiAuth.IS\Microsoft.AspNetCore.ApiAuthorization.IdentityServer.csproj", "{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Test", "test\ApiAuth.IS.Test\Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Test.csproj", "{ECFE11DD-1111-4557-8E28-42F8E9878823}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -338,28 +342,6 @@ Global
{FADA11FC-DC06-4832-A569-7B2374A6CD42}.ReleaseNoWPF|x64.Build.0 = Release|Any CPU
{FADA11FC-DC06-4832-A569-7B2374A6CD42}.ReleaseNoWPF|x86.ActiveCfg = Release|Any CPU
{FADA11FC-DC06-4832-A569-7B2374A6CD42}.ReleaseNoWPF|x86.Build.0 = Release|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.Debug|x64.ActiveCfg = Debug|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.Debug|x64.Build.0 = Debug|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.Debug|x86.ActiveCfg = Debug|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.Debug|x86.Build.0 = Debug|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.DebugNoWPF|Any CPU.ActiveCfg = Debug|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.DebugNoWPF|x64.ActiveCfg = Debug|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.DebugNoWPF|x64.Build.0 = Debug|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.DebugNoWPF|x86.ActiveCfg = Debug|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.DebugNoWPF|x86.Build.0 = Debug|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.Release|Any CPU.Build.0 = Release|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.Release|x64.ActiveCfg = Release|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.Release|x64.Build.0 = Release|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.Release|x86.ActiveCfg = Release|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.Release|x86.Build.0 = Release|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.ReleaseNoWPF|Any CPU.ActiveCfg = Release|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.ReleaseNoWPF|x64.ActiveCfg = Release|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.ReleaseNoWPF|x64.Build.0 = Release|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.ReleaseNoWPF|x86.ActiveCfg = Release|Any CPU
{39AA4E4D-5E62-4213-8641-BF8012D45DE4}.ReleaseNoWPF|x86.Build.0 = Release|Any CPU
{ACC75F4F-EA7D-49E0-A64C-9D4A3DFD5B8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ACC75F4F-EA7D-49E0-A64C-9D4A3DFD5B8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ACC75F4F-EA7D-49E0-A64C-9D4A3DFD5B8A}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -426,8 +408,8 @@ Global
{CAE02AD2-F941-4ACB-B469-13EFF551BB74}.Release|x64.Build.0 = Release|Any CPU
{CAE02AD2-F941-4ACB-B469-13EFF551BB74}.Release|x86.ActiveCfg = Release|Any CPU
{CAE02AD2-F941-4ACB-B469-13EFF551BB74}.Release|x86.Build.0 = Release|Any CPU
{CAE02AD2-F941-4ACB-B469-13EFF551BB74}.ReleaseNoWPF|Any CPU.ActiveCfg = Debug|Any CPU
{CAE02AD2-F941-4ACB-B469-13EFF551BB74}.ReleaseNoWPF|Any CPU.Build.0 = Debug|Any CPU
{CAE02AD2-F941-4ACB-B469-13EFF551BB74}.ReleaseNoWPF|Any CPU.ActiveCfg = Release|Any CPU
{CAE02AD2-F941-4ACB-B469-13EFF551BB74}.ReleaseNoWPF|Any CPU.Build.0 = Release|Any CPU
{CAE02AD2-F941-4ACB-B469-13EFF551BB74}.ReleaseNoWPF|x64.ActiveCfg = Debug|Any CPU
{CAE02AD2-F941-4ACB-B469-13EFF551BB74}.ReleaseNoWPF|x64.Build.0 = Debug|Any CPU
{CAE02AD2-F941-4ACB-B469-13EFF551BB74}.ReleaseNoWPF|x86.ActiveCfg = Debug|Any CPU
@ -456,6 +438,78 @@ Global
{B3616029-7DA6-4FB3-8722-D5AC69884B3F}.ReleaseNoWPF|x64.Build.0 = Debug|Any CPU
{B3616029-7DA6-4FB3-8722-D5AC69884B3F}.ReleaseNoWPF|x86.ActiveCfg = Debug|Any CPU
{B3616029-7DA6-4FB3-8722-D5AC69884B3F}.ReleaseNoWPF|x86.Build.0 = Debug|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.Debug|x64.ActiveCfg = Debug|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.Debug|x64.Build.0 = Debug|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.Debug|x86.ActiveCfg = Debug|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.Debug|x86.Build.0 = Debug|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.DebugNoWPF|Any CPU.ActiveCfg = Debug|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.DebugNoWPF|Any CPU.Build.0 = Debug|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.DebugNoWPF|x64.ActiveCfg = Debug|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.DebugNoWPF|x64.Build.0 = Debug|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.DebugNoWPF|x86.ActiveCfg = Debug|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.DebugNoWPF|x86.Build.0 = Debug|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.Release|Any CPU.Build.0 = Release|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.Release|x64.ActiveCfg = Release|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.Release|x64.Build.0 = Release|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.Release|x86.ActiveCfg = Release|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.Release|x86.Build.0 = Release|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.ReleaseNoWPF|Any CPU.ActiveCfg = Release|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.ReleaseNoWPF|Any CPU.Build.0 = Release|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.ReleaseNoWPF|x64.ActiveCfg = Debug|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.ReleaseNoWPF|x64.Build.0 = Debug|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.ReleaseNoWPF|x86.ActiveCfg = Debug|Any CPU
{7FA90737-4A2D-4BBB-8245-F6564D462FCB}.ReleaseNoWPF|x86.Build.0 = Debug|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.Debug|x64.ActiveCfg = Debug|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.Debug|x64.Build.0 = Debug|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.Debug|x86.ActiveCfg = Debug|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.Debug|x86.Build.0 = Debug|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.DebugNoWPF|Any CPU.ActiveCfg = Debug|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.DebugNoWPF|Any CPU.Build.0 = Debug|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.DebugNoWPF|x64.ActiveCfg = Debug|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.DebugNoWPF|x64.Build.0 = Debug|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.DebugNoWPF|x86.ActiveCfg = Debug|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.DebugNoWPF|x86.Build.0 = Debug|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.Release|Any CPU.Build.0 = Release|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.Release|x64.ActiveCfg = Release|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.Release|x64.Build.0 = Release|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.Release|x86.ActiveCfg = Release|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.Release|x86.Build.0 = Release|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.ReleaseNoWPF|Any CPU.ActiveCfg = Release|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.ReleaseNoWPF|Any CPU.Build.0 = Release|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.ReleaseNoWPF|x64.ActiveCfg = Debug|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.ReleaseNoWPF|x64.Build.0 = Debug|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.ReleaseNoWPF|x86.ActiveCfg = Debug|Any CPU
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4}.ReleaseNoWPF|x86.Build.0 = Debug|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.Debug|x64.ActiveCfg = Debug|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.Debug|x64.Build.0 = Debug|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.Debug|x86.ActiveCfg = Debug|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.Debug|x86.Build.0 = Debug|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.DebugNoWPF|Any CPU.ActiveCfg = Debug|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.DebugNoWPF|Any CPU.Build.0 = Debug|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.DebugNoWPF|x64.ActiveCfg = Debug|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.DebugNoWPF|x64.Build.0 = Debug|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.DebugNoWPF|x86.ActiveCfg = Debug|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.DebugNoWPF|x86.Build.0 = Debug|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.Release|Any CPU.Build.0 = Release|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.Release|x64.ActiveCfg = Release|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.Release|x64.Build.0 = Release|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.Release|x86.ActiveCfg = Release|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.Release|x86.Build.0 = Release|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.ReleaseNoWPF|Any CPU.ActiveCfg = Release|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.ReleaseNoWPF|Any CPU.Build.0 = Release|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.ReleaseNoWPF|x64.ActiveCfg = Debug|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.ReleaseNoWPF|x64.Build.0 = Debug|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.ReleaseNoWPF|x86.ActiveCfg = Debug|Any CPU
{ECFE11DD-1111-4557-8E28-42F8E9878823}.ReleaseNoWPF|x86.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -473,11 +527,13 @@ Global
{D5905D78-A32E-44B8-8F21-EDAEDC95D9B8} = {0F647068-6602-4E24-B1DC-8ED91481A50A}
{FADA11FC-DC06-4832-A569-7B2374A6CD42} = {0F647068-6602-4E24-B1DC-8ED91481A50A}
{1F83D453-E094-4D28-BCFA-9E537ABB5AD6} = {52D59F18-62D2-4D17-8CF2-BE192445AF8E}
{39AA4E4D-5E62-4213-8641-BF8012D45DE4} = {58D94A0E-C2B7-43A7-8826-99ECBB1E0A50}
{ACC75F4F-EA7D-49E0-A64C-9D4A3DFD5B8A} = {58D94A0E-C2B7-43A7-8826-99ECBB1E0A50}
{894E102D-56D4-4B02-8F13-8781F4324C3E} = {0F647068-6602-4E24-B1DC-8ED91481A50A}
{CAE02AD2-F941-4ACB-B469-13EFF551BB74} = {1F83D453-E094-4D28-BCFA-9E537ABB5AD6}
{B3616029-7DA6-4FB3-8722-D5AC69884B3F} = {52D59F18-62D2-4D17-8CF2-BE192445AF8E}
{7FA90737-4A2D-4BBB-8245-F6564D462FCB} = {58D94A0E-C2B7-43A7-8826-99ECBB1E0A50}
{590C70E2-FCCC-49C2-93F3-60B7AA0533A4} = {0F647068-6602-4E24-B1DC-8ED91481A50A}
{ECFE11DD-1111-4557-8E28-42F8E9878823} = {52D59F18-62D2-4D17-8CF2-BE192445AF8E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B3F2A592-CCE0-40C2-8CA4-7B1293DED874}

View File

@ -1,319 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.10
MinimumVisualStudioVersion = 15.0.26730.03
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0F647068-6602-4E24-B1DC-8ED91481A50A}"
ProjectSection(SolutionItems) = preProject
src\Directory.Build.props = src\Directory.Build.props
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{52D59F18-62D2-4D17-8CF2-BE192445AF8E}"
ProjectSection(SolutionItems) = preProject
test\Directory.Build.props = test\Directory.Build.props
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Identity", "src\Identity\Microsoft.AspNetCore.Identity.csproj", "{1729302E-A58E-4652-B639-5B6B68DA2748}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Identity.Test", "test\Identity.Test\Microsoft.AspNetCore.Identity.Test.csproj", "{2CF3927B-19E4-4866-9BAA-2C131580E7C3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Identity.InMemory.Test", "test\InMemory.Test\Microsoft.AspNetCore.Identity.InMemory.Test.csproj", "{65161409-C4C4-4D63-A73B-231FCFF4D503}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{58D94A0E-C2B7-43A7-8826-99ECBB1E0A50}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentitySample.Mvc", "samples\IdentitySample.Mvc\IdentitySample.Mvc.csproj", "{E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test", "test\EF.Test\Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test.csproj", "{37236EA3-915D-46D5-997C-DF513C500E4B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test", "test\EF.InMemory.Test\Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test.csproj", "{EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Identity.EntityFrameworkCore", "src\EF\Microsoft.AspNetCore.Identity.EntityFrameworkCore.csproj", "{4490894C-3572-4E63-86F1-EE5105CE8A06}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNet.Identity.AspNetCoreCompat", "src\AspNetCoreCompat\Microsoft.AspNet.Identity.AspNetCoreCompat.csproj", "{6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Identity.Specification.Tests", "src\Specification.Tests\Microsoft.AspNetCore.Identity.Specification.Tests.csproj", "{5608E828-DD54-4E2A-B73C-FC22268BE797}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Identity.Core", "src\Core\Microsoft.Extensions.Identity.Core.csproj", "{D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Identity.Stores", "src\Stores\Microsoft.Extensions.Identity.Stores.csproj", "{FADA11FC-DC06-4832-A569-7B2374A6CD42}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2C657F6C-D8AD-4833-9C59-2301A16957BD}"
ProjectSection(SolutionItems) = preProject
Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentitySample.DefaultUI", "samples\IdentitySample.DefaultUI\IdentitySample.DefaultUI.csproj", "{D5FB2E24-4C71-430C-A289-59C8D59164B0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Identity.UI", "src\UI\Microsoft.AspNetCore.Identity.UI.csproj", "{1FB3E9BB-E20A-4807-A4C3-F86A341304DB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Identity.FunctionalTests", "test\Identity.FunctionalTests\Microsoft.AspNetCore.Identity.FunctionalTests.csproj", "{BAC36757-9A47-43CB-A6F3-86E8C4650A28}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebSites", "WebSites", "{C47C1165-9F19-4DF8-ABA9-707ACEB3BDC7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Identity.DefaultUI.WebSite", "test\WebSites\Identity.DefaultUI.WebSite\Identity.DefaultUI.WebSite.csproj", "{EA424B4D-0BE1-49AC-A106-CC6CC808A104}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|Mixed Platforms = Debug|Mixed Platforms
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|Mixed Platforms = Release|Mixed Platforms
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1729302E-A58E-4652-B639-5B6B68DA2748}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1729302E-A58E-4652-B639-5B6B68DA2748}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1729302E-A58E-4652-B639-5B6B68DA2748}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{1729302E-A58E-4652-B639-5B6B68DA2748}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{1729302E-A58E-4652-B639-5B6B68DA2748}.Debug|x64.ActiveCfg = Debug|Any CPU
{1729302E-A58E-4652-B639-5B6B68DA2748}.Debug|x64.Build.0 = Debug|Any CPU
{1729302E-A58E-4652-B639-5B6B68DA2748}.Debug|x86.ActiveCfg = Debug|Any CPU
{1729302E-A58E-4652-B639-5B6B68DA2748}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1729302E-A58E-4652-B639-5B6B68DA2748}.Release|Any CPU.Build.0 = Release|Any CPU
{1729302E-A58E-4652-B639-5B6B68DA2748}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{1729302E-A58E-4652-B639-5B6B68DA2748}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{1729302E-A58E-4652-B639-5B6B68DA2748}.Release|x64.ActiveCfg = Release|Any CPU
{1729302E-A58E-4652-B639-5B6B68DA2748}.Release|x64.Build.0 = Release|Any CPU
{1729302E-A58E-4652-B639-5B6B68DA2748}.Release|x86.ActiveCfg = Release|Any CPU
{2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Debug|x64.ActiveCfg = Debug|Any CPU
{2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Debug|x64.Build.0 = Debug|Any CPU
{2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Debug|x86.ActiveCfg = Debug|Any CPU
{2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Release|Any CPU.Build.0 = Release|Any CPU
{2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Release|x64.ActiveCfg = Release|Any CPU
{2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Release|x64.Build.0 = Release|Any CPU
{2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Release|x86.ActiveCfg = Release|Any CPU
{65161409-C4C4-4D63-A73B-231FCFF4D503}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{65161409-C4C4-4D63-A73B-231FCFF4D503}.Debug|Any CPU.Build.0 = Debug|Any CPU
{65161409-C4C4-4D63-A73B-231FCFF4D503}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{65161409-C4C4-4D63-A73B-231FCFF4D503}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{65161409-C4C4-4D63-A73B-231FCFF4D503}.Debug|x64.ActiveCfg = Debug|Any CPU
{65161409-C4C4-4D63-A73B-231FCFF4D503}.Debug|x64.Build.0 = Debug|Any CPU
{65161409-C4C4-4D63-A73B-231FCFF4D503}.Debug|x86.ActiveCfg = Debug|Any CPU
{65161409-C4C4-4D63-A73B-231FCFF4D503}.Release|Any CPU.ActiveCfg = Release|Any CPU
{65161409-C4C4-4D63-A73B-231FCFF4D503}.Release|Any CPU.Build.0 = Release|Any CPU
{65161409-C4C4-4D63-A73B-231FCFF4D503}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{65161409-C4C4-4D63-A73B-231FCFF4D503}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{65161409-C4C4-4D63-A73B-231FCFF4D503}.Release|x64.ActiveCfg = Release|Any CPU
{65161409-C4C4-4D63-A73B-231FCFF4D503}.Release|x64.Build.0 = Release|Any CPU
{65161409-C4C4-4D63-A73B-231FCFF4D503}.Release|x86.ActiveCfg = Release|Any CPU
{E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Debug|x64.ActiveCfg = Debug|Any CPU
{E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Debug|x64.Build.0 = Debug|Any CPU
{E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Debug|x86.ActiveCfg = Debug|Any CPU
{E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Release|Any CPU.Build.0 = Release|Any CPU
{E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Release|x64.ActiveCfg = Release|Any CPU
{E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Release|x64.Build.0 = Release|Any CPU
{E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Release|x86.ActiveCfg = Release|Any CPU
{37236EA3-915D-46D5-997C-DF513C500E4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{37236EA3-915D-46D5-997C-DF513C500E4B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{37236EA3-915D-46D5-997C-DF513C500E4B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{37236EA3-915D-46D5-997C-DF513C500E4B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{37236EA3-915D-46D5-997C-DF513C500E4B}.Debug|x64.ActiveCfg = Debug|Any CPU
{37236EA3-915D-46D5-997C-DF513C500E4B}.Debug|x64.Build.0 = Debug|Any CPU
{37236EA3-915D-46D5-997C-DF513C500E4B}.Debug|x86.ActiveCfg = Debug|Any CPU
{37236EA3-915D-46D5-997C-DF513C500E4B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{37236EA3-915D-46D5-997C-DF513C500E4B}.Release|Any CPU.Build.0 = Release|Any CPU
{37236EA3-915D-46D5-997C-DF513C500E4B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{37236EA3-915D-46D5-997C-DF513C500E4B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{37236EA3-915D-46D5-997C-DF513C500E4B}.Release|x64.ActiveCfg = Release|Any CPU
{37236EA3-915D-46D5-997C-DF513C500E4B}.Release|x64.Build.0 = Release|Any CPU
{37236EA3-915D-46D5-997C-DF513C500E4B}.Release|x86.ActiveCfg = Release|Any CPU
{EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Debug|x64.ActiveCfg = Debug|Any CPU
{EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Debug|x64.Build.0 = Debug|Any CPU
{EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Debug|x86.ActiveCfg = Debug|Any CPU
{EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Release|Any CPU.Build.0 = Release|Any CPU
{EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Release|x64.ActiveCfg = Release|Any CPU
{EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Release|x64.Build.0 = Release|Any CPU
{EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Release|x86.ActiveCfg = Release|Any CPU
{4490894C-3572-4E63-86F1-EE5105CE8A06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4490894C-3572-4E63-86F1-EE5105CE8A06}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4490894C-3572-4E63-86F1-EE5105CE8A06}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{4490894C-3572-4E63-86F1-EE5105CE8A06}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{4490894C-3572-4E63-86F1-EE5105CE8A06}.Debug|x64.ActiveCfg = Debug|Any CPU
{4490894C-3572-4E63-86F1-EE5105CE8A06}.Debug|x64.Build.0 = Debug|Any CPU
{4490894C-3572-4E63-86F1-EE5105CE8A06}.Debug|x86.ActiveCfg = Debug|Any CPU
{4490894C-3572-4E63-86F1-EE5105CE8A06}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4490894C-3572-4E63-86F1-EE5105CE8A06}.Release|Any CPU.Build.0 = Release|Any CPU
{4490894C-3572-4E63-86F1-EE5105CE8A06}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{4490894C-3572-4E63-86F1-EE5105CE8A06}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{4490894C-3572-4E63-86F1-EE5105CE8A06}.Release|x64.ActiveCfg = Release|Any CPU
{4490894C-3572-4E63-86F1-EE5105CE8A06}.Release|x64.Build.0 = Release|Any CPU
{4490894C-3572-4E63-86F1-EE5105CE8A06}.Release|x86.ActiveCfg = Release|Any CPU
{6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Debug|x64.ActiveCfg = Debug|Any CPU
{6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Debug|x64.Build.0 = Debug|Any CPU
{6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Debug|x86.ActiveCfg = Debug|Any CPU
{6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Debug|x86.Build.0 = Debug|Any CPU
{6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Release|Any CPU.Build.0 = Release|Any CPU
{6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Release|x64.ActiveCfg = Release|Any CPU
{6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Release|x64.Build.0 = Release|Any CPU
{6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Release|x86.ActiveCfg = Release|Any CPU
{6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Release|x86.Build.0 = Release|Any CPU
{5608E828-DD54-4E2A-B73C-FC22268BE797}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5608E828-DD54-4E2A-B73C-FC22268BE797}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5608E828-DD54-4E2A-B73C-FC22268BE797}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{5608E828-DD54-4E2A-B73C-FC22268BE797}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{5608E828-DD54-4E2A-B73C-FC22268BE797}.Debug|x64.ActiveCfg = Debug|Any CPU
{5608E828-DD54-4E2A-B73C-FC22268BE797}.Debug|x64.Build.0 = Debug|Any CPU
{5608E828-DD54-4E2A-B73C-FC22268BE797}.Debug|x86.ActiveCfg = Debug|Any CPU
{5608E828-DD54-4E2A-B73C-FC22268BE797}.Debug|x86.Build.0 = Debug|Any CPU
{5608E828-DD54-4E2A-B73C-FC22268BE797}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5608E828-DD54-4E2A-B73C-FC22268BE797}.Release|Any CPU.Build.0 = Release|Any CPU
{5608E828-DD54-4E2A-B73C-FC22268BE797}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{5608E828-DD54-4E2A-B73C-FC22268BE797}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{5608E828-DD54-4E2A-B73C-FC22268BE797}.Release|x64.ActiveCfg = Release|Any CPU
{5608E828-DD54-4E2A-B73C-FC22268BE797}.Release|x64.Build.0 = Release|Any CPU
{5608E828-DD54-4E2A-B73C-FC22268BE797}.Release|x86.ActiveCfg = Release|Any CPU
{5608E828-DD54-4E2A-B73C-FC22268BE797}.Release|x86.Build.0 = Release|Any CPU
{D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Debug|x64.ActiveCfg = Debug|Any CPU
{D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Debug|x64.Build.0 = Debug|Any CPU
{D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Debug|x86.ActiveCfg = Debug|Any CPU
{D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Debug|x86.Build.0 = Debug|Any CPU
{D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Release|Any CPU.Build.0 = Release|Any CPU
{D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Release|x64.ActiveCfg = Release|Any CPU
{D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Release|x64.Build.0 = Release|Any CPU
{D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Release|x86.ActiveCfg = Release|Any CPU
{D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Release|x86.Build.0 = Release|Any CPU
{FADA11FC-DC06-4832-A569-7B2374A6CD42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FADA11FC-DC06-4832-A569-7B2374A6CD42}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FADA11FC-DC06-4832-A569-7B2374A6CD42}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{FADA11FC-DC06-4832-A569-7B2374A6CD42}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{FADA11FC-DC06-4832-A569-7B2374A6CD42}.Debug|x64.ActiveCfg = Debug|Any CPU
{FADA11FC-DC06-4832-A569-7B2374A6CD42}.Debug|x64.Build.0 = Debug|Any CPU
{FADA11FC-DC06-4832-A569-7B2374A6CD42}.Debug|x86.ActiveCfg = Debug|Any CPU
{FADA11FC-DC06-4832-A569-7B2374A6CD42}.Debug|x86.Build.0 = Debug|Any CPU
{FADA11FC-DC06-4832-A569-7B2374A6CD42}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FADA11FC-DC06-4832-A569-7B2374A6CD42}.Release|Any CPU.Build.0 = Release|Any CPU
{FADA11FC-DC06-4832-A569-7B2374A6CD42}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{FADA11FC-DC06-4832-A569-7B2374A6CD42}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{FADA11FC-DC06-4832-A569-7B2374A6CD42}.Release|x64.ActiveCfg = Release|Any CPU
{FADA11FC-DC06-4832-A569-7B2374A6CD42}.Release|x64.Build.0 = Release|Any CPU
{FADA11FC-DC06-4832-A569-7B2374A6CD42}.Release|x86.ActiveCfg = Release|Any CPU
{FADA11FC-DC06-4832-A569-7B2374A6CD42}.Release|x86.Build.0 = Release|Any CPU
{D5FB2E24-4C71-430C-A289-59C8D59164B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D5FB2E24-4C71-430C-A289-59C8D59164B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D5FB2E24-4C71-430C-A289-59C8D59164B0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{D5FB2E24-4C71-430C-A289-59C8D59164B0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{D5FB2E24-4C71-430C-A289-59C8D59164B0}.Debug|x64.ActiveCfg = Debug|Any CPU
{D5FB2E24-4C71-430C-A289-59C8D59164B0}.Debug|x64.Build.0 = Debug|Any CPU
{D5FB2E24-4C71-430C-A289-59C8D59164B0}.Debug|x86.ActiveCfg = Debug|Any CPU
{D5FB2E24-4C71-430C-A289-59C8D59164B0}.Debug|x86.Build.0 = Debug|Any CPU
{D5FB2E24-4C71-430C-A289-59C8D59164B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D5FB2E24-4C71-430C-A289-59C8D59164B0}.Release|Any CPU.Build.0 = Release|Any CPU
{D5FB2E24-4C71-430C-A289-59C8D59164B0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{D5FB2E24-4C71-430C-A289-59C8D59164B0}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{D5FB2E24-4C71-430C-A289-59C8D59164B0}.Release|x64.ActiveCfg = Release|Any CPU
{D5FB2E24-4C71-430C-A289-59C8D59164B0}.Release|x64.Build.0 = Release|Any CPU
{D5FB2E24-4C71-430C-A289-59C8D59164B0}.Release|x86.ActiveCfg = Release|Any CPU
{D5FB2E24-4C71-430C-A289-59C8D59164B0}.Release|x86.Build.0 = Release|Any CPU
{1FB3E9BB-E20A-4807-A4C3-F86A341304DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1FB3E9BB-E20A-4807-A4C3-F86A341304DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1FB3E9BB-E20A-4807-A4C3-F86A341304DB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{1FB3E9BB-E20A-4807-A4C3-F86A341304DB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{1FB3E9BB-E20A-4807-A4C3-F86A341304DB}.Debug|x64.ActiveCfg = Debug|Any CPU
{1FB3E9BB-E20A-4807-A4C3-F86A341304DB}.Debug|x64.Build.0 = Debug|Any CPU
{1FB3E9BB-E20A-4807-A4C3-F86A341304DB}.Debug|x86.ActiveCfg = Debug|Any CPU
{1FB3E9BB-E20A-4807-A4C3-F86A341304DB}.Debug|x86.Build.0 = Debug|Any CPU
{1FB3E9BB-E20A-4807-A4C3-F86A341304DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1FB3E9BB-E20A-4807-A4C3-F86A341304DB}.Release|Any CPU.Build.0 = Release|Any CPU
{1FB3E9BB-E20A-4807-A4C3-F86A341304DB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{1FB3E9BB-E20A-4807-A4C3-F86A341304DB}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{1FB3E9BB-E20A-4807-A4C3-F86A341304DB}.Release|x64.ActiveCfg = Release|Any CPU
{1FB3E9BB-E20A-4807-A4C3-F86A341304DB}.Release|x64.Build.0 = Release|Any CPU
{1FB3E9BB-E20A-4807-A4C3-F86A341304DB}.Release|x86.ActiveCfg = Release|Any CPU
{1FB3E9BB-E20A-4807-A4C3-F86A341304DB}.Release|x86.Build.0 = Release|Any CPU
{BAC36757-9A47-43CB-A6F3-86E8C4650A28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BAC36757-9A47-43CB-A6F3-86E8C4650A28}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BAC36757-9A47-43CB-A6F3-86E8C4650A28}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{BAC36757-9A47-43CB-A6F3-86E8C4650A28}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{BAC36757-9A47-43CB-A6F3-86E8C4650A28}.Debug|x64.ActiveCfg = Debug|Any CPU
{BAC36757-9A47-43CB-A6F3-86E8C4650A28}.Debug|x64.Build.0 = Debug|Any CPU
{BAC36757-9A47-43CB-A6F3-86E8C4650A28}.Debug|x86.ActiveCfg = Debug|Any CPU
{BAC36757-9A47-43CB-A6F3-86E8C4650A28}.Debug|x86.Build.0 = Debug|Any CPU
{BAC36757-9A47-43CB-A6F3-86E8C4650A28}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BAC36757-9A47-43CB-A6F3-86E8C4650A28}.Release|Any CPU.Build.0 = Release|Any CPU
{BAC36757-9A47-43CB-A6F3-86E8C4650A28}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{BAC36757-9A47-43CB-A6F3-86E8C4650A28}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{BAC36757-9A47-43CB-A6F3-86E8C4650A28}.Release|x64.ActiveCfg = Release|Any CPU
{BAC36757-9A47-43CB-A6F3-86E8C4650A28}.Release|x64.Build.0 = Release|Any CPU
{BAC36757-9A47-43CB-A6F3-86E8C4650A28}.Release|x86.ActiveCfg = Release|Any CPU
{BAC36757-9A47-43CB-A6F3-86E8C4650A28}.Release|x86.Build.0 = Release|Any CPU
{EA424B4D-0BE1-49AC-A106-CC6CC808A104}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EA424B4D-0BE1-49AC-A106-CC6CC808A104}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EA424B4D-0BE1-49AC-A106-CC6CC808A104}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{EA424B4D-0BE1-49AC-A106-CC6CC808A104}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{EA424B4D-0BE1-49AC-A106-CC6CC808A104}.Debug|x64.ActiveCfg = Debug|Any CPU
{EA424B4D-0BE1-49AC-A106-CC6CC808A104}.Debug|x64.Build.0 = Debug|Any CPU
{EA424B4D-0BE1-49AC-A106-CC6CC808A104}.Debug|x86.ActiveCfg = Debug|Any CPU
{EA424B4D-0BE1-49AC-A106-CC6CC808A104}.Debug|x86.Build.0 = Debug|Any CPU
{EA424B4D-0BE1-49AC-A106-CC6CC808A104}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EA424B4D-0BE1-49AC-A106-CC6CC808A104}.Release|Any CPU.Build.0 = Release|Any CPU
{EA424B4D-0BE1-49AC-A106-CC6CC808A104}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{EA424B4D-0BE1-49AC-A106-CC6CC808A104}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{EA424B4D-0BE1-49AC-A106-CC6CC808A104}.Release|x64.ActiveCfg = Release|Any CPU
{EA424B4D-0BE1-49AC-A106-CC6CC808A104}.Release|x64.Build.0 = Release|Any CPU
{EA424B4D-0BE1-49AC-A106-CC6CC808A104}.Release|x86.ActiveCfg = Release|Any CPU
{EA424B4D-0BE1-49AC-A106-CC6CC808A104}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{1729302E-A58E-4652-B639-5B6B68DA2748} = {0F647068-6602-4E24-B1DC-8ED91481A50A}
{2CF3927B-19E4-4866-9BAA-2C131580E7C3} = {52D59F18-62D2-4D17-8CF2-BE192445AF8E}
{65161409-C4C4-4D63-A73B-231FCFF4D503} = {52D59F18-62D2-4D17-8CF2-BE192445AF8E}
{E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6} = {58D94A0E-C2B7-43A7-8826-99ECBB1E0A50}
{37236EA3-915D-46D5-997C-DF513C500E4B} = {52D59F18-62D2-4D17-8CF2-BE192445AF8E}
{EA7EB28F-53B8-4009-9C6B-74DB090CA8DD} = {52D59F18-62D2-4D17-8CF2-BE192445AF8E}
{4490894C-3572-4E63-86F1-EE5105CE8A06} = {0F647068-6602-4E24-B1DC-8ED91481A50A}
{6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475} = {0F647068-6602-4E24-B1DC-8ED91481A50A}
{5608E828-DD54-4E2A-B73C-FC22268BE797} = {0F647068-6602-4E24-B1DC-8ED91481A50A}
{D5905D78-A32E-44B8-8F21-EDAEDC95D9B8} = {0F647068-6602-4E24-B1DC-8ED91481A50A}
{FADA11FC-DC06-4832-A569-7B2374A6CD42} = {0F647068-6602-4E24-B1DC-8ED91481A50A}
{D5FB2E24-4C71-430C-A289-59C8D59164B0} = {58D94A0E-C2B7-43A7-8826-99ECBB1E0A50}
{1FB3E9BB-E20A-4807-A4C3-F86A341304DB} = {0F647068-6602-4E24-B1DC-8ED91481A50A}
{BAC36757-9A47-43CB-A6F3-86E8C4650A28} = {52D59F18-62D2-4D17-8CF2-BE192445AF8E}
{C47C1165-9F19-4DF8-ABA9-707ACEB3BDC7} = {52D59F18-62D2-4D17-8CF2-BE192445AF8E}
{EA424B4D-0BE1-49AC-A106-CC6CC808A104} = {C47C1165-9F19-4DF8-ABA9-707ACEB3BDC7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {21D598B0-2383-4B22-826D-E7FB4921BD66}
EndGlobalSection
EndGlobal

View File

@ -5,11 +5,15 @@
<PropertyGroup Label="Package Versions">
<AngleSharpPackageVersion>0.9.9</AngleSharpPackageVersion>
<InternalAspNetCoreSdkPackageVersion>2.2.0-preview2-20181004.6</InternalAspNetCoreSdkPackageVersion>
<IdentityServer4PackageVersion>2.3.0-preview1-update2</IdentityServer4PackageVersion>
<IdentityServer4AspNetIdentityPackageVersion>2.3.0-preview1-update2</IdentityServer4AspNetIdentityPackageVersion>
<IdentityServer4EntityFrameworkPackageVersion>2.3.0-preview1-update1</IdentityServer4EntityFrameworkPackageVersion>
<MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>
<MicrosoftAspNetCoreAuthenticationFacebookPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreAuthenticationFacebookPackageVersion>
<MicrosoftAspNetCoreAuthenticationGooglePackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreAuthenticationGooglePackageVersion>
<MicrosoftAspNetCoreAuthenticationOpenIdConnectPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreAuthenticationOpenIdConnectPackageVersion>
<MicrosoftAspNetCoreAuthenticationTwitterPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreAuthenticationTwitterPackageVersion>
<MicrosoftAspNetCoreAuthenticationJwtBearerPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreAuthenticationJwtBearerPackageVersion>
<MicrosoftAspNetCoreAuthorizationPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreAuthorizationPackageVersion>
<MicrosoftAspNetCoreCookiePolicyPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreCookiePolicyPackageVersion>
<MicrosoftAspNetCoreCryptographyKeyDerivationPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreCryptographyKeyDerivationPackageVersion>

View File

@ -1,10 +1,6 @@
<Project>
<Import Project="dependencies.props" />
<ItemGroup>
<ExcludeSolutions Include="$(RepositoryRoot)IdentityCore.sln" />
<ExcludeSolutions Include="$(RepositoryRoot)Identity.Samples.sln" />
</ItemGroup>
<PropertyGroup>
<!-- These properties are use by the automation that updates dependencies.props -->
<LineupPackageId>Internal.AspNetCore.Universe.Lineup</LineupPackageId>

View File

@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.2;net461</TargetFrameworks>
<UserSecretsId>aspnet-ApiAuthSample-12ED8ECC-9EF1-4D31-87B4-1405B3198E5E</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.CookiePolicy" Version="$(MicrosoftAspNetCoreCookiePolicyPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.HttpsPolicy" Version="$(MicrosoftAspNetCoreHttpsPolicyPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.Extensions" Version="$(MicrosoftAspNetCoreDataProtectionExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="$(MicrosoftAspNetCoreDiagnosticsPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="$(MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(MicrosoftAspNetCoreHostingPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="$(MicrosoftAspNetCoreMvcPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Rewrite" Version="$(MicrosoftAspNetCoreRewritePackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(MicrosoftAspNetCoreServerIISIntegrationPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Https" Version="$(MicrosoftAspNetCoreServerKestrelHttpsPackageVersion)" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="$(MicrosoftEntityFrameworkCoreSqlServerPackageVersion)" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="$(MicrosoftEntityFrameworkCoreSqlitePackageVersion)" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" PrivateAssets="All" Version="$(MicrosoftEntityFrameworkCoreToolsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="$(MicrosoftExtensionsConfigurationEnvironmentVariablesPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="$(MicrosoftExtensionsConfigurationUserSecretsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="$(MicrosoftExtensionsConfigurationCommandLinePackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="$(MicrosoftExtensionsLoggingConfigurationPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(MicrosoftExtensionsLoggingDebugPackageVersion)" />
<PackageReference Include="Microsoft.NET.Sdk.Razor" Version="$(MicrosoftNETSdkRazorPackageVersion)" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\ApiAuth.IS\Microsoft.AspNetCore.ApiAuthorization.IdentityServer.csproj" />
<ProjectReference Include="..\..\src\EF\Microsoft.AspNetCore.Identity.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\..\src\UI\Microsoft.AspNetCore.Identity.UI.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,25 @@
@using Microsoft.AspNetCore.Identity
@using ApiAuthSample.Models;
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager
@if (SignInManager.IsSignedIn(User))
{
<form asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Page("/Index", new { area = "" })" method="post" id="logoutForm" class="navbar-right">
<ul class="nav navbar-nav navbar-right">
<li>
<a asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @UserManager.GetUserName(User)!</a>
</li>
<li>
<button type="submit" class="btn btn-link navbar-btn navbar-link">Logout</button>
</li>
</ul>
</form>
}
else
{
<ul class="nav navbar-nav navbar-right">
<li><a asp-area="Identity" asp-page="/Account/Register">Register</a></li>
<li><a asp-area="Identity" asp-page="/Account/Login">Login</a></li>
</ul>
}

View File

@ -0,0 +1,5 @@
@using Microsoft.AspNetCore.Identity
@using ApiAuthSample.Areas.Identity
@using ApiAuthSample.Models
@namespace ApiAuthSample.Areas.Identity.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -0,0 +1,44 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace ApiAuthSample.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
return "value";
}
// POST api/values
[HttpPost]
public void Post([FromBody] string value)
{
}
// PUT api/values/5
[HttpPut("{id}")]
public void Put(int id, [FromBody] string value)
{
}
// DELETE api/values/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}

View File

@ -0,0 +1,27 @@
using IdentityServer4.EntityFramework.Options;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using ApiAuthSample.Models;
namespace ApiAuthSample.Data
{
public class ApplicationDbContext : ApiAuthorizationDbContext<ApplicationUser>
{
public ApplicationDbContext(
DbContextOptions<ApplicationDbContext> options,
IOptions<OperationalStoreOptions> operationalStoreOptions)
: base(options, operationalStoreOptions)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
}
}

View File

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

View File

@ -0,0 +1,242 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace ApiAuthSample.Data.Migrations
{
public partial class InitialMigration : 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: "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_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: "PersistedGrants");
migrationBuilder.DropTable(
name: "AspNetRoles");
migrationBuilder.DropTable(
name: "AspNetUsers");
}
}
}

View File

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

View File

@ -0,0 +1,9 @@
using Microsoft.AspNetCore.Identity;
namespace ApiAuthSample.Models
{
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser
{
}
}

View File

@ -0,0 +1,33 @@
@page
@addTagHelper *, Microsoft.AspNetCore.ApiAuthorization.IdentityServer
@model ApiAuthSample.Pages.IndexModel
@{
ViewData["Title"] = "Index";
}
<!DOCTYPE html>
<html>
<head>
<title>@ViewData["Title"]</title>
</head>
<body>
<h1>ApiAuthSample SPA client</h1>
<button id="login">Login</button>
<button id="logout" disabled>Logout</button>
<button id="call-api" disabled>Call API</button>
<div id="login-result"></div>
<div id="api-result"></div>
<script src="js/oidc-client.js"></script>
<script id="apiauth" type="text/javascript" asp-apiauth-parameters="ApiAuthSampleSPA">
let $data = document.querySelector("#apiauth");
let configuration = {};
for (let key in $data.dataset) {
configuration[key] = $data.dataset[key];
}
let mgr = new Oidc.UserManager(configuration);
</script>
<script src="js/app.js"></script>
</body>
</html>

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace ApiAuthSample.Pages
{
public class IndexModel : PageModel
{
public void OnGet()
{
}
}
}

View File

@ -0,0 +1,75 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.IO;
using System.Reflection;
namespace ApiAuthSample
{
public class Program
{
public static void Main(string[] args)
{
if (args == null)
{
throw new ArgumentNullException(nameof(args));
}
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
var builder = new WebHostBuilder()
.UseKestrel((builderContext, options) =>
{
options.Configure(builderContext.Configuration.GetSection("Kestrel"));
})
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false);
if (env.IsDevelopment())
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
}
config.AddEnvironmentVariables();
if (args != null)
{
config.AddCommandLine(args);
}
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
})
.UseIISIntegration()
.UseDefaultServiceProvider((context, options) =>
{
options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
});
if (args != null)
{
builder.UseConfiguration(new ConfigurationBuilder().AddCommandLine(args).Build());
}
builder.UseStartup<Startup>();
return builder;
}
}
}

View File

@ -0,0 +1,33 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:14440",
"sslPort": 44316
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"ApiAuthSample": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}"
}
}
}

View File

@ -0,0 +1,60 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using ApiAuthSample.Data;
using ApiAuthSample.Models;
namespace ApiAuthSample
{
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.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<ApplicationUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddAuthentication()
.AddIdentityServerJwt();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseIdentityServer();
app.UseMvc();
}
}
}

View File

@ -0,0 +1,14 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Debug",
"Microsoft": "Debug"
}
},
"IdentityServer": {
"Key": {
"Type": "Development"
}
}
}

View File

@ -0,0 +1,13 @@
{
"ConnectionStrings": {
"DefaultConnection": "DataSource=app.db"
},
"IdentityServer": {
"Clients": {
"ApiAuthSampleSPA": {
"Profile": "IdentityServerSPA"
}
}
},
"AllowedHosts": "*"
}

60
samples/ApiAuthSample/package-lock.json generated Normal file
View File

@ -0,0 +1,60 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"babel-polyfill": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz",
"integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=",
"optional": true,
"requires": {
"babel-runtime": "6.26.0",
"core-js": "2.5.7",
"regenerator-runtime": "0.10.5"
}
},
"babel-runtime": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
"integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
"optional": true,
"requires": {
"core-js": "2.5.7",
"regenerator-runtime": "0.11.1"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
"optional": true
}
}
},
"core-js": {
"version": "2.5.7",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz",
"integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw=="
},
"jsrsasign": {
"version": "8.0.12",
"resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-8.0.12.tgz",
"integrity": "sha1-Iqu5ZW00owuVMENnIINeicLlwxY="
},
"oidc-client": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/oidc-client/-/oidc-client-1.5.2.tgz",
"integrity": "sha512-2w4TOraEf4PEbuo8mR8tSRhtgAHQaghBWgt2qpnwebsdW87BRMC7XKAytHcbZ1GYjFH9jJn30Cav64zbYdjiCQ==",
"requires": {
"babel-polyfill": "6.26.0",
"jsrsasign": "8.0.12"
}
},
"regenerator-runtime": {
"version": "0.10.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
"integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=",
"optional": true
}
}
}

View File

@ -0,0 +1,116 @@

function invokeLogin() {
// Redirects to the Authorization Server for sign in.
return mgr.signinRedirect();
}
function invokeLogout() {
// Redirects to the Authorization Server for sign out.
return mgr.signoutRedirect();
}
async function handleAuthorizationServerCallback() {
try {
let user = await mgr.signinRedirectCallback();
updateUserUI(user);
} catch (error) {
updateUserUI(undefined, error);
}
}
async function callApi() {
try {
let user = await mgr.getUser();
let response = await fetch(
window.location.origin + '/api/values',
{
method: 'GET',
headers: {
'Authorization': `Bearer ${user.access_token}`
}
});
if (response.ok) {
return await response.json();
} else {
let text = await response.text();
return text;
}
} catch (e) {
return e.message;
}
}
// Code to update the UI
if (window.location.hash) {
handleAuthorizationServerCallback();
window.location.hash = '';
}
let ids = {
login: 'login',
logout: 'logout',
callApi: 'call-api',
loginResult: 'login-result',
apiResults: 'api-result'
};
document.onreadystatechange = function () {
if (document.readyState === 'complete') {
let login = document.getElementById(ids.login);
let logout = document.getElementById(ids.logout);
let callApi = document.getElementById(ids.callApi);
login.addEventListener('click', invokeLogin);
logout.addEventListener('click', invokeLogout);
callApi.addEventListener('click', invokeCallApi);
}
};
function updateUserUI(user, error) {
let loginResults = document.getElementById(ids.loginResult);
let heading = document.createElement('h2');
heading.innerText = 'Login result';
if (user) {
loginResults.appendChild(heading);
loginResults.insertAdjacentText('beforeend', `Hello ${user.profile.name}`);
updateButtons(true, false, false);
} else {
loginResults.innerText = error.message;
}
}
function updateButtons(login, callApi, logout) {
let loginB = document.getElementById(ids.login);
let logoutB = document.getElementById(ids.logout);
let callApiB = document.getElementById(ids.callApi);
loginB.disabled = login;
logoutB.disabled = logout;
callApiB.disabled = callApi;
}
async function invokeCallApi() {
let result = await callApi();
let results = document.getElementById(ids.apiResults);
if (Array.isArray(result)) {
let list = document.createElement('ul');
let listElements = result.map(e => createListElement(e));
for (let element of listElements) {
list.appendChild(element);
}
let heading = document.createElement('h2');
heading.innerText = 'API call results';
results.appendChild(heading);
results.appendChild(list);
} else {
results.innerText = result;
}
function createListElement(element) {
let node = document.createElement('li');
node.innerText = element;
return node;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
</configuration>

View File

@ -1,9 +0,0 @@
<Application x:Class="NativeWPFClient.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NativeWPFClient"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>

View File

@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace NativeWPFClient
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}

View File

@ -1,25 +0,0 @@
<Window x:Class="NativeWPFClient.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:NativeWPFClient"
mc:Ignorable="d"
Title="MainWindow" Width="525" SizeToContent="Height">
<StackPanel Margin="0,0,0,0" VerticalAlignment="Stretch" Orientation="Vertical">
<Label Content="Base address" Margin="5,0" />
<TextBox Name="BaseAddress" Margin="5,0" TextWrapping="Wrap" Text="{Binding BaseAddress}" />
<Label Content="Redirect URI" Margin="5,0" />
<TextBox Name="RedirectUri" Margin="5,0" TextWrapping="Wrap" Text="{Binding RedirectUri}" />
<Label Content="Tenant" Margin="5,0" />
<TextBox Name="Tenant" Margin="5,0" TextWrapping="Wrap" Text="{Binding Tenant}" />
<Label Content="Policy" HorizontalAlignment="Left" Margin="5,0" />
<TextBox Name="Policy" Margin="5,0" TextWrapping="Wrap" Text="{Binding Policy}" />
<Label Content="Client ID" Margin="5,0" />
<TextBox Name="ClientID" Margin="5,0" TextWrapping="Wrap" Text="{Binding ClientId}" />
<Label Content="Scopes" Margin="5,0" />
<TextBox Name="Scopes" Margin="5,0" TextWrapping="Wrap" Text="{Binding Scopes}" />
<Button Name="Authorize" Margin="5,3" Click="Authorize_Click">Authorize</Button>
<Label Margin="5,0" Name="Result" Content="{Binding Result, Mode=TwoWay}"></Label>
</StackPanel>
</Window>

View File

@ -1,139 +0,0 @@
using Microsoft.Identity.Client;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace NativeWPFClient
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private Task _currentAuthorization;
public MainWindow()
{
InitializeComponent();
// Local client
DataContext = new NativeWPFClientViewModel
{
BaseAddress = "https://localhost:44324/",
RedirectUri = "urn:ietf:wg:oauth:2.0:oob",
Tenant = "Identity",
Policy = "signinsignup",
ClientId = "06D7C2FB-A66A-41AD-9509-77BDDFAB111B",
Scopes = "https://localhost/DFC7191F-FF74-42B9-A292-08FEA80F5B20/v2.0/ProtectedApi/read"
};
// DataContext = new NativeWPFClientViewModel
// {
// BaseAddress = "https://login.microsoftonline.com/",
// RedirectUri = "urn:ietf:wg:oauth:2.0:oob",
// Tenant = "jacalvarb2c.onmicrosoft.com",
// Policy = "B2C_1_signinsignup",
// ClientId = "42291769-0dc8-4497-9cbc-d3879783d6e7",
// Scopes = "https://jacalvarb2c.onmicrosoft.com/ProtectedApi/read"
// };
ViewModel.Result = "Hit authorize to sign in";
}
NativeWPFClientViewModel ViewModel => (NativeWPFClientViewModel)DataContext;
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
}
private async void Authorize_Click(object sender, RoutedEventArgs e)
{
if (_currentAuthorization == null)
{
Authorize.IsEnabled = false;
await AuthorizeAsync();
}
}
private async Task AuthorizeAsync()
{
var authority = $"{ViewModel.BaseAddress}tfp/{ViewModel.Tenant}/{ViewModel.Policy}";
var client = new PublicClientApplication(ViewModel.ClientId, authority)
{
ValidateAuthority = false
};
try
{
var scope = new string[] { };
var appScopes = ViewModel.Scopes.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
var currentAuthorization = await client.AcquireTokenAsync(
appScopes,
user: null,
behavior: UIBehavior.ForceLogin,
extraQueryParameters: string.Empty,
extraScopesToConsent: null,
authority: authority);
ViewModel.Result = currentAuthorization.User?.Name ?? "Authenticated but no name";
}
catch (MsalException ex)
{
if (ex.ErrorCode != "authentication_canceled")
{
// An unexpected error occurred.
string message = ex.Message;
if (ex.InnerException != null)
{
message += "Inner Exception : " + ex.InnerException.Message;
}
MessageBox.Show(message);
}
}
finally
{
_currentAuthorization = null;
Authorize.IsEnabled = true;
}
}
}
internal class NativeWPFClientViewModel : INotifyPropertyChanged
{
private string _result;
public string BaseAddress { get; set; }
public string RedirectUri { get; set; }
public string Tenant { get; set; }
public string Policy { get; set; }
public string ClientId { get; set; }
public string Scopes { get; set; }
public string Result
{
get => _result;
set
{
_result = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Result)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}

View File

@ -1,107 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<ImportDirectoryBuildTargets>false</ImportDirectoryBuildTargets>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{39AA4E4D-5E62-4213-8641-BF8012D45DE4}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>NativeWPFClient</RootNamespace>
<AssemblyName>NativeWPFClient</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<!-- Workaround for Visual Studio complaining about inexisting project.json file -->
<RuntimeIdentifier Condition="'$(BaseNuGetRuntimeIdentifier)' != ''">$(BaseNuGetRuntimeIdentifier)</RuntimeIdentifier>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<None Include="App.config">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Identity.Client">
<Version>1.1.0-preview</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -1,55 +0,0 @@
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("NativeWPFClient")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("NativeWPFClient")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
//In order to begin building localizable applications, set
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
//inside a <PropertyGroup>. For example, if you are using US english
//in your source files, set the <UICulture> to en-US. Then uncomment
//the NeutralResourceLanguage attribute below. Update the "en-US" in
//the line below to match the UICulture setting in the project file.
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -1,71 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace NativeWPFClient.Properties
{
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources
{
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources()
{
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if ((resourceMan == null))
{
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NativeWPFClient.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
return resourceCulture;
}
set
{
resourceCulture = value;
}
}
}
}

View File

@ -1,117 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -1,30 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace NativeWPFClient.Properties
{
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default
{
get
{
return defaultInstance;
}
}
}
}

View File

@ -1,7 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

View File

@ -0,0 +1,65 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using IdentityServer4.Stores;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Authentication;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Authentication
{
/// <summary>
/// Extension methods to configure authentication for existing APIs coexisting with an Authorization Server.
/// </summary>
public static class AuthenticationBuilderExtensions
{
private const string IdentityServerJwtNameSuffix = "API";
private static readonly PathString DefaultIdentityUIPathPrefix =
new PathString("/Identity");
/// <summary>
/// Adds an authentication handler for an API that coexists with an Authorization Server.
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <returns>The <see cref="AuthenticationBuilder"/>.</returns>
public static AuthenticationBuilder AddIdentityServerJwt(this AuthenticationBuilder builder)
{
var services = builder.Services;
services.TryAddSingleton<IIdentityServerJwtDescriptor, IdentityServerJwtDescriptor>();
services.TryAddEnumerable(ServiceDescriptor
.Transient<IConfigureOptions<JwtBearerOptions>, IdentityServerJwtBearerOptionsConfiguration>(JwtBearerOptionsFactory));
services.AddAuthentication(IdentityServerJwtConstants.IdentityServerJwtScheme)
.AddPolicyScheme(IdentityServerJwtConstants.IdentityServerJwtScheme, null, options =>
{
options.ForwardDefaultSelector = new IdentityServerJwtPolicySchemeForwardSelector(
DefaultIdentityUIPathPrefix,
IdentityServerJwtConstants.IdentityServerJwtBearerScheme).SelectScheme;
})
.AddJwtBearer(IdentityServerJwtConstants.IdentityServerJwtBearerScheme, null, o => { });
return builder;
IdentityServerJwtBearerOptionsConfiguration JwtBearerOptionsFactory(IServiceProvider sp)
{
var schemeName = IdentityServerJwtConstants.IdentityServerJwtBearerScheme;
var localApiDescriptor = sp.GetRequiredService<IIdentityServerJwtDescriptor>();
var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
var apiName = hostingEnvironment.ApplicationName + IdentityServerJwtNameSuffix;
return new IdentityServerJwtBearerOptionsConfiguration(schemeName, apiName, localApiDescriptor);
}
}
}
}

View File

@ -0,0 +1,77 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using IdentityServer4.Extensions;
using IdentityServer4.Stores;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
internal class IdentityServerJwtBearerOptionsConfiguration : IConfigureNamedOptions<JwtBearerOptions>
{
private readonly string _scheme;
private readonly string _apiName;
private readonly IIdentityServerJwtDescriptor _localApiDescriptor;
public IdentityServerJwtBearerOptionsConfiguration(
string scheme,
string apiName,
IIdentityServerJwtDescriptor localApiDescriptor)
{
_scheme = scheme;
_apiName = apiName;
_localApiDescriptor = localApiDescriptor;
}
public void Configure(string name, JwtBearerOptions options)
{
var definitions = _localApiDescriptor.GetResourceDefinitions();
if (!definitions.ContainsKey(_apiName))
{
return;
}
if (string.Equals(name, _scheme, StringComparison.Ordinal))
{
options.Events = options.Events ?? new JwtBearerEvents();
options.Events.OnMessageReceived = ResolveAuthorityAndKeysAsync;
options.Audience = _apiName;
var staticConfiguration = new OpenIdConnectConfiguration
{
Issuer = options.Authority
};
var manager = new StaticConfigurationManager(staticConfiguration);
options.ConfigurationManager = manager;
options.TokenValidationParameters.ValidIssuer = options.Authority;
options.TokenValidationParameters.NameClaimType = "name";
options.TokenValidationParameters.RoleClaimType = "role";
}
}
internal static async Task ResolveAuthorityAndKeysAsync(MessageReceivedContext messageReceivedContext)
{
var options = messageReceivedContext.Options;
if (options.TokenValidationParameters.ValidIssuer == null || options.TokenValidationParameters.IssuerSigningKey == null)
{
var store = messageReceivedContext.HttpContext.RequestServices.GetRequiredService<ISigningCredentialStore>();
var credential = await store.GetSigningCredentialsAsync();
options.Authority = options.Authority ?? messageReceivedContext.HttpContext.GetIdentityServerIssuerUri();
options.TokenValidationParameters.IssuerSigningKey = credential.Key;
options.TokenValidationParameters.ValidIssuer = options.Authority;
}
}
public void Configure(JwtBearerOptions options)
{
}
}
}

View File

@ -0,0 +1,21 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
/// <summary>
/// Constants for a default API authentication handler.
/// </summary>
public class IdentityServerJwtConstants
{
/// <summary>
/// Scheme used for the default API policy authentication scheme.
/// </summary>
public const string IdentityServerJwtScheme = "IdentityServerJwt";
/// <summary>
/// Scheme used for the underlying default API JwtBearer authentication scheme.
/// </summary>
public const string IdentityServerJwtBearerScheme = "IdentityServerJwtBearer";
}
}

View File

@ -0,0 +1,33 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Authentication
{
internal class IdentityServerJwtPolicySchemeForwardSelector
{
private readonly PathString _identityPath;
private readonly string _IdentityServerJwtScheme;
public IdentityServerJwtPolicySchemeForwardSelector(
string identityPath,
string IdentityServerJwtScheme)
{
_identityPath = identityPath;
_IdentityServerJwtScheme = IdentityServerJwtScheme;
}
public string SelectScheme(HttpContext ctx)
{
if (ctx.Request.Path.StartsWithSegments(_identityPath, StringComparison.OrdinalIgnoreCase))
{
return IdentityConstants.ApplicationScheme;
}
return _IdentityServerJwtScheme;
}
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
internal class StaticConfigurationManager : IConfigurationManager<OpenIdConnectConfiguration>
{
private readonly Task<OpenIdConnectConfiguration> _configuration;
public StaticConfigurationManager(OpenIdConnectConfiguration configuration) => _configuration = Task.FromResult(configuration);
public Task<OpenIdConnectConfiguration> GetConfigurationAsync(CancellationToken cancel) => _configuration;
public void RequestRefresh()
{
}
}
}

View File

@ -0,0 +1,41 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
/// <summary>
/// Constants for the different application profiles for applications in an authorization server.
/// </summary>
public static class ApplicationProfiles
{
/// <summary>
/// The application is an external API registered with the authorization server.
/// </summary>
public const string API = "API";
/// <summary>
/// The application is an API that coexists with the authorization server.
/// </summary>
public const string IdentityServerJwt = "IdentityServerJwt";
/// <summary>
/// The application is an external single page application registered with the authorization server.
/// </summary>
public const string SPA = "SPA";
/// <summary>
/// The application is a single page application that coexists with the authorization server.
/// </summary>
public const string IdentityServerSPA = "IdentityServerSPA";
/// <summary>
/// The application is a native application like a mobile or desktop application.
/// </summary>
public const string NativeApp = "NativeApp";
/// <summary>
/// The application is a web application.
/// </summary>
internal const string WebApplication = "WebApplication";
}
}

View File

@ -0,0 +1,33 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using IdentityServer4.Models;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
/// <summary>
/// Constants used for storing information about application profiles in the <see cref="Client.Properties"/> or <see cref="Resource.Properties"/>
/// of a <see cref="Client"/> or <see cref="ApiResource"/> respectively.
/// </summary>
public static class ApplicationProfilesPropertyNames
{
/// <summary>
/// Key to the Profile on <see cref="Client.Properties"/> or <see cref="Resource.Properties"/>.
/// The Profile value will be one of the constants in <see cref="ApplicationProfiles"/>.
/// </summary>
public const string Profile = nameof(Profile);
/// <summary>
/// Key to the Source on <see cref="Client.Properties"/> or <see cref="Resource.Properties"/>.
/// The Source value will be Configuration if present.
/// </summary>
public const string Source = nameof(Source);
/// <summary>
/// Key to the Clients on <see cref="Resource.Properties"/>.
/// The Clients value will be <c>*</c> to indicate that all clients are allowed to access this resource or a space separated list of
/// the client ids that are allowed to access this resource.
/// </summary>
public const string Clients = nameof(Clients);
}
}

View File

@ -0,0 +1,31 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using IdentityServer4.Models;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
/// <summary>
/// Constants for special values defined for specific <see cref="ApplicationProfilesPropertyNames" /> keys.
/// </summary>
public static class ApplicationProfilesPropertyValues
{
/// <summary>
/// The value given to <see cref="ApplicationProfilesPropertyNames.Clients"/> in <see cref="Resource.Properties"/> to indicate that the
/// resource can be accessed by all configured clients.
/// </summary>
public const string AllowAllApplications = "*";
/// <summary>
/// The value given to <see cref="ApplicationProfilesPropertyNames.Source"/> in <see cref="Resource.Properties"/> or <see cref="Client.Properties"/>
/// to indicate that the application was defined in configuration.
/// </summary>
public const string Configuration = nameof(Configuration);
/// <summary>
/// The value given to <see cref="ApplicationProfilesPropertyNames.Source"/> in <see cref="Resource.Properties"/>
/// to indicate that the resource was defined as a default identity resource.
/// </summary>
public const string Default = nameof(Default);
}
}

View File

@ -0,0 +1,24 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using IdentityServer4.Configuration;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
internal class AspNetConventionsConfigureOptions : IConfigureOptions<IdentityServerOptions>
{
public void Configure(IdentityServerOptions options)
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
options.Authentication.CookieAuthenticationScheme = IdentityConstants.ApplicationScheme;
options.UserInteraction.LoginUrl = "/Identity/Account/Login";
options.UserInteraction.LogoutUrl = "/Identity/Account/Logout";
options.UserInteraction.ErrorUrl = "/Identity/Error";
}
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
internal class ClientDefinition : ServiceDefinition
{
public string RedirectUri { get; set; }
public string LogoutUri { get; set; }
public string ClientSecret { get; set; }
}
}

View File

@ -0,0 +1,110 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using IdentityServer4.Models;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.DependencyInjection
{
internal class ConfigureApiResources : IConfigureOptions<ApiAuthorizationOptions>
{
private static readonly char[] ScopesSeparator = new char[] { ' ' };
private readonly IConfiguration _configuration;
private readonly ILogger<ConfigureApiResources> _logger;
private readonly IIdentityServerJwtDescriptor _localApiDescriptor;
public ConfigureApiResources(
IConfiguration configuration,
IIdentityServerJwtDescriptor localApiDescriptor,
ILogger<ConfigureApiResources> logger)
{
_configuration = configuration;
_localApiDescriptor = localApiDescriptor;
_logger = logger;
}
public void Configure(ApiAuthorizationOptions options)
{
var resources = GetApiResources();
foreach (var resource in resources)
{
options.ApiResources.Add(resource);
}
}
internal IEnumerable<ApiResource> GetApiResources()
{
var data = _configuration
.Get<Dictionary<string, ResourceDefinition>>();
if (data != null)
{
foreach (var kvp in data)
{
_logger.LogInformation($"Configuring API resource '{kvp.Key}'.");
yield return GetResource(kvp.Key, kvp.Value);
}
}
var localResources = _localApiDescriptor.GetResourceDefinitions();
if (localResources != null)
{
foreach (var kvp in localResources)
{
_logger.LogInformation($"Configuring local API resource '{kvp.Key}'.");
yield return GetResource(kvp.Key, kvp.Value);
}
}
}
public ApiResource GetResource(string name, ResourceDefinition definition)
{
switch (definition.Profile)
{
case ApplicationProfiles.API:
return GetAPI(name, definition);
case ApplicationProfiles.IdentityServerJwt:
return GetLocalAPI(name, definition);
default:
throw new InvalidOperationException($"Type '{definition.Profile}' is not supported.");
}
}
private string[] ParseScopes(string scopes)
{
if (scopes == null)
{
return null;
}
var parsed = scopes.Split(ScopesSeparator, StringSplitOptions.RemoveEmptyEntries);
if (parsed.Length == 0)
{
return null;
}
return parsed;
}
private ApiResource GetAPI(string name, ResourceDefinition definition) =>
ApiResourceBuilder.ApiResource(name)
.FromConfiguration()
.WithAllowedClients(ApplicationProfilesPropertyValues.AllowAllApplications)
.ReplaceScopes(ParseScopes(definition.Scopes) ?? new[] { name })
.Build();
private ApiResource GetLocalAPI(string name, ResourceDefinition definition) =>
ApiResourceBuilder.IdentityServerJwt(name)
.FromConfiguration()
.WithAllowedClients(ApplicationProfilesPropertyValues.AllowAllApplications)
.ReplaceScopes(ParseScopes(definition.Scopes) ?? new[] { name })
.Build();
}
}

View File

@ -0,0 +1,110 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using IdentityServer4.Models;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration
{
internal class ConfigureClientScopes : IPostConfigureOptions<ApiAuthorizationOptions>
{
private static readonly char[] DefaultClientListSeparator = new char[] { ' ' };
private readonly ILogger<ConfigureClientScopes> _logger;
public ConfigureClientScopes(ILogger<ConfigureClientScopes> logger)
{
_logger = logger;
}
public void PostConfigure(string name, ApiAuthorizationOptions options)
{
AddApiResourceScopesToClients(options);
AddIdentityResourceScopesToClients(options);
}
private void AddIdentityResourceScopesToClients(ApiAuthorizationOptions options)
{
foreach (var identityResource in options.IdentityResources)
{
if (!identityResource.Properties.TryGetValue(ApplicationProfilesPropertyNames.Clients, out var clientList))
{
_logger.LogInformation($"Identity resource '{identityResource.Name}' doesn't define a list of allowed applications.");
continue;
}
var resourceClients = clientList.Split(DefaultClientListSeparator, StringSplitOptions.RemoveEmptyEntries);
if (resourceClients.Length == 0)
{
_logger.LogInformation($"Identity resource '{identityResource.Name}' doesn't define a list of allowed applications.");
continue;
}
if (resourceClients.Length == 1 && resourceClients[0] == ApplicationProfilesPropertyValues.AllowAllApplications)
{
_logger.LogInformation($"Identity resource '{identityResource.Name}' allows all applications.");
}
else
{
_logger.LogInformation($"Identity resource '{identityResource.Name}' allows applications '{string.Join(" ", resourceClients)}'.");
}
foreach (var client in options.Clients)
{
if ((resourceClients.Length == 1 && resourceClients[0] == ApplicationProfilesPropertyValues.AllowAllApplications) ||
resourceClients.Contains(client.ClientId))
{
client.AllowedScopes.Add(identityResource.Name);
}
}
}
}
private void AddApiResourceScopesToClients(ApiAuthorizationOptions options)
{
foreach (var resource in options.ApiResources)
{
if (!resource.Properties.TryGetValue(ApplicationProfilesPropertyNames.Clients, out var clientList))
{
_logger.LogInformation($"Resource '{resource.Name}' doesn't define a list of allowed applications.");
continue;
}
var resourceClients = clientList.Split(DefaultClientListSeparator, StringSplitOptions.RemoveEmptyEntries);
if (resourceClients.Length == 0)
{
_logger.LogInformation($"Resource '{resource.Name}' doesn't define a list of allowed applications.");
continue;
}
if (resourceClients.Length == 1 && resourceClients[0] == ApplicationProfilesPropertyValues.AllowAllApplications)
{
_logger.LogInformation($"Resource '{resource.Name}' allows all applications.");
}
else
{
_logger.LogInformation($"Resource '{resource.Name}' allows applications '{string.Join(" ", resourceClients)}'.");
}
foreach (var client in options.Clients)
{
if ((resourceClients.Length == 1 && resourceClients[0] == ApplicationProfilesPropertyValues.AllowAllApplications) ||
resourceClients.Contains(client.ClientId))
{
AddScopes(resource, client);
}
}
}
}
private static void AddScopes(ApiResource resource, Client client)
{
foreach (var scope in resource.Scopes)
{
client.AllowedScopes.Add(scope.Name);
}
}
}
}

View File

@ -0,0 +1,165 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using IdentityServer4.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
internal class ConfigureClients : IConfigureOptions<ApiAuthorizationOptions>
{
private const string DefaultLocalSPARelativeRedirectUri = "";
private const string DefaultLocalSPARelativePostLogoutRedirectUri = "";
private readonly IConfiguration _configuration;
private readonly ILogger<ConfigureClients> _logger;
public ConfigureClients(
IConfiguration configuration,
ILogger<ConfigureClients> logger)
{
_configuration = configuration;
_logger = logger;
}
public void Configure(ApiAuthorizationOptions options)
{
foreach (var client in GetClients())
{
options.Clients.Add(client);
}
}
internal IEnumerable<Client> GetClients()
{
var data = _configuration.Get<Dictionary<string, ClientDefinition>>();
if (data != null)
{
foreach (var kvp in data)
{
_logger.LogInformation($"Configuring client '{kvp.Key}'.");
var name = kvp.Key;
var definition = kvp.Value;
switch (definition.Profile)
{
case ApplicationProfiles.SPA:
yield return GetSPA(name, definition);
break;
//case ApplicationProfiles.WebApplication:
// yield return GetWebApplication(name, definition);
// break;
case ApplicationProfiles.IdentityServerSPA:
yield return GetLocalSPA(name, definition);
break;
case ApplicationProfiles.NativeApp:
yield return GetNativeApp(name, definition);
break;
default:
throw new InvalidOperationException($"Type '{definition.Profile}' is not supported.");
}
}
}
}
private Client GetWebApplication(string name, ClientDefinition definition)
{
if (definition.RedirectUri == null ||
!Uri.TryCreate(definition.RedirectUri, UriKind.Absolute, out var redirectUri))
{
throw new InvalidOperationException($"The redirect uri " +
$"'{definition.RedirectUri}' for '{name}' is invalid. " +
$"The redirect URI must be an absolute url.");
}
if (definition.LogoutUri == null ||
!Uri.TryCreate(definition.LogoutUri, UriKind.Absolute, out var postLogouturi))
{
throw new InvalidOperationException($"The logout uri " +
$"'{definition.LogoutUri}' for '{name}' is invalid. " +
$"The logout URI must be an absolute url.");
}
if (!string.Equals(
redirectUri.GetLeftPart(UriPartial.Authority),
postLogouturi.GetLeftPart(UriPartial.Authority),
StringComparison.Ordinal))
{
throw new InvalidOperationException($"The redirect uri and the logout uri " +
$"for '{name}' have a different scheme, host or port.");
}
if (definition.ClientSecret == null)
{
throw new InvalidOperationException($"The configuration for '{name}' does not contain a client secret. " +
$"Client secrets are required for web applications.");
}
return ClientBuilder.WebApplication(name)
.WithRedirectUri(definition.RedirectUri)
.WithLogoutRedirectUri(definition.LogoutUri)
.FromConfiguration()
.WithClientSecret(definition.ClientSecret)
.Build();
}
private Client GetSPA(string name, ClientDefinition definition)
{
if (definition.RedirectUri == null ||
!Uri.TryCreate(definition.RedirectUri, UriKind.Absolute, out var redirectUri))
{
throw new InvalidOperationException($"The redirect uri " +
$"'{definition.RedirectUri}' for '{name}' is invalid. " +
$"The redirect URI must be an absolute url.");
}
if (definition.LogoutUri == null ||
!Uri.TryCreate(definition.LogoutUri, UriKind.Absolute, out var postLogouturi))
{
throw new InvalidOperationException($"The logout uri " +
$"'{definition.LogoutUri}' for '{name}' is invalid. " +
$"The logout URI must be an absolute url.");
}
if (!string.Equals(
redirectUri.GetLeftPart(UriPartial.Authority),
postLogouturi.GetLeftPart(UriPartial.Authority),
StringComparison.Ordinal))
{
throw new InvalidOperationException($"The redirect uri and the logout uri " +
$"for '{name}' have a different scheme, host or port.");
}
var client = ClientBuilder.SPA(name)
.WithRedirectUri(definition.RedirectUri)
.WithLogoutRedirectUri(definition.LogoutUri)
.WithAllowedOrigins(redirectUri.GetLeftPart(UriPartial.Authority))
.FromConfiguration();
return client.Build();
}
private Client GetNativeApp(string name, ClientDefinition definition)
{
var client = ClientBuilder.NativeApp(name)
.FromConfiguration();
return client.Build();
}
private Client GetLocalSPA(string name, ClientDefinition definition)
{
var client = ClientBuilder
.IdentityServerSPA(name)
.WithRedirectUri(definition.RedirectUri ?? DefaultLocalSPARelativeRedirectUri)
.WithLogoutRedirectUri(definition.LogoutUri ?? DefaultLocalSPARelativePostLogoutRedirectUri)
.WithAllowedOrigins()
.FromConfiguration();
return client.Build();
}
}
}

View File

@ -0,0 +1,108 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
internal class ConfigureIdentityResources : IConfigureOptions<ApiAuthorizationOptions>
{
private readonly IConfiguration _configuration;
private readonly ILogger<ConfigureIdentityResources> _logger;
private static readonly char[] ScopesSeparator = new char[] { ' ' };
public ConfigureIdentityResources(IConfiguration configuration, ILogger<ConfigureIdentityResources> logger)
{
_configuration = configuration;
_logger = logger;
}
public void Configure(ApiAuthorizationOptions options)
{
var data = _configuration.Get<IdentityResourceDefinition>();
if (data != null && data.Scopes != null)
{
var scopes = ParseScopes(data.Scopes);
if (scopes != null && scopes.Length > 0)
{
ClearDefaultIdentityResources(options);
}
foreach (var scope in scopes)
{
switch (scope)
{
case IdentityServer4.IdentityServerConstants.StandardScopes.OpenId:
options.IdentityResources.Add(IdentityResourceBuilder.OpenId()
.AllowAllClients()
.FromConfiguration()
.Build());
break;
case IdentityServer4.IdentityServerConstants.StandardScopes.Profile:
options.IdentityResources.Add(IdentityResourceBuilder.Profile()
.AllowAllClients()
.FromConfiguration()
.Build());
break;
case IdentityServer4.IdentityServerConstants.StandardScopes.Address:
options.IdentityResources.Add(IdentityResourceBuilder.Address()
.AllowAllClients()
.FromConfiguration()
.Build());
break;
case IdentityServer4.IdentityServerConstants.StandardScopes.Email:
options.IdentityResources.Add(IdentityResourceBuilder.Email()
.AllowAllClients()
.FromConfiguration()
.Build());
break;
case IdentityServer4.IdentityServerConstants.StandardScopes.Phone:
options.IdentityResources.Add(IdentityResourceBuilder.Phone()
.AllowAllClients()
.FromConfiguration()
.Build());
break;
default:
throw new InvalidOperationException($"Invalid identity resource name '{scope}'");
}
}
}
}
private static void ClearDefaultIdentityResources(ApiAuthorizationOptions options)
{
var allDefault = true;
foreach (var resource in options.IdentityResources)
{
if (!resource.Properties.TryGetValue(ApplicationProfilesPropertyNames.Source, out var source) ||
!string.Equals(ApplicationProfilesPropertyValues.Default, source, StringComparison.OrdinalIgnoreCase))
{
allDefault = false;
break;
}
}
if (allDefault)
{
options.IdentityResources.Clear();
}
}
private string[] ParseScopes(string scopes)
{
if (scopes == null)
{
return null;
}
var parsed = scopes.Split(ScopesSeparator, StringSplitOptions.RemoveEmptyEntries);
if (parsed.Length == 0)
{
return null;
}
return parsed;
}
}
}

View File

@ -0,0 +1,117 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
internal class ConfigureSigningCredentials : IConfigureOptions<ApiAuthorizationOptions>
{
// We need to cast the underlying int value of the EphemeralKeySet to X509KeyStorageFlags
// due to the fact that is not part of .NET Standard. This value is only used with non-windows
// platforms (all .NET Core) for which the value is defined on the underlying platform.
private const X509KeyStorageFlags UnsafeEphemeralKeySet = (X509KeyStorageFlags)32;
private const string DefaultTempKeyRelativePath = "obj/tempkey.json";
private readonly IConfiguration _configuration;
private readonly ILogger<ConfigureSigningCredentials> _logger;
public ConfigureSigningCredentials(
IConfiguration configuration,
ILogger<ConfigureSigningCredentials> logger)
{
_configuration = configuration;
_logger = logger;
}
public void Configure(ApiAuthorizationOptions options)
{
var key = LoadKey();
options.SigningCredential = key;
}
public SigningCredentials LoadKey()
{
var key = new KeyDefinition();
_configuration.Bind(key);
switch (key.Type)
{
case KeySources.Development:
var developmentKeyPath = Path.Combine(Directory.GetCurrentDirectory(), key.FilePath ?? DefaultTempKeyRelativePath);
var createIfMissing = key.Persisted ?? true;
_logger.LogInformation($"Loading development key at '{developmentKeyPath}'.");
var developmentKey = new RsaSecurityKey(SigningKeysLoader.LoadDevelopment(developmentKeyPath, createIfMissing))
{
KeyId = "Development"
};
return new SigningCredentials(developmentKey, "RS256");
case KeySources.File:
var pfxPath = Path.Combine(Directory.GetCurrentDirectory(), key.FilePath);
var pfxPassword = key.Password;
var storageFlags = GetStorageFlags(key);
_logger.LogInformation($"Loading certificate file at '{pfxPath}' with storage flags '{key.StorageFlags}'.");
return new SigningCredentials(new X509SecurityKey(SigningKeysLoader.LoadFromFile(pfxPath, key.Password, storageFlags)), "RS256");
case KeySources.Store:
if (!Enum.TryParse<StoreLocation>(key.StoreLocation, out var storeLocation))
{
throw new InvalidOperationException($"Invalid certificate store location '{key.StoreLocation}'.");
}
_logger.LogInformation($"Loading certificate with subject '{key.Name}' in '{key.StoreLocation}\\{key.StoreName}'.");
return new SigningCredentials(new X509SecurityKey(SigningKeysLoader.LoadFromStoreCert(key.Name, key.StoreName, storeLocation, GetCurrentTime())), "RS256");
case null:
throw new InvalidOperationException($"Key type not specified.");
default:
throw new InvalidOperationException($"Invalid key type '{key.Type ?? "(null)"}'.");
}
}
// for testing purposes only
internal virtual DateTimeOffset GetCurrentTime() => DateTimeOffset.UtcNow;
private X509KeyStorageFlags GetStorageFlags(KeyDefinition key)
{
var defaultFlags = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ?
UnsafeEphemeralKeySet : (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? X509KeyStorageFlags.PersistKeySet :
X509KeyStorageFlags.DefaultKeySet);
if (key.StorageFlags == null)
{
return defaultFlags;
}
var flagsList = key.StorageFlags.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (flagsList.Length == 0)
{
return defaultFlags;
}
var result = ParseCurrentFlag(flagsList[0]);
foreach (var flag in flagsList.Skip(1))
{
result |= ParseCurrentFlag(flag);
}
return result;
X509KeyStorageFlags ParseCurrentFlag(string candidate)
{
if (Enum.TryParse<X509KeyStorageFlags>(candidate, out var flag))
{
return flag;
}
else
{
throw new InvalidOperationException($"Invalid storage flag '{candidate}'");
}
}
}
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration
{
internal interface IIdentityServerJwtDescriptor
{
IDictionary<string, ResourceDefinition> GetResourceDefinitions();
}
}

View File

@ -0,0 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
internal class IdentityResourceDefinition : ResourceDefinition
{
public IdentityResourceDefinition()
{
Profile = "API";
}
}
}

View File

@ -0,0 +1,26 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Hosting;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration
{
internal class IdentityServerJwtDescriptor : IIdentityServerJwtDescriptor
{
public IdentityServerJwtDescriptor(IHostingEnvironment environment)
{
Environment = environment;
}
public IHostingEnvironment Environment { get; }
public IDictionary<string, ResourceDefinition> GetResourceDefinitions()
{
return new Dictionary<string, ResourceDefinition>
{
[Environment.ApplicationName + "API"] = new ResourceDefinition() { Profile = ApplicationProfiles.IdentityServerJwt }
};
}
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration
{
internal class KeyDefinition
{
public string Type { get; set; }
public bool? Persisted { get; set; }
public string FilePath { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public string StoreLocation { get; set; }
public string StoreName { get; set; }
public string StorageFlags { get; set; }
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration
{
internal class KeySources
{
public const string File = nameof(File);
public const string Development = nameof(Development);
public const string Store = nameof(Store);
}
}

View File

@ -0,0 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
internal class ResourceDefinition : ServiceDefinition
{
public string Scopes { get; set; }
}
}

View File

@ -0,0 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
internal class ServiceDefinition
{
public string Profile { get; set; }
}
}

View File

@ -0,0 +1,243 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Newtonsoft.Json;
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
internal static class SigningKeysLoader
{
public static X509Certificate2 LoadFromFile(string path, string password, X509KeyStorageFlags keyStorageFlags)
{
try
{
if (!File.Exists(path))
{
throw new InvalidOperationException($"There was an error loading the certificate. The file '{path}' was not found.");
}
else if (password == null)
{
throw new InvalidOperationException("There was an error loading the certificate. No password was provided.");
}
return new X509Certificate2(path, password, keyStorageFlags);
}
catch (CryptographicException e)
{
var message = "There was an error loading the certificate. Either the password is incorrect or the process does not have permisions to " +
$"store the key in the Keyset '{keyStorageFlags}'";
throw new InvalidOperationException(message, e);
}
}
public static X509Certificate2 LoadFromStoreCert(
string subject,
string storeName,
StoreLocation storeLocation,
DateTimeOffset currentTime)
{
using (var store = new X509Store(storeName, storeLocation))
{
X509Certificate2Collection storeCertificates = null;
X509Certificate2 foundCertificate = null;
try
{
store.Open(OpenFlags.ReadOnly);
storeCertificates = store.Certificates;
var foundCertificates = storeCertificates
.Find(X509FindType.FindBySubjectDistinguishedName, subject, validOnly: false);
foundCertificate = foundCertificates
.OfType<X509Certificate2>()
.Where(certificate => certificate.NotBefore <= currentTime && certificate.NotAfter > currentTime)
.OrderBy(certificate => certificate.NotAfter)
.FirstOrDefault();
if (foundCertificate == null)
{
throw new InvalidOperationException("Couldn't find a valid certificate with " +
$"subject '{subject}' on the '{storeLocation}\\{storeName}'");
}
return foundCertificate;
}
finally
{
DisposeCertificates(storeCertificates, except: foundCertificate);
}
}
}
public static RSA LoadDevelopment(string path, bool createIfMissing)
{
var fileExists = File.Exists(path);
if (!fileExists && !createIfMissing)
{
throw new InvalidOperationException($"Couldn't find the file '{path}' and creation of a development key was not requested.");
}
if (fileExists)
{
var rsa = JsonConvert.DeserializeObject<RSAKeyParameters>(File.ReadAllText(path));
return rsa.GetRSA();
}
else
{
var parameters = RSAKeyParameters.Create();
var directory = Path.GetDirectoryName(path);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
File.WriteAllText(path, JsonConvert.SerializeObject(parameters));
return parameters.GetRSA();
}
}
private class RSAKeyParameters
{
public string D { get; set; }
public string DP { get; set; }
public string DQ { get; set; }
public string E { get; set; }
public string IQ { get; set; }
public string M { get; set; }
public string P { get; set; }
public string Q { get; set; }
public static RSAKeyParameters Create()
{
using (var rsa = RSA.Create())
{
if (rsa is RSACryptoServiceProvider rSACryptoServiceProvider && rsa.KeySize < 2048)
{
rsa.KeySize = 2048;
if (rsa.KeySize < 2048)
{
throw new InvalidOperationException("We can't generate an RSA key with at least 2048 bits. Key generation is not supported in this system.");
}
}
return GetParameters(rsa);
}
}
public static RSAKeyParameters GetParameters(RSA key)
{
var result = new RSAKeyParameters();
var rawParameters = key.ExportParameters(includePrivateParameters: true);
if (rawParameters.D != null)
{
result.D = Convert.ToBase64String(rawParameters.D);
}
if (rawParameters.DP != null)
{
result.DP = Convert.ToBase64String(rawParameters.DP);
}
if (rawParameters.DQ != null)
{
result.DQ = Convert.ToBase64String(rawParameters.DQ);
}
if (rawParameters.Exponent != null)
{
result.E = Convert.ToBase64String(rawParameters.Exponent);
}
if (rawParameters.InverseQ != null)
{
result.IQ = Convert.ToBase64String(rawParameters.InverseQ);
}
if (rawParameters.Modulus != null)
{
result.M = Convert.ToBase64String(rawParameters.Modulus);
}
if (rawParameters.P != null)
{
result.P = Convert.ToBase64String(rawParameters.P);
}
if (rawParameters.Q != null)
{
result.Q = Convert.ToBase64String(rawParameters.Q);
}
return result;
}
public RSA GetRSA()
{
var parameters = new RSAParameters();
if (D != null)
{
parameters.D = Convert.FromBase64String(D);
}
if (DP != null)
{
parameters.DP = Convert.FromBase64String(DP);
}
if (DQ != null)
{
parameters.DQ = Convert.FromBase64String(DQ);
}
if (E != null)
{
parameters.Exponent = Convert.FromBase64String(E);
}
if (IQ != null)
{
parameters.InverseQ = Convert.FromBase64String(IQ);
}
if (M != null)
{
parameters.Modulus = Convert.FromBase64String(M);
}
if (P != null)
{
parameters.P = Convert.FromBase64String(P);
}
if (Q != null)
{
parameters.Q = Convert.FromBase64String(Q);
}
var rsa = RSA.Create();
rsa.ImportParameters(parameters);
return rsa;
}
}
private static void DisposeCertificates(X509Certificate2Collection certificates, X509Certificate2 except)
{
if (certificates != null)
{
foreach (var certificate in certificates)
{
if (!certificate.Equals(except))
{
certificate.Dispose();
}
}
}
}
}
}

View File

@ -0,0 +1,52 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
using IdentityServer4.EntityFramework.Entities;
using IdentityServer4.EntityFramework.Extensions;
using IdentityServer4.EntityFramework.Interfaces;
using IdentityServer4.EntityFramework.Options;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
/// <summary>
/// Database abstraction for a combined <see cref="DbContext"/> using ASP.NET Identity and Identity Server.
/// </summary>
/// <typeparam name="TUser"></typeparam>
public class ApiAuthorizationDbContext<TUser> : IdentityDbContext<TUser>, IPersistedGrantDbContext where TUser : IdentityUser
{
private readonly IOptions<OperationalStoreOptions> _operationalStoreOptions;
/// <summary>
/// Initializes a new instance of <see cref="ApiAuthorizationDbContext{TUser}"/>.
/// </summary>
/// <param name="options">The <see cref="DbContextOptions"/>.</param>
/// <param name="operationalStoreOptions">The <see cref="IOptions{OperationalStoreOptions}"/>.</param>
public ApiAuthorizationDbContext(
DbContextOptions options,
IOptions<OperationalStoreOptions> operationalStoreOptions)
: base(options)
{
_operationalStoreOptions = operationalStoreOptions;
}
/// <summary>
/// Gets or sets the <see cref="PersistedGrant"/>.
/// </summary>
public DbSet<PersistedGrant> PersistedGrants { get; set; }
Task<int> IPersistedGrantDbContext.SaveChangesAsync() => base.SaveChangesAsync();
/// <inheritdoc />
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value);
}
}
}

View File

@ -0,0 +1,63 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using System;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
internal class AbsoluteUrlFactory : IAbsoluteUrlFactory
{
public AbsoluteUrlFactory(IHttpContextAccessor httpContextAccessor)
{
// We need the context accessor here in order to produce an absolute url from a potentially relative url.
ContextAccessor = httpContextAccessor;
}
public IHttpContextAccessor ContextAccessor { get; }
// Call this method when you are overriding a service that doesn't have an HttpContext instance available.
public string GetAbsoluteUrl(string path)
{
var (process, result) = ShouldProcessPath(path);
if (!process)
{
return result;
}
if (ContextAccessor.HttpContext?.Request == null)
{
throw new InvalidOperationException("The request is not currently available. This service can only be used within the context of an existing HTTP request.");
}
return GetAbsoluteUrl(ContextAccessor.HttpContext, path);
}
// Call this method when you are implementing a service that has an HttpContext instance available.
public string GetAbsoluteUrl(HttpContext context, string path)
{
var (process, result) = ShouldProcessPath(path);
if (!process)
{
return result;
}
var request = context.Request;
return $"{request.Scheme}://{request.Host.ToUriComponent()}{request.PathBase.ToUriComponent()}{path}";
}
private (bool, string) ShouldProcessPath(string path)
{
if (path == null || !Uri.IsWellFormedUriString(path, UriKind.RelativeOrAbsolute))
{
return (false, null);
}
if (Uri.IsWellFormedUriString(path, UriKind.Absolute))
{
return (false, path);
}
return (true, path);
}
}
}

View File

@ -0,0 +1,124 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Specialized;
using System.Net;
using System.Threading.Tasks;
using IdentityServer4.Configuration;
using IdentityServer4.Endpoints.Results;
using IdentityServer4.Hosting;
using IdentityServer4.Services;
using IdentityServer4.Validation;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
internal class AutoRedirectEndSessionEndpoint : IEndpointHandler
{
private readonly ILogger _logger;
private readonly IUserSession _session;
private readonly IOptions<IdentityServerOptions> _identityServerOptions;
private readonly IEndSessionRequestValidator _requestvalidator;
public AutoRedirectEndSessionEndpoint(
ILogger<AutoRedirectEndSessionEndpoint> logger,
IEndSessionRequestValidator requestValidator,
IOptions<IdentityServerOptions> identityServerOptions,
IUserSession session)
{
_logger = logger;
_session = session;
_identityServerOptions = identityServerOptions;
_requestvalidator = requestValidator;
}
public async Task<IEndpointResult> ProcessAsync(HttpContext ctx)
{
var validtionResult = ValidateRequest(ctx.Request);
if (validtionResult != null)
{
return validtionResult;
}
var parameters = await GetParametersAsync(ctx.Request);
var user = await _session.GetUserAsync();
var result = await _requestvalidator.ValidateAsync(parameters, user);
if (result.IsError)
{
_logger.LogError($"Error ending session {result.Error}");
return new RedirectResult(_identityServerOptions.Value.UserInteraction.ErrorUrl);
}
var client = result.ValidatedRequest?.Client;
if (client != null &&
client.Properties.TryGetValue(ApplicationProfilesPropertyNames.Profile, out var type))
{
var signInScheme = _identityServerOptions.Value.Authentication.CookieAuthenticationScheme;
if (signInScheme != null)
{
await ctx.SignOutAsync(signInScheme);
}
else
{
await ctx.SignOutAsync();
}
return new RedirectResult(result.ValidatedRequest.PostLogOutUri);
}
else
{
return new RedirectResult(_identityServerOptions.Value.UserInteraction.LogoutUrl);
}
}
private async Task<NameValueCollection> GetParametersAsync(HttpRequest request)
{
if (HttpMethods.IsGet(request.Method))
{
return request.Query.AsNameValueCollection();
}
else
{
var form = await request.ReadFormAsync();
return form.AsNameValueCollection();
}
}
private IEndpointResult ValidateRequest(HttpRequest request)
{
if (!HttpMethods.IsPost(request.Method) && !HttpMethods.IsGet(request.Method))
{
return new StatusCodeResult(HttpStatusCode.BadRequest);
}
if (HttpMethods.IsPost(request.Method) &&
!string.Equals(request.ContentType, "application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase))
{
return new StatusCodeResult(HttpStatusCode.BadRequest);
}
return null;
}
internal class RedirectResult : IEndpointResult
{
public RedirectResult(string url)
{
Url = url;
}
public string Url { get; }
public Task ExecuteAsync(HttpContext context)
{
context.Response.Redirect(Url);
return Task.CompletedTask;
}
}
}
}

View File

@ -0,0 +1,65 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using IdentityServer4.Extensions;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
internal class DefaultClientRequestParametersProvider : IClientRequestParametersProvider
{
public DefaultClientRequestParametersProvider(
IAbsoluteUrlFactory urlFactory,
IOptions<ApiAuthorizationOptions> options)
{
UrlFactory = urlFactory;
Options = options;
}
public IAbsoluteUrlFactory UrlFactory { get; }
public IOptions<ApiAuthorizationOptions> Options { get; }
public IDictionary<string, string> GetClientParameters(HttpContext context, string clientId)
{
var client = Options.Value.Clients[clientId];
var authority = context.GetIdentityServerIssuerUri();
var responseType = "";
if (!client.Properties.TryGetValue(ApplicationProfilesPropertyNames.Profile, out var type))
{
throw new InvalidOperationException($"Can't determine the type for the client '{clientId}'");
}
switch (type)
{
case ApplicationProfiles.IdentityServerSPA:
case ApplicationProfiles.SPA:
responseType = "id_token token";
break;
case ApplicationProfiles.NativeApp:
responseType = "code";
break;
//case ApplicationProfiles.WebApplication:
// responseType = "id_token code";
// break;
default:
throw new InvalidOperationException($"Invalid application type '{type}' for '{clientId}'.");
}
return new Dictionary<string, string>
{
["authority"] = authority,
["client_id"] = client.ClientId,
["redirect_uri"] = UrlFactory.GetAbsoluteUrl(context, client.RedirectUris.First()),
["post_logout_redirect_uri"] = UrlFactory.GetAbsoluteUrl(context, client.RedirectUris.First()),
["response_type"] = responseType,
["scope"] = string.Join(" ", client.AllowedScopes)
};
}
}
}

View File

@ -0,0 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
internal interface IAbsoluteUrlFactory
{
string GetAbsoluteUrl(string path);
string GetAbsoluteUrl(HttpContext context, string path);
}
}

View File

@ -0,0 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
/// <summary>
/// Generates oauth/openID parameter values for configured clients.
/// </summary>
public interface IClientRequestParametersProvider
{
/// <summary>
/// Gets parameter values for the client with client id<paramref name="clientId"/>.
/// </summary>
/// <param name="context">The current <see cref="HttpContext"/>.</param>
/// <param name="clientId">The client id for the client.</param>
/// <returns>A <see cref="IDictionary{TKey, TValue}"/> containing the client parameters and their values.</returns>
IDictionary<string, string> GetClientParameters(HttpContext context, string clientId);
}
}

View File

@ -0,0 +1,71 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using IdentityServer4.Models;
using IdentityServer4.Validation;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
internal class RelativeRedirectUriValidator : StrictRedirectUriValidator
{
public RelativeRedirectUriValidator(IAbsoluteUrlFactory absoluteUrlFactory)
{
if (absoluteUrlFactory == null)
{
throw new ArgumentNullException(nameof(absoluteUrlFactory));
}
AbsoluteUrlFactory = absoluteUrlFactory;
}
public IAbsoluteUrlFactory AbsoluteUrlFactory { get; }
public override Task<bool> IsRedirectUriValidAsync(string requestedUri, Client client)
{
if (IsLocalSPA(client))
{
return ValidateRelativeUris(requestedUri, client.RedirectUris);
}
else
{
return base.IsRedirectUriValidAsync(requestedUri, client);
}
}
public override Task<bool> IsPostLogoutRedirectUriValidAsync(string requestedUri, Client client)
{
if (IsLocalSPA(client))
{
return ValidateRelativeUris(requestedUri, client.PostLogoutRedirectUris);
}
else
{
return base.IsPostLogoutRedirectUriValidAsync(requestedUri, client);
}
}
private static bool IsLocalSPA(Client client) =>
client.Properties.TryGetValue(ApplicationProfilesPropertyNames.Profile, out var clientType) &&
ApplicationProfiles.IdentityServerSPA == clientType;
private Task<bool> ValidateRelativeUris(string requestedUri, IEnumerable<string> clientUris)
{
foreach (var url in clientUris)
{
if (Uri.IsWellFormedUriString(url, UriKind.Relative))
{
var newUri = AbsoluteUrlFactory.GetAbsoluteUrl(url);
if (string.Equals(newUri, requestedUri, StringComparison.Ordinal))
{
return Task.FromResult(true);
}
}
}
return Task.FromResult(false);
}
}
}

View File

@ -0,0 +1,270 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using IdentityServer4.Configuration;
using IdentityServer4.EntityFramework.Interfaces;
using IdentityServer4.Hosting;
using IdentityServer4.Models;
using IdentityServer4.Stores;
using IdentityServer4.Validation;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Extension methods for configuring Identity Server.
/// </summary>
public static class IdentityServerBuilderConfigurationExtensions
{
/// <summary>
/// Configures defaults for Identity Server for ASP.NET Core scenarios.
/// </summary>
/// <typeparam name="TUser">The <typeparamref name="TUser"/> type.</typeparam>
/// <typeparam name="TContext">The <typeparamref name="TContext"/> type.</typeparam>
/// <param name="builder">The <see cref="IIdentityServerBuilder"/>.</param>
/// <returns>The <see cref="IIdentityServerBuilder"/>.</returns>
public static IIdentityServerBuilder AddApiAuthorization<TUser, TContext>(
this IIdentityServerBuilder builder) where TUser : class
where TContext : DbContext, IPersistedGrantDbContext
{
builder.AddApiAuthorization<TUser, TContext>(o => { });
return builder;
}
/// <summary>
/// Configures defaults on Identity Server for ASP.NET Core scenarios.
/// </summary>
/// <typeparam name="TUser">The <typeparamref name="TUser"/> type.</typeparam>
/// <typeparam name="TContext">The <typeparamref name="TContext"/> type.</typeparam>
/// <param name="builder">The <see cref="IIdentityServerBuilder"/>.</param>
/// <param name="configure">The <see cref="Action{ApplicationsOptions}"/>
/// to configure the <see cref="ApiAuthorizationOptions"/>.</param>
/// <returns>The <see cref="IIdentityServerBuilder"/>.</returns>
public static IIdentityServerBuilder AddApiAuthorization<TUser, TContext>(
this IIdentityServerBuilder builder,
Action<ApiAuthorizationOptions> configure)
where TUser : class
where TContext : DbContext, IPersistedGrantDbContext
{
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
builder.AddAspNetIdentity<TUser>()
.AddOperationalStore<TContext>()
.ConfigureReplacedServices()
.AddIdentityResources()
.AddApiResources()
.AddClients()
.AddSigningCredentials();
builder.Services.Configure(configure);
return builder;
}
/// <summary>
/// Adds API resources from the default configuration to the server using the key
/// IdentityServer:Resources
/// </summary>
/// <param name="builder">The <see cref="IIdentityServerBuilder"/>.</param>
/// <returns>The <see cref="IIdentityServerBuilder"/>.</returns>
public static IIdentityServerBuilder AddApiResources(
this IIdentityServerBuilder builder) => builder.AddApiResources(configuration: null);
/// <summary>
/// Adds API resources from the given <paramref name="configuration"/> instance.
/// </summary>
/// <param name="builder">The <see cref="IIdentityServerBuilder"/>.</param>
/// <param name="configuration">The <see cref="IConfiguration"/> instance containing the API definitions.</param>
/// <returns>The <see cref="IIdentityServerBuilder"/>.</returns>
public static IIdentityServerBuilder AddApiResources(
this IIdentityServerBuilder builder,
IConfiguration configuration)
{
builder.ConfigureReplacedServices();
builder.AddInMemoryApiResources(Enumerable.Empty<ApiResource>());
builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<IConfigureOptions<ApiAuthorizationOptions>, ConfigureApiResources>(sp =>
{
var logger = sp.GetRequiredService<ILogger<ConfigureApiResources>>();
var effectiveConfig = configuration ?? sp.GetRequiredService<IConfiguration>().GetSection("IdentityServer:Resources");
var localApiDescriptor = sp.GetService<IIdentityServerJwtDescriptor>();
return new ConfigureApiResources(effectiveConfig, localApiDescriptor, logger);
}));
// We take over the setup for the API resources as Identity Server registers the enumerable as a singleton
// and that prevents normal composition.
builder.Services.AddSingleton<IEnumerable<ApiResource>>(sp =>
{
var options = sp.GetRequiredService<IOptions<ApiAuthorizationOptions>>();
return options.Value.ApiResources;
});
return builder;
}
/// <summary>
/// Adds identity resources from the default configuration to the server using the key
/// IdentityServer:Resources
/// </summary>
/// <param name="builder">The <see cref="IIdentityServerBuilder"/>.</param>
/// <returns>The <see cref="IIdentityServerBuilder"/>.</returns>
public static IIdentityServerBuilder AddIdentityResources(
this IIdentityServerBuilder builder) => builder.AddIdentityResources(configuration: null);
/// <summary>
/// Adds identity resources from the given <paramref name="configuration"/> instance.
/// </summary>
/// <param name="builder">The <see cref="IIdentityServerBuilder"/>.</param>
/// <param name="configuration">The <see cref="IConfiguration"/> instance containing the API definitions.</param>
/// <returns>The <see cref="IIdentityServerBuilder"/>.</returns>
public static IIdentityServerBuilder AddIdentityResources(
this IIdentityServerBuilder builder,
IConfiguration configuration)
{
builder.ConfigureReplacedServices();
builder.AddInMemoryIdentityResources(Enumerable.Empty<IdentityResource>());
builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<IConfigureOptions<ApiAuthorizationOptions>, ConfigureIdentityResources>(sp =>
{
var logger = sp.GetRequiredService<ILogger<ConfigureIdentityResources>>();
var effectiveConfig = configuration ?? sp.GetRequiredService<IConfiguration>().GetSection("IdentityServer:Identity");
return new ConfigureIdentityResources(effectiveConfig, logger);
}));
// We take over the setup for the identity resources as Identity Server registers the enumerable as a singleton
// and that prevents normal composition.
builder.Services.AddSingleton<IEnumerable<IdentityResource>>(sp =>
{
var options = sp.GetRequiredService<IOptions<ApiAuthorizationOptions>>();
return options.Value.IdentityResources;
});
return builder;
}
/// <summary>
/// Adds clients from the default configuration to the server using the key
/// IdentityServer:Clients
/// </summary>
/// <param name="builder">The <see cref="IIdentityServerBuilder"/>.</param>
/// <returns>The <see cref="IIdentityServerBuilder"/>.</returns>
public static IIdentityServerBuilder AddClients(
this IIdentityServerBuilder builder) => builder.AddClients(configuration: null);
/// <summary>
/// Adds clients from the given <paramref name="configuration"/> instance.
/// </summary>
/// <param name="builder">The <see cref="IIdentityServerBuilder"/>.</param>
/// <param name="configuration">The <see cref="IConfiguration"/> instance containing the client definitions.</param>
/// <returns>The <see cref="IIdentityServerBuilder"/>.</returns>
public static IIdentityServerBuilder AddClients(
this IIdentityServerBuilder builder,
IConfiguration configuration)
{
builder.ConfigureReplacedServices();
builder.AddInMemoryClients(Enumerable.Empty<Client>());
builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPostConfigureOptions<ApiAuthorizationOptions>, ConfigureClientScopes>());
builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<IConfigureOptions<ApiAuthorizationOptions>, ConfigureClients>(sp =>
{
var logger = sp.GetRequiredService<ILogger<ConfigureClients>>();
var effectiveConfig = configuration ?? sp.GetRequiredService<IConfiguration>().GetSection("IdentityServer:Clients");
return new ConfigureClients(effectiveConfig, logger);
}));
// We take over the setup for the clients as Identity Server registers the enumerable as a singleton and that prevents normal composition.
builder.Services.AddSingleton<IEnumerable<Client>>(sp =>
{
var options = sp.GetRequiredService<IOptions<ApiAuthorizationOptions>>();
return options.Value.Clients;
});
return builder;
}
/// <summary>
/// Adds a signing key from the default configuration to the server using the configuration key
/// IdentityServer:Key
/// </summary>
/// <param name="builder">The <see cref="IIdentityServerBuilder"/>.</param>
/// <returns>The <see cref="IIdentityServerBuilder"/>.</returns>
public static IIdentityServerBuilder AddSigningCredentials(
this IIdentityServerBuilder builder) => builder.AddSigningCredentials(configuration: null);
/// <summary>
/// Adds a signing key from the given <paramref name="configuration"/> instance.
/// </summary>
/// <param name="builder">The <see cref="IIdentityServerBuilder"/>.</param>
/// <param name="configuration">The <see cref="IConfiguration"/>.</param>
/// <returns>The <see cref="IIdentityServerBuilder"/>.</returns>
public static IIdentityServerBuilder AddSigningCredentials(
this IIdentityServerBuilder builder,
IConfiguration configuration)
{
builder.ConfigureReplacedServices();
builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<IConfigureOptions<ApiAuthorizationOptions>, ConfigureSigningCredentials>(sp =>
{
var logger = sp.GetRequiredService<ILogger<ConfigureSigningCredentials>>();
var effectiveConfig = configuration ?? sp.GetRequiredService<IConfiguration>().GetSection("IdentityServer:Key");
return new ConfigureSigningCredentials(effectiveConfig, logger);
}));
// We take over the setup for the credentials store as Identity Server registers a singleton
builder.Services.AddSingleton<ISigningCredentialStore>(sp =>
{
var options = sp.GetRequiredService<IOptions<ApiAuthorizationOptions>>();
return new DefaultSigningCredentialsStore(options.Value.SigningCredential);
});
// We take over the setup for the validation keys store as Identity Server registers a singleton
builder.Services.AddSingleton<IValidationKeysStore>(sp =>
{
var options = sp.GetRequiredService<IOptions<ApiAuthorizationOptions>>();
return new DefaultValidationKeysStore(new[] { options.Value.SigningCredential.Key });
});
return builder;
}
internal static IIdentityServerBuilder ConfigureReplacedServices(this IIdentityServerBuilder builder)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<IdentityServerOptions>, AspNetConventionsConfigureOptions>());
builder.Services.TryAddSingleton<IAbsoluteUrlFactory, AbsoluteUrlFactory>();
builder.Services.AddSingleton<IRedirectUriValidator, RelativeRedirectUriValidator>();
builder.Services.AddSingleton<IClientRequestParametersProvider, DefaultClientRequestParametersProvider>();
ReplaceEndSessionEndpoint(builder);
return builder;
}
private static void ReplaceEndSessionEndpoint(IIdentityServerBuilder builder)
{
// We don't have a better way to replace the end session endpoint as far as we know other than looking the descriptor up
// on the container and replacing the instance. This is due to the fact that we chain on AddIdentityServer which configures the
// list of endpoints by default.
var endSessionEndpointDescriptor = builder.Services
.Single(s => s.ImplementationInstance is Endpoint e &&
string.Equals(e.Name, "Endsession", StringComparison.OrdinalIgnoreCase) &&
string.Equals("/connect/endsession", e.Path, StringComparison.OrdinalIgnoreCase));
builder.Services.Remove(endSessionEndpointDescriptor);
builder.AddEndpoint<AutoRedirectEndSessionEndpoint>("EndSession", "/connect/endsession");
}
}
}

View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Description>ASP.NET Core API Authorization package powered by Identity Server.</Description>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>aspnetcore;apiauth;identity</PackageTags>
<EnableApiCheck>false</EnableApiCheck>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="IdentityServer4" Version="$(IdentityServer4PackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="$(MicrosoftAspNetCoreMvcPackageVersion)" />
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="$(IdentityServer4AspNetIdentityPackageVersion)" />
<PackageReference Include="IdentityServer4.EntityFramework" Version="$(IdentityServer4EntityFrameworkPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(MicrosoftAspNetCoreAuthenticationJwtBearerPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EF\Microsoft.AspNetCore.Identity.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\UI\Microsoft.AspNetCore.Identity.UI.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,46 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.IdentityModel.Tokens;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
/// <summary>
/// Options for API authorization.
/// </summary>
public class ApiAuthorizationOptions
{
/// <summary>
/// Gets or sets the <see cref="IdentityResources"/>.
/// </summary>
public IdentityResourceCollection IdentityResources { get; set; } =
new IdentityResourceCollection
{
IdentityResourceBuilder.OpenId()
.AllowAllClients()
.FromDefault()
.Build(),
IdentityResourceBuilder.Profile()
.AllowAllClients()
.FromDefault()
.Build()
};
/// <summary>
/// Gets or sets the <see cref="ApiResources"/>.
/// </summary>
public ApiResourceCollection ApiResources { get; set; } =
new ApiResourceCollection();
/// <summary>
/// Gets or sets the <see cref="Clients"/>.
/// </summary>
public ClientCollection Clients { get; set; } =
new ClientCollection();
/// <summary>
/// Gets or sets the <see cref="SigningCredentials"/> to use for signing tokens.
/// </summary>
public SigningCredentials SigningCredential { get; set; }
}
}

View File

@ -0,0 +1,138 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using IdentityServer4.Models;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
/// <summary>
/// A builder for API resources
/// </summary>
public class ApiResourceBuilder
{
private ApiResource _apiResource;
private bool _built;
/// <summary>
/// Creates a new builder for an externally registered API.
/// </summary>
/// <param name="name">The name of the API.</param>
/// <returns>An <see cref="ApiResourceBuilder"/>.</returns>
public static ApiResourceBuilder ApiResource(string name)
{
var apiResource = new ApiResource(name);
return new ApiResourceBuilder(apiResource)
.WithApplicationProfile(ApplicationProfiles.API);
}
/// <summary>
/// Creates a new builder for an API that coexists with an authorization server.
/// </summary>
/// <param name="name">The name of the API.</param>
/// <returns>An <see cref="ApiResourceBuilder"/>.</returns>
public static ApiResourceBuilder IdentityServerJwt(string name)
{
var apiResource = new ApiResource(name);
return new ApiResourceBuilder(apiResource)
.WithApplicationProfile(ApplicationProfiles.IdentityServerJwt);
}
/// <summary>
/// Initializes a new instance of <see cref="ApiResourceBuilder"/>.
/// </summary>
public ApiResourceBuilder() : this(new ApiResource())
{
}
/// <summary>
/// Initializes a new instance of <see cref="ApiResourceBuilder"/>.
/// </summary>
/// <param name="resource">A preconfigured resource.</param>
public ApiResourceBuilder(ApiResource resource)
{
_apiResource = resource;
}
/// <summary>
/// Sets the application profile for the resource.
/// </summary>
/// <param name="profile">The the profile for the application from <see cref="ApplicationProfiles"/>.</param>
/// <returns>The <see cref="ApiResourceBuilder"/>.</returns>
public ApiResourceBuilder WithApplicationProfile(string profile)
{
_apiResource.Properties.Add(ApplicationProfilesPropertyNames.Profile, profile);
return this;
}
/// <summary>
/// Adds additional scopes to the API resource.
/// </summary>
/// <param name="resourceScopes">The list of scopes.</param>
/// <returns>The <see cref="ApiResourceBuilder"/>.</returns>
public ApiResourceBuilder WithScopes(params string[] resourceScopes)
{
foreach (var scope in resourceScopes)
{
if (_apiResource.Scopes.Any(s => s.Name == scope))
{
continue;
}
_apiResource.Scopes.Add(new Scope(scope));
}
return this;
}
/// <summary>
/// Replaces the scopes defined for the application with a new set of scopes.
/// </summary>
/// <param name="resourceScopes">The list of scopes.</param>
/// <returns>The <see cref="ApiResourceBuilder"/>.</returns>
public ApiResourceBuilder ReplaceScopes(params string[] resourceScopes)
{
_apiResource.Scopes.Clear();
return WithScopes(resourceScopes);
}
/// <summary>
/// Configures the API resource to allow all clients to access it.
/// </summary>
/// <returns>The <see cref="ApiResourceBuilder"/>.</returns>
public ApiResourceBuilder AllowAllClients()
{
_apiResource.Properties[ApplicationProfilesPropertyNames.Clients] = ApplicationProfilesPropertyValues.AllowAllApplications;
return this;
}
/// <summary>
/// Builds the API resource.
/// </summary>
/// <returns>The built <see cref="IdentityServer4.Models.ApiResource"/>.</returns>
public ApiResource Build()
{
if (_built)
{
throw new InvalidOperationException("ApiResource already built.");
}
_built = true;
return _apiResource;
}
internal ApiResourceBuilder WithAllowedClients(string clientList)
{
_apiResource.Properties[ApplicationProfilesPropertyNames.Clients] = clientList;
return this;
}
internal ApiResourceBuilder FromConfiguration()
{
_apiResource.Properties[ApplicationProfilesPropertyNames.Source] = ApplicationProfilesPropertyValues.Configuration;
return this;
}
}
}

View File

@ -0,0 +1,102 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using IdentityServer4.Models;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
/// <summary>
/// A collection of <see cref="ApiResource"/>.
/// </summary>
public class ApiResourceCollection : Collection<ApiResource>
{
/// <summary>
/// Initializes a new instance of <see cref="ApiResourceCollection"/>.
/// </summary>
public ApiResourceCollection()
{
}
/// <summary>
/// Initializes a new instance of <see cref="ApiResourceCollection"/> with the given
/// API resources in <paramref name="list"/>.
/// </summary>
/// <param name="list">The initial list of <see cref="ApiResource"/>.</param>
public ApiResourceCollection(IList<ApiResource> list) : base(list)
{
}
/// <summary>
/// Gets an API resource given its name.
/// </summary>
/// <param name="key">The name of the <see cref="ApiResource"/>.</param>
/// <returns>The <see cref="ApiResource"/>.</returns>
public ApiResource this[string key]
{
get
{
for (int i = 0; i < Items.Count; i++)
{
var candidate = Items[i];
if (string.Equals(candidate.Name, key, StringComparison.Ordinal))
{
return candidate;
}
}
throw new InvalidOperationException($"ApiResource '{key}' not found.");
}
}
/// <summary>
/// Adds the resources in <paramref name="resources"/> to the collection.
/// </summary>
/// <param name="resources">The list of <see cref="ApiResource"/> to add.</param>
public void AddRange(IEnumerable<ApiResource> resources)
{
foreach (var resource in resources)
{
Add(resource);
}
}
/// <summary>
/// Adds the resources in <paramref name="resources"/> to the collection.
/// </summary>
/// <param name="resources">The list of <see cref="ApiResource"/> to add.</param>
public void AddRange(params ApiResource[] resources)
{
foreach (var resource in resources)
{
Add(resource);
}
}
/// <summary>
/// Adds a new externally registered API.
/// </summary>
/// <param name="name">The name of the API.</param>
/// <param name="configure">The <see cref="Action{ApiResourceBuilder}"/> to configure the externally registered API.</param>
public void AddApiResource(string name, Action<ApiResourceBuilder> configure)
{
var apiResource = ApiResourceBuilder.ApiResource(name);
configure(apiResource);
Add(apiResource.Build());
}
/// <summary>
/// Creates a new API that coexists with an authorization server.
/// </summary>
/// <param name="name">The name of the API.</param>
/// <param name="configure">The <see cref="Func{ApiResourceBuilder, ApiResource}"/> to configure the identity server jwt API.</param>
public void AddIdentityServerJwt(string name, Action<ApiResourceBuilder> configure)
{
var apiResource = ApiResourceBuilder.IdentityServerJwt(name);
configure(apiResource);
Add(apiResource.Build());
}
}
}

View File

@ -0,0 +1,245 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using IdentityServer4;
using IdentityServer4.Models;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
/// <summary>
/// A builder for Clients.
/// </summary>
public class ClientBuilder
{
private const string NativeAppClientRedirectUri = "urn:ietf:wg:oauth:2.0:oob";
Client _client;
private bool _built = false;
/// <summary>
/// Creates a new builder for a single page application that coexists with an authorization server.
/// </summary>
/// <param name="clientId">The client id for the single page application.</param>
/// <returns>A <see cref="ClientBuilder"/>.</returns>
public static ClientBuilder IdentityServerSPA(string clientId)
{
var client = CreateClient(clientId);
return new ClientBuilder(client)
.WithApplicationProfile(ApplicationProfiles.IdentityServerSPA)
.WithAllowedGrants(GrantTypes.Implicit)
.WithAllowedOrigins(Array.Empty<string>())
.AllowAccessTokensViaBrowser();
}
/// <summary>
/// Creates a new builder for an externally registered single page application.
/// </summary>
/// <param name="clientId">The client id for the single page application.</param>
/// <returns>A <see cref="ClientBuilder"/>.</returns>
public static ClientBuilder SPA(string clientId)
{
var client = CreateClient(clientId);
return new ClientBuilder(client)
.WithApplicationProfile(ApplicationProfiles.SPA)
.WithAllowedGrants(GrantTypes.Implicit)
.AllowAccessTokensViaBrowser();
}
/// <summary>
/// Creates a new builder for an externally registered native application.
/// </summary>
/// <param name="clientId">The client id for the native application.</param>
/// <returns>A <see cref="ClientBuilder"/>.</returns>
public static ClientBuilder NativeApp(string clientId)
{
var client = CreateClient(clientId);
return new ClientBuilder(client)
.WithApplicationProfile(ApplicationProfiles.NativeApp)
.WithAllowedGrants(GrantTypes.Code)
.WithRedirectUri(NativeAppClientRedirectUri)
.WithLogoutRedirectUri(NativeAppClientRedirectUri)
.WithPkce()
.WithoutClientSecrets()
.WithScopes(IdentityServerConstants.StandardScopes.OfflineAccess);
}
/// <summary>
/// Creates a new builder for an externally registered web application.
/// </summary>
/// <param name="clientId">The client id for the web application.</param>
/// <returns>A <see cref="ClientBuilder"/>.</returns>
internal static ClientBuilder WebApplication(string clientId)
{
var client = CreateClient(clientId);
return new ClientBuilder(client)
.WithApplicationProfile(ApplicationProfiles.WebApplication)
.WithAllowedGrants(GrantTypes.HybridAndClientCredentials)
.WithScopes(IdentityServerConstants.StandardScopes.OfflineAccess);
}
/// <summary>
/// Initializes a new instance of <see cref="ClientBuilder"/>.
/// </summary>
public ClientBuilder() : this(new Client())
{
}
/// <summary>
/// Initializes a new intance of <see cref="ClientBuilder"/>.
/// </summary>
/// <param name="client">A preconfigured client.</param>
public ClientBuilder(Client client)
{
_client = client;
}
/// <summary>
/// Updates the client id (and name) of the client.
/// </summary>
/// <param name="clientId">The new client id.</param>
/// <returns>The <see cref="ClientBuilder"/>.</returns>
public ClientBuilder WithClientId(string clientId)
{
_client.ClientId = clientId;
_client.ClientName = clientId;
return this;
}
/// <summary>
/// Sets the application profile for the client.
/// </summary>
/// <param name="profile">The the profile for the application from <see cref="ApplicationProfiles"/>.</param>
/// <returns>The <see cref="ClientBuilder"/>.</returns>
public ClientBuilder WithApplicationProfile(string profile)
{
_client.Properties.Add(ApplicationProfilesPropertyNames.Profile, profile);
return this;
}
/// <summary>
/// Adds the <paramref name="scopes"/> to the list of allowed scopes for the client.
/// </summary>
/// <param name="scopes">The list of scopes.</param>
/// <returns>The <see cref="ClientBuilder"/>.</returns>
public ClientBuilder WithScopes(params string[] scopes)
{
foreach (var scope in scopes)
{
_client.AllowedScopes.Add(scope);
}
return this;
}
/// <summary>
/// Adds the <paramref name="redirectUri"/> to the list of valid redirect uris for the client.
/// </summary>
/// <param name="redirectUri">The redirect uri to add.</param>
/// <returns>The <see cref="ClientBuilder"/>.</returns>
public ClientBuilder WithRedirectUri(string redirectUri)
{
_client.RedirectUris.Add(redirectUri);
return this;
}
/// <summary>
/// Adds the <paramref name="logoutUri"/> to the list of valid logout redirect uris for the client.
/// </summary>
/// <param name="logoutUri">The logout uri to add.</param>
/// <returns>The <see cref="ClientBuilder"/>.</returns>
public ClientBuilder WithLogoutRedirectUri(string logoutUri)
{
_client.PostLogoutRedirectUris.Add(logoutUri);
return this;
}
/// <summary>
/// Adds the <paramref name="clientSecret"/> to the list of client secrets for the client and configures the client to
/// require using the secret when getting tokens from the token endpoint.
/// </summary>
/// <param name="clientSecret">The client secret to add.</param>
/// <returns>The <see cref="ClientBuilder"/>.</returns>
internal ClientBuilder WithClientSecret(string clientSecret)
{
_client.ClientSecrets.Add(new Secret(clientSecret));
_client.RequireClientSecret = true;
return this;
}
/// <summary>
/// Removes any configured client secret from the client and configures it to not require a client secret for getting tokens
/// from the token endpoint.
/// </summary>
/// <returns>The <see cref="ClientBuilder"/>.</returns>
public ClientBuilder WithoutClientSecrets()
{
_client.RequireClientSecret = false;
_client.ClientSecrets.Clear();
return this;
}
/// <summary>
/// Builds the client.
/// </summary>
/// <returns>The built <see cref="Client"/>.</returns>
public Client Build()
{
if (_built)
{
throw new InvalidOperationException("Client already built.");
}
_built = true;
return _client;
}
internal ClientBuilder WithPkce()
{
_client.RequirePkce = true;
_client.AllowPlainTextPkce = false;
return this;
}
internal ClientBuilder FromConfiguration()
{
_client.Properties[ApplicationProfilesPropertyNames.Source] = ApplicationProfilesPropertyValues.Configuration;
return this;
}
internal ClientBuilder WithAllowedGrants(ICollection<string> grants)
{
_client.AllowedGrantTypes = grants;
return this;
}
internal ClientBuilder WithAllowedOrigins(params string[] origins)
{
_client.AllowedCorsOrigins = origins;
return this;
}
internal ClientBuilder AllowAccessTokensViaBrowser()
{
_client.AllowAccessTokensViaBrowser = true;
return this;
}
private static Client CreateClient(string name)
{
var client = new Client
{
ClientId = name,
ClientName = name,
RequireConsent = false
};
return client;
}
}
}

View File

@ -0,0 +1,126 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using IdentityServer4.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
/// <summary>
/// A collection of <see cref="Client"/>.
/// </summary>
public class ClientCollection : Collection<Client>
{
/// <summary>
/// Initializes a new instance of <see cref="ClientCollection"/>.
/// </summary>
public ClientCollection()
{
}
/// <summary>
/// Initializes a new instance of <see cref="ClientCollection"/> with the given
/// clients in <paramref name="list"/>.
/// </summary>
/// <param name="list">The initial list of <see cref="Client"/>.</param>
public ClientCollection(IList<Client> list) : base(list)
{
}
/// <summary>
/// Gets a client given its client id.
/// </summary>
/// <param name="key">The name of the <see cref="Client"/>.</param>
/// <returns>The <see cref="Client"/>.</returns>
public Client this[string key]
{
get
{
for (var i = 0; i < Items.Count; i++)
{
var candidate = Items[i];
if (string.Equals(candidate.ClientId, key, StringComparison.Ordinal))
{
return candidate;
}
}
throw new InvalidOperationException($"Client '{key}' not found.");
}
}
/// <summary>
/// Adds the clients in <paramref name="clients"/> to the collection.
/// </summary>
/// <param name="clients">The list of <see cref="Client"/> to add.</param>
public void AddRange(IEnumerable<Client> clients)
{
foreach (var client in clients)
{
Add(client);
}
}
/// <summary>
/// Adds the clients in <paramref name="clients"/> to the collection.
/// </summary>
/// <param name="clients">The list of <see cref="Client"/> to add.</param>
public void AddRange(params Client[] clients)
{
foreach (var client in clients)
{
Add(client);
}
}
/// <summary>
/// Adds a single page application that coexists with an authorization server.
/// </summary>
/// <param name="clientId">The client id for the single page application.</param>
/// <param name="configure">The <see cref="Action{ClientBuilder}"/> to configure the default single page application.</param>
public void AddIdentityServerSPA(string clientId, Action<ClientBuilder> configure)
{
var app = ClientBuilder.IdentityServerSPA(clientId);
configure(app);
Add(app.Build());
}
/// <summary>
/// Adds an externally registered single page application.
/// </summary>
/// <param name="clientId">The client id for the single page application.</param>
/// <param name="configure">The <see cref="Action{ClientBuilder}"/> to configure the default single page application.</param>
public void AddSPA(string clientId, Action<ClientBuilder> configure)
{
var app = ClientBuilder.SPA(clientId);
configure(app);
Add(app.Build());
}
/// <summary>
/// Adds an externally registered native application..
/// </summary>
/// <param name="clientId">The client id for the single page application.</param>
/// <param name="configure">The <see cref="Action{ClientBuilder}"/> to configure the native application.</param>
public void AddNativeApp(string clientId, Action<ClientBuilder> configure)
{
var app = ClientBuilder.NativeApp(clientId);
configure(app);
Add(app.Build());
}
/// <summary>
/// Adds an externally registered web application..
/// </summary>
/// <param name="clientId">The client id for the web application.</param>
/// <param name="configure">The <see cref="Action{ClientBuilder}"/> to configure the web application.</param>
public void AddWebApplication(string clientId, Action<ClientBuilder> configure)
{
var app = ClientBuilder.WebApplication(clientId);
configure(app);
Add(app.Build());
}
}
}

View File

@ -0,0 +1,132 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using IdentityServer4;
using IdentityServer4.Models;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
/// <summary>
/// A builder for identity resources
/// </summary>
public class IdentityResourceBuilder
{
private IdentityResource _identityResource;
private bool _built;
/// <summary>
/// Creates an openid resource.
/// </summary>
public static IdentityResourceBuilder OpenId() =>
IdentityResource(IdentityServerConstants.StandardScopes.OpenId);
/// <summary>
/// Creates a profile resource.
/// </summary>
public static IdentityResourceBuilder Profile() =>
IdentityResource(IdentityServerConstants.StandardScopes.Profile);
/// <summary>
/// Creates an address resource.
/// </summary>
public static IdentityResourceBuilder Address() =>
IdentityResource(IdentityServerConstants.StandardScopes.Address);
/// <summary>
/// Creates an email resource.
/// </summary>
public static IdentityResourceBuilder Email() =>
IdentityResource(IdentityServerConstants.StandardScopes.Email);
/// <summary>
/// Creates a phone resource.
/// </summary>
public static IdentityResourceBuilder Phone() =>
IdentityResource(IdentityServerConstants.StandardScopes.Phone);
/// <summary>
/// Initializes a new instance of <see cref="IdentityResourceBuilder"/>.
/// </summary>
public IdentityResourceBuilder() : this(new IdentityResource())
{
}
/// <summary>
/// Initializes a new instance of <see cref="IdentityResourceBuilder"/>.
/// </summary>
/// <param name="resource">A preconfigured resource.</param>
public IdentityResourceBuilder(IdentityResource resource)
{
_identityResource = resource;
}
/// <summary>
/// Configures the API resource to allow all clients to access it.
/// </summary>
/// <returns>The <see cref="IdentityResourceBuilder"/>.</returns>
public IdentityResourceBuilder AllowAllClients()
{
_identityResource.Properties[ApplicationProfilesPropertyNames.Clients] = ApplicationProfilesPropertyValues.AllowAllApplications;
return this;
}
/// <summary>
/// Builds the API resource.
/// </summary>
/// <returns>The built <see cref="IdentityServer4.Models.IdentityResource"/>.</returns>
public IdentityResource Build()
{
if (_built)
{
throw new InvalidOperationException("IdentityResource already built.");
}
_built = true;
return _identityResource;
}
internal IdentityResourceBuilder WithAllowedClients(string clientList)
{
_identityResource.Properties[ApplicationProfilesPropertyNames.Clients] = clientList;
return this;
}
internal IdentityResourceBuilder FromConfiguration()
{
_identityResource.Properties[ApplicationProfilesPropertyNames.Source] = ApplicationProfilesPropertyValues.Configuration;
return this;
}
internal IdentityResourceBuilder FromDefault()
{
_identityResource.Properties[ApplicationProfilesPropertyNames.Source] = ApplicationProfilesPropertyValues.Default;
return this;
}
internal static IdentityResourceBuilder IdentityResource(string name)
{
var identityResource = GetResource(name);
return new IdentityResourceBuilder(identityResource);
}
private static IdentityResource GetResource(string name)
{
switch (name)
{
case IdentityServerConstants.StandardScopes.OpenId:
return new IdentityResources.OpenId();
case IdentityServerConstants.StandardScopes.Profile:
return new IdentityResources.Profile();
case IdentityServerConstants.StandardScopes.Address:
return new IdentityResources.Address();
case IdentityServerConstants.StandardScopes.Email:
return new IdentityResources.Email();
case IdentityServerConstants.StandardScopes.Phone:
return new IdentityResources.Phone();
default:
throw new InvalidOperationException("Invalid identity resource type.");
}
}
}
}

View File

@ -0,0 +1,163 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using IdentityServer4.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
/// <summary>
/// A collection of <see cref="IdentityResource"/>.
/// </summary>
public class IdentityResourceCollection : Collection<IdentityResource>
{
/// <summary>
/// Initializes a new instance of <see cref="IdentityResourceCollection"/>.
/// </summary>
public IdentityResourceCollection()
{
}
/// <summary>
/// Initializes a new instance of <see cref="IdentityResourceCollection"/> with the given
/// identity resources in <paramref name="list"/>.
/// </summary>
/// <param name="list">The initial list of <see cref="IdentityResource"/>.</param>
public IdentityResourceCollection(IList<IdentityResource> list) : base(list)
{
}
/// <summary>
/// Gets an identity resource given its name.
/// </summary>
/// <param name="key">The name of the <see cref="IdentityResource"/>.</param>
/// <returns>The <see cref="IdentityResource"/>.</returns>
public IdentityResource this[string key]
{
get
{
for (int i = 0; i < Items.Count; i++)
{
var candidate = Items[i];
if (string.Equals(candidate.Name, key, StringComparison.Ordinal))
{
return candidate;
}
}
throw new InvalidOperationException($"IdentityResource '{key}' not found.");
}
}
/// <summary>
/// Adds the identity resources in <paramref name="identityResources"/> to the collection.
/// </summary>
/// <param name="identityResources">The list of <see cref="IdentityResource"/> to add.</param>
public void AddRange(IEnumerable<IdentityResource> identityResources)
{
foreach (var resource in identityResources)
{
Add(resource);
}
}
/// <summary>
/// Adds the identity resources in <paramref name="identityResources"/> to the collection.
/// </summary>
/// <param name="identityResources">The list of <see cref="IdentityResource"/> to add.</param>
public void AddRange(params IdentityResource[] identityResources)
{
foreach (var resource in identityResources)
{
Add(resource);
}
}
/// <summary>
/// Adds an openid resource.
/// </summary>
public void AddOpenId() =>
Add(IdentityResourceBuilder.OpenId().Build());
/// <summary>
/// Adds an openid resource.
/// </summary>
/// <param name="configure">The <see cref="Action{IdentityResourceBuilder}"/> to configure the openid scope.</param>
public void AddOpenId(Action<IdentityResourceBuilder> configure)
{
var resource = IdentityResourceBuilder.OpenId();
configure(resource);
Add(resource.Build());
}
/// <summary>
/// Adds a profile resource.
/// </summary>
public void AddProfile() =>
Add(IdentityResourceBuilder.Profile().Build());
/// <summary>
/// Adds a profile resource.
/// </summary>
/// <param name="configure">The <see cref="Action{IdentityResourceBuilder}"/> to configure the profile scope.</param>
public void AddProfile(Action<IdentityResourceBuilder> configure)
{
var resource = IdentityResourceBuilder.Profile();
configure(resource);
Add(resource.Build());
}
/// <summary>
/// Adds an address resource.
/// </summary>
public void AddAddress() =>
Add(IdentityResourceBuilder.Address().Build());
/// <summary>
/// Adds an address resource.
/// </summary>
/// <param name="configure">The <see cref="Action{IdentityResourceBuilder}"/> to configure the address scope.</param>
public void AddAddress(Action<IdentityResourceBuilder> configure)
{
var resource = IdentityResourceBuilder.Address();
configure(resource);
Add(resource.Build());
}
/// <summary>
/// Adds an email resource.
/// </summary>
public void AddEmail() =>
Add(IdentityResourceBuilder.Email().Build());
/// <summary>
/// Adds an email resource.
/// </summary>
/// <param name="configure">The <see cref="Action{IdentityResourceBuilder}"/> to configure the email scope.</param>
public void AddEmail(Action<IdentityResourceBuilder> configure)
{
var resource = IdentityResourceBuilder.Email();
configure(resource);
Add(resource.Build());
}
/// <summary>
/// Adds a phone resource.
/// </summary>
public void AddPhone() =>
Add(IdentityResourceBuilder.Phone().Build());
/// <summary>
/// Adds a phone resource.
/// </summary>
/// <param name="configure">The <see cref="Action{IdentityResourceBuilder}"/> to configure the phone scope.</param>
public void AddPhone(Action<IdentityResourceBuilder> configure)
{
var resource = IdentityResourceBuilder.Phone();
configure(resource);
Add(resource.Build());
}
}
}

View File

@ -0,0 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey = 0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]

View File

@ -0,0 +1,55 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
/// <summary>
/// A tag helper for generating client parameters for a given oauth/openid client as data attributes.
/// </summary>
[HtmlTargetElement("*", Attributes = "[asp-apiauth-parameters]")]
public class ClientParametersTagHelper : TagHelper
{
private readonly IClientRequestParametersProvider _clientRequestParametersProvider;
/// <summary>
/// Initializes a new instance of <see cref="ClientParametersTagHelper"/>.
/// </summary>
/// <param name="clientRequestParametersProvider">The <see cref="IClientRequestParametersProvider"/>.</param>
public ClientParametersTagHelper(IClientRequestParametersProvider clientRequestParametersProvider)
{
_clientRequestParametersProvider = clientRequestParametersProvider;
}
/// <summary>
/// Gets or sets the client id.
/// </summary>
[HtmlAttributeName("asp-apiauth-parameters")]
public string ClientId { get; set; }
/// <summary>
/// Gets or sets the ViewContext.
/// </summary>
[ViewContext]
public ViewContext ViewContext { get; set; }
/// <inheritdoc />
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var parameters = _clientRequestParametersProvider.GetClientParameters(ViewContext.HttpContext, ClientId);
if (parameters == null)
{
throw new InvalidOperationException($"Parameters for client '{ClientId}' not found.");
}
foreach (var parameter in parameters)
{
output.Attributes.Add("data-" + parameter.Key, parameter.Value);
}
}
}
}

View File

@ -0,0 +1,156 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Threading.Tasks;
using IdentityServer4.Configuration;
using IdentityServer4.Stores;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
public class IdentityServerJwtBearerOptionsConfigurationTest
{
[Fact]
public void Configure_SetsUpBearerSchemeForTheLocalApi()
{
// Arrange
var localApiDescriptor = new Mock<IIdentityServerJwtDescriptor>();
localApiDescriptor.Setup(lad => lad.GetResourceDefinitions())
.Returns(new Dictionary<string, ResourceDefinition>
{
["TestAPI"] = new ResourceDefinition { Profile = ApplicationProfiles.IdentityServerJwt }
});
var bearerConfiguration = new IdentityServerJwtBearerOptionsConfiguration(
"authScheme",
"TestAPI",
localApiDescriptor.Object);
var options = new JwtBearerOptions();
// Act
bearerConfiguration.Configure("authScheme", options);
// Assert
Assert.Equal("name", options.TokenValidationParameters.NameClaimType);
Assert.Equal("role", options.TokenValidationParameters.RoleClaimType);
Assert.Equal("TestAPI", options.Audience);
}
[Fact]
public async Task ResolveAuthorityAndKeysAsync_SetsUpAuthorityAndKeysOnTheTokenValidationParametersAsync()
{
// Arrange
var localApiDescriptor = new Mock<IIdentityServerJwtDescriptor>();
localApiDescriptor.Setup(lad => lad.GetResourceDefinitions())
.Returns(new Dictionary<string, ResourceDefinition>
{
["TestAPI"] = new ResourceDefinition { Profile = ApplicationProfiles.IdentityServerJwt }
});
var credentialsStore = new Mock<ISigningCredentialStore>();
var key = new RsaSecurityKey(RSA.Create());
credentialsStore.Setup(cs => cs.GetSigningCredentialsAsync())
.ReturnsAsync(new SigningCredentials(key, "RS256"));
var context = new DefaultHttpContext();
context.Request.Scheme = "https";
context.Request.Host = new HostString("localhost");
context.RequestServices = new ServiceCollection()
.AddSingleton(new IdentityServerOptions())
.AddSingleton(credentialsStore.Object)
.BuildServiceProvider();
var options = new JwtBearerOptions();
var args = new MessageReceivedContext(context, new AuthenticationScheme("TestAPI",null, Mock.Of<IAuthenticationHandler>().GetType()), options);
// Act
await IdentityServerJwtBearerOptionsConfiguration.ResolveAuthorityAndKeysAsync(args);
// Assert
Assert.Equal("https://localhost", options.TokenValidationParameters.ValidIssuer);
Assert.Equal(key, options.TokenValidationParameters.IssuerSigningKey);
}
[Fact]
public void Configure_IgnoresOptionsForDifferentSchemes()
{
// Arrange
var localApiDescriptor = new Mock<IIdentityServerJwtDescriptor>();
localApiDescriptor.Setup(lad => lad.GetResourceDefinitions())
.Returns(new Dictionary<string, ResourceDefinition>
{
["TestAPI"] = new ResourceDefinition { Profile = ApplicationProfiles.IdentityServerJwt }
});
var bearerConfiguration = new IdentityServerJwtBearerOptionsConfiguration(
"authScheme",
"TestAPI",
localApiDescriptor.Object);
var options = new JwtBearerOptions();
// Act
bearerConfiguration.Configure("otherScheme", options);
// Assert
Assert.NotEqual("name", options.TokenValidationParameters.NameClaimType);
Assert.NotEqual("role", options.TokenValidationParameters.RoleClaimType);
Assert.NotEqual("TestAPI", options.Audience);
Assert.NotEqual("https://localhost", options.Authority);
}
[Fact]
public void Configure_IgnoresOptionsForNonExistingAPIs()
{
// Arrange
var contextAccessor = new Mock<IHttpContextAccessor>();
var context = new DefaultHttpContext();
context.Request.Scheme = "https";
context.Request.Host = new HostString("localhost");
context.RequestServices = new ServiceCollection()
.AddSingleton(new IdentityServerOptions())
.BuildServiceProvider();
contextAccessor.SetupGet(ca => ca.HttpContext).Returns(
context);
var localApiDescriptor = new Mock<IIdentityServerJwtDescriptor>();
localApiDescriptor.Setup(lad => lad.GetResourceDefinitions())
.Returns(new Dictionary<string, ResourceDefinition>
{
["TestAPI"] = new ResourceDefinition { Profile = ApplicationProfiles.IdentityServerJwt }
});
var credentialsStore = new Mock<ISigningCredentialStore>();
var key = new RsaSecurityKey(RSA.Create());
credentialsStore.Setup(cs => cs.GetSigningCredentialsAsync())
.ReturnsAsync(new SigningCredentials(key, "RS256"));
var bearerConfiguration = new IdentityServerJwtBearerOptionsConfiguration(
"authScheme",
"NonExistingApi",
localApiDescriptor.Object);
var options = new JwtBearerOptions();
// Act
bearerConfiguration.Configure("authScheme", options);
// Assert
Assert.NotEqual("name", options.TokenValidationParameters.NameClaimType);
Assert.NotEqual("role", options.TokenValidationParameters.RoleClaimType);
Assert.NotEqual(key, options.TokenValidationParameters.IssuerSigningKey);
Assert.NotEqual("TestAPI", options.Audience);
Assert.NotEqual("https://localhost", options.Authority);
}
}
}

View File

@ -0,0 +1,48 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Xunit;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Authentication
{
public class LocalApiPolicySchemeForwardSelectorTests
{
[Theory]
[InlineData("/Identity/Account/Login")]
[InlineData("/Identity/Error")]
[InlineData("/identity/Account/Manage")]
[InlineData("/Identity/ACCOUNT/TwoFactor")]
public void SelectScheme_ReturnsTheIdentityApplicationScheme_ForIdentityRelatedPaths(string path)
{
// Arrange
var selector = new IdentityServerJwtPolicySchemeForwardSelector("/Identity", "Local");
var ctx = new DefaultHttpContext();
ctx.Request.Path = path;
// Act
var scheme = selector.SelectScheme(ctx);
// Assert
Assert.Equal(IdentityConstants.ApplicationScheme, scheme);
}
[Theory]
[InlineData("/api/values")]
[InlineData("/connect/openid")]
public void SelectScheme_ReturnsTheDefaultScheme_ForOtherPaths(string path)
{
// Arrange
var selector = new IdentityServerJwtPolicySchemeForwardSelector("/Identity", "Local");
var ctx = new DefaultHttpContext();
ctx.Request.Path = path;
// Act
var scheme = selector.SelectScheme(ctx);
// Assert
Assert.Equal("Local", scheme);
}
}
}

View File

@ -0,0 +1,46 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using IdentityServer4.Configuration;
using Microsoft.AspNetCore.Identity;
using Xunit;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
public class AspNetConventionsConfigureOptionsTests
{
[Fact]
public void Configure_SetsUpIdentityPathsAndCookie()
{
// Arrange
var options = new IdentityServerOptions();
var configure = new AspNetConventionsConfigureOptions();
// Act
configure.Configure(options);
// Assert
Assert.Equal("/Identity/Account/Login", options.UserInteraction.LoginUrl);
Assert.Equal("/Identity/Account/Logout", options.UserInteraction.LogoutUrl);
Assert.Equal("/Identity/Error", options.UserInteraction.ErrorUrl);
Assert.Equal(IdentityConstants.ApplicationScheme, options.Authentication.CookieAuthenticationScheme);
}
[Fact]
public void Configure_SetsUpIdentityServerEvents()
{
// Arrange
var options = new IdentityServerOptions();
var configure = new AspNetConventionsConfigureOptions();
// Act
configure.Configure(options);
// Assert
Assert.True(options.Events.RaiseErrorEvents);
Assert.True(options.Events.RaiseInformationEvents);
Assert.True(options.Events.RaiseFailureEvents);
Assert.True(options.Events.RaiseSuccessEvents);
}
}
}

View File

@ -0,0 +1,135 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
public class ConfigureApiResourcesTests
{
[Fact]
public void GetApiResources_ReadsApisFromConfiguration()
{
// Arrange
var configuration = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string>
{
["MyAPI:Profile"] = "API"
}).Build();
var localApiDescriptor = new TestLocalApiDescriptor();
var configurationLoader = new ConfigureApiResources(
configuration,
localApiDescriptor,
new TestLogger<ConfigureApiResources>());
// Act
var resources = configurationLoader.GetApiResources();
// Assert
var resource = Assert.Single(resources);
var scope = Assert.Single(resource.Scopes);
Assert.Equal("MyAPI", resource.Name);
Assert.Equal("MyAPI", scope.Name);
}
[Fact]
public void GetApiResources_ReadsApiScopesFromConfiguration()
{
// Arrange
var expectedScopes = new[] { "First", "Second", "Third" };
var configuration = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string>
{
["MyAPI:Profile"] = "API",
["MyAPI:Scopes"] = "First Second Third"
}).Build();
var localApiDescriptor = new TestLocalApiDescriptor();
var configurationLoader = new ConfigureApiResources(
configuration,
localApiDescriptor,
new TestLogger<ConfigureApiResources>());
// Act
var resources = configurationLoader.GetApiResources();
// Assert
var resource = Assert.Single(resources);
Assert.Equal("MyAPI", resource.Name);
Assert.NotNull(resource.Scopes);
Assert.Equal(3, resource.Scopes.Count);
Assert.Equal(expectedScopes, resource.Scopes.Select(s => s.Name).ToArray());
}
[Fact]
public void GetApiResources_DetectsLocallyRegisteredApis()
{
// Arrange
var configuration = new ConfigurationBuilder().Build();
var localApiDescriptor = new TestLocalApiDescriptor(new Dictionary<string, ResourceDefinition>
{
["MyAPI"] = new ResourceDefinition { Profile = ApplicationProfiles.IdentityServerJwt }
});
var configurationLoader = new ConfigureApiResources(
configuration,
localApiDescriptor,
new TestLogger<ConfigureApiResources>());
// Act
var resources = configurationLoader.GetApiResources();
// Assert
var resource = Assert.Single(resources);
var scope = Assert.Single(resource.Scopes);
Assert.Equal("MyAPI", resource.Name);
Assert.Equal("MyAPI", scope.Name);
}
[Fact]
public void Configure_AddsResourcesToExistingResourceList()
{
// Arrange
var configuration = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string>
{
["MyAPI:Profile"] = "API"
}).Build();
var localApiDescriptor = new TestLocalApiDescriptor();
var configurationLoader = new ConfigureApiResources(
configuration,
localApiDescriptor,
new TestLogger<ConfigureApiResources>());
var options = new ApiAuthorizationOptions();
// Act
configurationLoader.Configure(options);
// Assert
var resource = Assert.Single(options.ApiResources);
var scope = Assert.Single(resource.Scopes);
Assert.Equal("MyAPI", resource.Name);
Assert.Equal("MyAPI", scope.Name);
}
private class TestLocalApiDescriptor : IIdentityServerJwtDescriptor
{
private readonly IDictionary<string, ResourceDefinition> _definitions;
public TestLocalApiDescriptor()
: this(new Dictionary<string, ResourceDefinition>())
{
}
public TestLocalApiDescriptor(IDictionary<string, ResourceDefinition> definitions)
{
_definitions = definitions;
}
public IDictionary<string, ResourceDefinition> GetResourceDefinitions()
{
return _definitions;
}
}
}
}

View File

@ -0,0 +1,76 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using Xunit;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration
{
public class ConfigureClientScopesTests
{
[Fact]
public void PostConfigure_AddResourcesScopesToClients()
{
// Arrange
var configureClientScopes = new ConfigureClientScopes(new TestLogger<ConfigureClientScopes>());
var options = new ApiAuthorizationOptions();
options.Clients.AddRange(
ClientBuilder
.IdentityServerSPA("TestSPA")
.FromConfiguration()
.Build(),
ClientBuilder
.NativeApp("NativeApp")
.FromConfiguration()
.Build());
options.ApiResources.AddRange(
ApiResourceBuilder.ApiResource("ResourceApi")
.FromConfiguration()
.AllowAllClients()
.Build());
// Act
configureClientScopes.PostConfigure(Extensions.Options.Options.DefaultName, options);
// Assert
foreach (var client in options.Clients)
{
Assert.Contains("ResourceApi", client.AllowedScopes);
}
}
[Fact]
public void PostConfigure_AddIdentityResourcesScopesToClients()
{
// Arrange
var configureClientScopes = new ConfigureClientScopes(new TestLogger<ConfigureClientScopes>());
var options = new ApiAuthorizationOptions();
options.Clients.AddRange(
ClientBuilder
.IdentityServerSPA("TestSPA")
.FromConfiguration()
.Build(),
ClientBuilder
.NativeApp("NativeApp")
.FromConfiguration()
.Build());
options.ApiResources.AddRange(
ApiResourceBuilder.ApiResource("ResourceAPI")
.FromConfiguration()
.AllowAllClients()
.Build());
// Act
configureClientScopes.PostConfigure(Extensions.Options.Options.DefaultName, options);
// Assert
var spaClient = Assert.Single(options.Clients, c => c.ClientId == "TestSPA");
Assert.Equal(new[] { "openid", "profile", "ResourceAPI" }, spaClient.AllowedScopes.OrderBy(id => id).ToArray());
var nativeApp = Assert.Single(options.Clients, c => c.ClientId == "NativeApp");
Assert.Equal(new[] { "offline_access", "openid", "profile", "ResourceAPI" }, nativeApp.AllowedScopes.OrderBy(id => id).ToArray());
}
}
}

View File

@ -0,0 +1,220 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using IdentityServer4;
using IdentityServer4.Models;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration
{
public class ConfigureClientsTests
{
[Fact]
public void GetClients_DoesNothingIfThereAreNoConfiguredClients()
{
// Arrange
var config = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string>
{
}).Build();
var resources = Array.Empty<ApiResource>();
var clientLoader = new ConfigureClients(config, new TestLogger<ConfigureClients>());
// Act
var clients = clientLoader.GetClients();
// Assert
Assert.Empty(clients);
}
[Fact]
public void GetClients_ReadsIdentityServerSPAFromConfiguration()
{
// Arrange
var config = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string>
{
["MyClient:Profile"] = "IdentityServerSPA"
}).Build();
var resources = Array.Empty<ApiResource>();
var expectedScopes = new[]
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
};
var clientLoader = new ConfigureClients(config, new TestLogger<ConfigureClients>());
// Act
var clients = clientLoader.GetClients();
// Assert
var client = Assert.Single(clients);
Assert.Equal("MyClient", client.ClientId);
Assert.Equal("MyClient", client.ClientName);
Assert.True(client.AllowAccessTokensViaBrowser);
Assert.Equal(new[] { "" }, client.RedirectUris.ToArray());
Assert.Equal(new[] { "" }, client.PostLogoutRedirectUris.ToArray());
Assert.Empty(client.AllowedCorsOrigins);
Assert.False(client.RequireConsent);
Assert.Empty(client.ClientSecrets);
Assert.Equal(GrantTypes.Implicit.ToArray(), client.AllowedGrantTypes.ToArray());
//Assert.Equal(expectedScopes, client.AllowedScopes.ToArray());
}
[Fact]
public void GetClients_ReadsNativeAppFromConfiguration()
{
// Arrange
var config = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string>
{
["MyClient:Profile"] = "NativeApp"
}).Build();
var resources = Array.Empty<ApiResource>();
var clientLoader = new ConfigureClients(config, new TestLogger<ConfigureClients>());
var expectedScopes = new[]
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess
};
// Act
var clients = clientLoader.GetClients();
// Assert
var client = Assert.Single(clients);
Assert.Equal("MyClient", client.ClientId);
Assert.Equal("MyClient", client.ClientName);
Assert.False(client.AllowAccessTokensViaBrowser);
Assert.Equal(new[] { "urn:ietf:wg:oauth:2.0:oob" }, client.RedirectUris.ToArray());
Assert.Equal(new[] { "urn:ietf:wg:oauth:2.0:oob" }, client.PostLogoutRedirectUris.ToArray());
Assert.Empty(client.AllowedCorsOrigins);
Assert.False(client.RequireConsent);
Assert.Empty(client.ClientSecrets);
Assert.Equal(GrantTypes.Code.ToArray(), client.AllowedGrantTypes.ToArray());
Assert.True(client.RequirePkce);
Assert.False(client.AllowPlainTextPkce);
//Assert.Equal(expectedScopes, client.AllowedScopes.ToArray());
}
[Fact]
public void GetClients_ReadsSPAFromConfiguration()
{
// Arrange
var expectedRedirectUrl = "https://www.example.com/authenticate";
var expectedLogoutUrl = "https://www.example.com/logout";
var expectedAllowedOrigins = "https://www.example.com";
var config = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string>
{
["MyClient:Profile"] = "SPA",
["MyClient:RedirectUri"] = expectedRedirectUrl,
["MyClient:LogoutUri"] = expectedLogoutUrl,
}).Build();
var resources = Array.Empty<ApiResource>();
var expectedScopes = new[]
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
};
var clientLoader = new ConfigureClients(config, new TestLogger<ConfigureClients>());
// Act
var clients = clientLoader.GetClients();
// Assert
var client = Assert.Single(clients);
Assert.Equal("MyClient", client.ClientId);
Assert.Equal("MyClient", client.ClientName);
Assert.True(client.AllowAccessTokensViaBrowser);
Assert.Equal(new[] { expectedRedirectUrl }, client.RedirectUris.ToArray());
Assert.Equal(new[] { expectedLogoutUrl }, client.PostLogoutRedirectUris.ToArray());
Assert.Equal(new[] { expectedAllowedOrigins }, client.AllowedCorsOrigins);
Assert.False(client.RequireConsent);
Assert.Empty(client.ClientSecrets);
Assert.Equal(GrantTypes.Implicit.ToArray(), client.AllowedGrantTypes.ToArray());
//Assert.Equal(expectedScopes, client.AllowedScopes.ToArray());
}
[Fact]
public void GetClients_ReadsWebAppFromConfiguration()
{
// Arrange
var config = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string>
{
["MyClient:Profile"] = "IdentityServerSPA"
}).Build();
var resources = Array.Empty<ApiResource>();
var expectedScopes = new[]
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
};
var clientLoader = new ConfigureClients(config, new TestLogger<ConfigureClients>());
// Act
var clients = clientLoader.GetClients();
// Assert
var client = Assert.Single(clients);
Assert.Equal("MyClient", client.ClientId);
Assert.Equal("MyClient", client.ClientName);
Assert.True(client.AllowAccessTokensViaBrowser);
Assert.Equal(new[] { "" }, client.RedirectUris.ToArray());
Assert.Equal(new[] { "" }, client.PostLogoutRedirectUris.ToArray());
Assert.Empty(client.AllowedCorsOrigins);
Assert.False(client.RequireConsent);
Assert.Empty(client.ClientSecrets);
Assert.Equal(GrantTypes.Implicit.ToArray(), client.AllowedGrantTypes.ToArray());
//Assert.Equal(expectedScopes, client.AllowedScopes.ToArray());
}
[Fact]
public void Configure_AddsClientsToExistingClientsList()
{
// Arrange
var config = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string>
{
["MyClient:Profile"] = "IdentityServerSPA"
}).Build();
var resources = Array.Empty<ApiResource>();
var expectedScopes = new[]
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
};
var clientLoader = new ConfigureClients(config, new TestLogger<ConfigureClients>());
var options = new ApiAuthorizationOptions();
// Act
clientLoader.Configure(options);
// Assert
var client = Assert.Single(options.Clients);
Assert.Equal("MyClient", client.ClientId);
Assert.Equal("MyClient", client.ClientName);
Assert.True(client.AllowAccessTokensViaBrowser);
Assert.Equal(new[] { "" }, client.RedirectUris.ToArray());
Assert.Equal(new[] { "" }, client.PostLogoutRedirectUris.ToArray());
Assert.Empty(client.AllowedCorsOrigins);
Assert.False(client.RequireConsent);
Assert.Empty(client.ClientSecrets);
Assert.Equal(GrantTypes.Implicit.ToArray(), client.AllowedGrantTypes.ToArray());
}
}
}

View File

@ -0,0 +1,176 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using Xunit;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
public class ConfigureSigningCredentialsTests
{
// We need to cast the underlying int value of the EphemeralKeySet to X509KeyStorageFlags
// due to the fact that is not part of .NET Standard. This value is only used with non-windows
// platforms (all .NET Core) for which the value is defined on the underlying platform.
private const X509KeyStorageFlags UnsafeEphemeralKeySet = (X509KeyStorageFlags)32;
private static readonly X509KeyStorageFlags DefaultFlags = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ?
UnsafeEphemeralKeySet : (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? X509KeyStorageFlags.PersistKeySet :
X509KeyStorageFlags.DefaultKeySet);
[ConditionalFact]
[FrameworkSkipCondition(RuntimeFrameworks.CLR)]
public void Configure_AddsDevelopmentKeyFromConfiguration()
{
var expectedKeyPath = Path.Combine(Directory.GetCurrentDirectory(), "./testkey.json");
try
{
// Arrange
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>()
{
["Type"] = "Development",
["FilePath"] = "testkey.json"
}).Build();
var configureSigningCredentials = new ConfigureSigningCredentials(
configuration,
new TestLogger<ConfigureSigningCredentials>());
var options = new ApiAuthorizationOptions();
// Act
configureSigningCredentials.Configure(options);
// Assert
Assert.NotNull(options);
Assert.True(File.Exists(expectedKeyPath));
Assert.NotNull(options.SigningCredential);
Assert.Equal("Development", options.SigningCredential.Kid);
Assert.IsType<RsaSecurityKey>(options.SigningCredential.Key);
}
finally
{
if (File.Exists(expectedKeyPath))
{
File.Delete(expectedKeyPath);
}
}
}
[Fact]
public void Configure_LoadsPfxCertificateCredentialFromConfiguration()
{
// Arrange
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>()
{
["Type"] = "File",
["FilePath"] = "test.pfx",
["Password"] = "aspnetcore"
}).Build();
var configureSigningCredentials = new ConfigureSigningCredentials(
configuration,
new TestLogger<ConfigureSigningCredentials>());
var options = new ApiAuthorizationOptions();
// Act
configureSigningCredentials.Configure(options);
// Assert
Assert.NotNull(options);
Assert.NotNull(options.SigningCredential);
var key = Assert.IsType<X509SecurityKey>(options.SigningCredential.Key);
Assert.NotNull(key.Certificate);
Assert.Equal("AC8FDF4BD4C10841BD24DC88D983225D10B43BB2", key.Certificate.Thumbprint);
}
[Fact]
public void Configure_LoadsCertificateStoreCertificateCredentialFromConfiguration()
{
try
{
// Arrange
var x509Certificate = new X509Certificate2("test.pfx", "aspnetcore", DefaultFlags);
SetupTestCertificate(x509Certificate);
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>()
{
["Type"] = "Store",
["StoreLocation"] = "CurrentUser",
["StoreName"] = "My",
["Name"] = "CN=Test"
}).Build();
var configureSigningCredentials = new ConfigureSigningCredentials(
configuration,
new TestLogger<ConfigureSigningCredentials>());
var options = new ApiAuthorizationOptions();
// Act
configureSigningCredentials.Configure(options);
// Assert
Assert.NotNull(options);
Assert.NotNull(options.SigningCredential);
var key = Assert.IsType<X509SecurityKey>(options.SigningCredential.Key);
Assert.NotNull(key.Certificate);
Assert.Equal("AC8FDF4BD4C10841BD24DC88D983225D10B43BB2", key.Certificate.Thumbprint);
}
finally
{
CleanupTestCertificate();
}
}
private static void CleanupTestCertificate()
{
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite);
var certificates = store
.Certificates
.Find(X509FindType.FindByThumbprint, "1646CFBEE354788D7116DF86EFC35C0075A9C05D", validOnly: false);
foreach (var certificate in certificates)
{
store.Certificates.Remove(certificate);
}
foreach (var certificate in certificates)
{
certificate.Dispose();
}
store.Close();
}
}
private static void SetupTestCertificate(X509Certificate2 x509Certificate)
{
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite);
var certificates = store
.Certificates
.Find(X509FindType.FindByThumbprint, "AC8FDF4BD4C10841BD24DC88D983225D10B43BB2", validOnly: false);
if (certificates.Count == 0)
{
store.Add(x509Certificate);
}
foreach (var certificate in certificates)
{
certificate.Dispose();
}
store.Close();
}
}
}
}

View File

@ -0,0 +1,30 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Hosting;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration
{
public class LocalApiDescriptorTests
{
[Fact]
public void LocalApiDescriptor_DefinesApiResources()
{
// Arrange
var environment = new Mock<IHostingEnvironment>();
environment.SetupGet(e => e.ApplicationName).Returns("Test");
var descriptor = new IdentityServerJwtDescriptor(environment.Object);
// Act
var resources = descriptor.GetResourceDefinitions();
// Assert
var apiResource = Assert.Single(resources);
Assert.Equal("TestAPI", apiResource.Key);
Assert.NotNull(apiResource.Value);
Assert.Equal(ApplicationProfiles.IdentityServerJwt, apiResource.Value.Profile);
}
}
}

View File

@ -0,0 +1,204 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Testing.xunit;
using Xunit;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration
{
public class SigningKeysLoaderTests
{
// We need to cast the underlying int value of the EphemeralKeySet to X509KeyStorageFlags
// due to the fact that is not part of .NET Standard. This value is only used with non-windows
// platforms (all .NET Core) for which the value is defined on the underlying platform.
private const X509KeyStorageFlags UnsafeEphemeralKeySet = (X509KeyStorageFlags)32;
private static readonly X509KeyStorageFlags DefaultFlags = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ?
UnsafeEphemeralKeySet : (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? X509KeyStorageFlags.PersistKeySet :
X509KeyStorageFlags.DefaultKeySet);
[Fact]
public void LoadFromFile_ThrowsIfFileDoesNotExist()
{
// Arrange, Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => SigningKeysLoader.LoadFromFile("./nonexisting.pfx", "", DefaultFlags));
Assert.Equal($"There was an error loading the certificate. The file './nonexisting.pfx' was not found.", exception.Message);
}
[Fact]
public void LoadFromFile_ThrowsIfPasswordIsNull()
{
// Arrange, Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => SigningKeysLoader.LoadFromFile("test.pfx", null, DefaultFlags));
Assert.Equal("There was an error loading the certificate. No password was provided.", exception.Message);
}
[Fact]
public void LoadFromFile_ThrowsIfPasswordIsIncorrect()
{
// Arrange, Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => SigningKeysLoader.LoadFromFile("test.pfx", "incorrect", DefaultFlags));
Assert.Equal(
$"There was an error loading the certificate. Either the password is incorrect or the process does not have permisions to store the key in the Keyset '{DefaultFlags}'",
exception.Message);
}
[Fact]
public static void LoadFromStoreCert_ThrowsIfThereIsNoCertificateAvailable()
{
// Arrange
var time = new DateTimeOffset(2018, 09, 25, 12, 0, 0, TimeSpan.Zero);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => SigningKeysLoader.LoadFromStoreCert("Invalid", "My", StoreLocation.CurrentUser, time));
Assert.Equal("Couldn't find a valid certificate with subject 'Invalid' on the 'CurrentUser\\My'", exception.Message);
}
[Fact]
public static void LoadFromStoreCert_SkipsCertificatesNotYetValid()
{
try
{
SetupCertificates("./current.pfx", "./future.pfx");
// Arrange
var time = new DateTimeOffset(2018, 10, 29, 12, 0, 0, TimeSpan.Zero);
// Act
var certificate = SigningKeysLoader.LoadFromStoreCert("CN=SigningKeysLoaderTest", "My", StoreLocation.CurrentUser, time);
// Assert
Assert.NotNull(certificate);
Assert.Equal("C54CD513088C23EC2AFD256874CC6C0F81EA9D5E", certificate.Thumbprint);
}
finally
{
CleanupCertificates();
}
}
[Fact]
public static void LoadFromStoreCert_PrefersCertificatesCloserToExpirationDate()
{
try
{
SetupCertificates("./current.pfx", "./future.pfx");
// Arrange
var time = new DateTimeOffset(2020, 10, 29, 12, 0, 0, TimeSpan.Zero);
// Act
var certificate = SigningKeysLoader.LoadFromStoreCert("CN=SigningKeysLoaderTest", "My", StoreLocation.CurrentUser, time);
// Assert
Assert.NotNull(certificate);
Assert.Equal("C54CD513088C23EC2AFD256874CC6C0F81EA9D5E", certificate.Thumbprint);
}
finally
{
CleanupCertificates();
}
}
[Fact]
public static void LoadFromStoreCert_SkipsExpiredCertificates()
{
try
{
SetupCertificates("./expired.pfx", "./current.pfx", "./future.pfx");
// Arrange
var time = new DateTimeOffset(2024, 01, 01, 12, 0, 0, TimeSpan.Zero);
// Act
var certificate = SigningKeysLoader.LoadFromStoreCert("CN=SigningKeysLoaderTest", "My", StoreLocation.CurrentUser, time);
// Assert
Assert.NotNull(certificate);
Assert.Equal("35840DD366107B89D2885A6B4F42CCBBAE6BA8E3", certificate.Thumbprint);
}
finally
{
CleanupCertificates();
}
}
[Fact]
public static void LoadDevelopment_ThrowsIfKeyDoesNotExist()
{
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => SigningKeysLoader.LoadDevelopment("c:/inexistent.json", createIfMissing: false));
Assert.Equal("Couldn't find the file 'c:/inexistent.json' and creation of a development key was not requested.", exception.Message);
}
[ConditionalFact]
[FrameworkSkipCondition(RuntimeFrameworks.CLR)]
public static void LoadDevelopment_CreatesKeyIfItDoesNotExist()
{
// Arrange
var path = "./tempkeyfolder/tempkey.json";
if (File.Exists(path))
{
File.Delete(path);
}
// Act
var key = SigningKeysLoader.LoadDevelopment(path, createIfMissing: true);
// Assert
Assert.NotNull(key);
Assert.True(File.Exists(path));
}
[ConditionalFact]
[FrameworkSkipCondition(RuntimeFrameworks.CLR)]
public static void LoadDevelopment_ReusesKeyIfExists()
{
// Arrange
var path = "./tempkeyfolder/existing.json";
if (File.Exists(path))
{
File.Delete(path);
}
var existingKey = SigningKeysLoader.LoadDevelopment(path, createIfMissing: true);
var existingParameters = existingKey.ExportParameters(includePrivateParameters: true);
// Act
var currentKey = SigningKeysLoader.LoadDevelopment(path, createIfMissing: true);
var currentParameters = currentKey.ExportParameters(includePrivateParameters: true);
// Assert
Assert.NotNull(currentKey);
Assert.Equal(existingParameters.P, currentParameters.P);
Assert.Equal(existingParameters.Q, currentParameters.Q);
}
private static void CleanupCertificates()
{
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite);
store.RemoveRange(store.Certificates.Find(X509FindType.FindBySubjectName, "CN=SigningKeysLoaderTest", validOnly: false));
store.Close();
}
}
private static void SetupCertificates(params string[] certificateFiles)
{
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite);
foreach (var certificate in certificateFiles)
{
var cert = new X509Certificate2(certificate, "aspnetcore", DefaultFlags);
if (!(store.Certificates.Find(X509FindType.FindByThumbprint, cert.Thumbprint, validOnly: false).Count > 0))
{
store.Add(cert);
}
}
store.Close();
}
}
}
}

View File

@ -0,0 +1,63 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
public class AbsoluteUrlFactoryTests
{
[Fact]
public void GetAbsoluteUrl_ReturnsNull_ForInvalidData()
{
// Arrange
var accessor = new Mock<IHttpContextAccessor>();
var factory = new AbsoluteUrlFactory(accessor.Object);
var path = "something|invalid";
// Act
var result = factory.GetAbsoluteUrl(path);
// Assert
Assert.Null(result);
}
[Fact]
public void GetAbsoluteUrl_ReturnsUnmodifiedUrl_ForAbsoluteUrls()
{
// Arrange
var accessor = new Mock<IHttpContextAccessor>();
var factory = new AbsoluteUrlFactory(accessor.Object);
var path = "https://localhost:5001/authenticate";
// Act
var result = factory.GetAbsoluteUrl(path);
// Assert
Assert.Equal(path, result);
}
[Fact]
public void GetAbsoluteUrl_ReturnsContextBasedAbsoluteUrl_ForRelativeUrls()
{
// Arrange
var ctx = new DefaultHttpContext();
ctx.Request.Scheme = "https";
ctx.Request.Host = new HostString("localhost:5001");
ctx.Request.PathBase = "/virtual";
var accessor = new Mock<IHttpContextAccessor>();
accessor.SetupGet(c => c.HttpContext).Returns(ctx);
var factory = new AbsoluteUrlFactory(accessor.Object);
var path = "/authenticate";
// Act
var result = factory.GetAbsoluteUrl(path);
// Assert
Assert.Equal("https://localhost:5001/virtual/authenticate", result);
}
}
}

View File

@ -0,0 +1,285 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Specialized;
using System.Security.Claims;
using System.Threading.Tasks;
using IdentityServer4.Configuration;
using IdentityServer4.Endpoints.Results;
using IdentityServer4.Models;
using IdentityServer4.Services;
using IdentityServer4.Validation;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
public class AutoRedirectEndSessionEndpointTests
{
[Fact]
public async Task AutoRedirectSessionEndpoint_AutoRedirectsValidatedPostLogoutRequests_ToApplicationsWithProfiles()
{
// Arrange
var session = new Mock<IUserSession>();
session.Setup(s => s.GetUserAsync()).ReturnsAsync(new ClaimsPrincipal());
var endSessionValidator = new Mock<IEndSessionRequestValidator>();
endSessionValidator.Setup(esv => esv.ValidateAsync(It.IsAny<NameValueCollection>(), It.IsAny<ClaimsPrincipal>()))
.ReturnsAsync(new EndSessionValidationResult()
{
IsError = false,
ValidatedRequest = new ValidatedEndSessionRequest()
{
Client = ClientBuilder.IdentityServerSPA("MySPA").Build(),
PostLogOutUri = "https://www.example.com/logout"
}
});
var identityServerOptions = Options.Create(new IdentityServerOptions());
identityServerOptions.Value.Authentication.CookieAuthenticationScheme = IdentityConstants.ApplicationScheme;
identityServerOptions.Value.UserInteraction.LogoutUrl = "/Identity/Account/Logout";
identityServerOptions.Value.UserInteraction.ErrorUrl = "/Identity/Error";
var endpoint = new AutoRedirectEndSessionEndpoint(new TestLogger<AutoRedirectEndSessionEndpoint>(), endSessionValidator.Object, identityServerOptions, session.Object);
var ctx = new DefaultHttpContext();
SetupRequestServices(ctx);
ctx.Request.Method = HttpMethods.Post;
ctx.Request.ContentType = "application/x-www-form-urlencoded";
// Act
var response = await endpoint.ProcessAsync(ctx);
// Assert
Assert.NotNull(response);
var redirect = Assert.IsType<AutoRedirectEndSessionEndpoint.RedirectResult>(response);
Assert.Equal("https://www.example.com/logout", redirect.Url);
await response.ExecuteAsync(ctx);
Assert.Equal(StatusCodes.Status302Found, ctx.Response.StatusCode);
Assert.Equal("https://www.example.com/logout", ctx.Response.Headers[HeaderNames.Location]);
}
[Fact]
public async Task AutoRedirectSessionEndpoint_AutoRedirectsValidatedGetLogoutRequests_ToApplicationsWithProfiles()
{
// Arrange
var session = new Mock<IUserSession>();
session.Setup(s => s.GetUserAsync()).ReturnsAsync(new ClaimsPrincipal());
var endSessionValidator = new Mock<IEndSessionRequestValidator>();
endSessionValidator.Setup(esv => esv.ValidateAsync(It.IsAny<NameValueCollection>(), It.IsAny<ClaimsPrincipal>()))
.ReturnsAsync(new EndSessionValidationResult()
{
IsError = false,
ValidatedRequest = new ValidatedEndSessionRequest()
{
Client = ClientBuilder.IdentityServerSPA("MySPA").Build(),
PostLogOutUri = "https://www.example.com/logout"
}
});
var identityServerOptions = Options.Create(new IdentityServerOptions());
identityServerOptions.Value.Authentication.CookieAuthenticationScheme = IdentityConstants.ApplicationScheme;
identityServerOptions.Value.UserInteraction.LogoutUrl = "/Identity/Account/Logout";
identityServerOptions.Value.UserInteraction.ErrorUrl = "/Identity/Error";
var endpoint = new AutoRedirectEndSessionEndpoint(new TestLogger<AutoRedirectEndSessionEndpoint>(), endSessionValidator.Object, identityServerOptions, session.Object);
var ctx = new DefaultHttpContext();
SetupRequestServices(ctx);
ctx.Request.Method = HttpMethods.Get;
// Act
var response = await endpoint.ProcessAsync(ctx);
// Assert
Assert.NotNull(response);
var redirect = Assert.IsType<AutoRedirectEndSessionEndpoint.RedirectResult>(response);
Assert.Equal("https://www.example.com/logout", redirect.Url);
await response.ExecuteAsync(ctx);
Assert.Equal(StatusCodes.Status302Found, ctx.Response.StatusCode);
Assert.Equal("https://www.example.com/logout", ctx.Response.Headers[HeaderNames.Location]);
}
[Fact]
public async Task AutoRedirectSessionEndpoint_RedirectsToError_WhenValidationFails()
{
// Arrange
var session = new Mock<IUserSession>();
session.Setup(s => s.GetUserAsync()).ReturnsAsync(new ClaimsPrincipal());
var endSessionValidator = new Mock<IEndSessionRequestValidator>();
endSessionValidator.Setup(esv => esv.ValidateAsync(It.IsAny<NameValueCollection>(), It.IsAny<ClaimsPrincipal>()))
.ReturnsAsync(new EndSessionValidationResult()
{
IsError = true,
Error = "SomeError"
});
var identityServerOptions = Options.Create(new IdentityServerOptions());
identityServerOptions.Value.Authentication.CookieAuthenticationScheme = IdentityConstants.ApplicationScheme;
identityServerOptions.Value.UserInteraction.LogoutUrl = "/Identity/Account/Logout";
identityServerOptions.Value.UserInteraction.ErrorUrl = "/Identity/Error";
var endpoint = new AutoRedirectEndSessionEndpoint(new TestLogger<AutoRedirectEndSessionEndpoint>(), endSessionValidator.Object, identityServerOptions, session.Object);
var ctx = new DefaultHttpContext();
SetupRequestServices(ctx);
ctx.Request.Method = HttpMethods.Post;
ctx.Request.ContentType = "application/x-www-form-urlencoded";
// Act
var response = await endpoint.ProcessAsync(ctx);
// Assert
Assert.NotNull(response);
var redirect = Assert.IsType<AutoRedirectEndSessionEndpoint.RedirectResult>(response);
Assert.Equal("/Identity/Error", redirect.Url);
await response.ExecuteAsync(ctx);
Assert.Equal(StatusCodes.Status302Found, ctx.Response.StatusCode);
Assert.Equal("/Identity/Error", ctx.Response.Headers[HeaderNames.Location]);
}
[Fact]
public async Task AutoRedirectSessionEndpoint_RedirectsToLogoutUri_WhenClientDoesntHaveAProfile()
{
// Arrange
var session = new Mock<IUserSession>();
session.Setup(s => s.GetUserAsync()).ReturnsAsync(new ClaimsPrincipal());
var endSessionValidator = new Mock<IEndSessionRequestValidator>();
endSessionValidator.Setup(esv => esv.ValidateAsync(It.IsAny<NameValueCollection>(), It.IsAny<ClaimsPrincipal>()))
.ReturnsAsync(new EndSessionValidationResult()
{
IsError = false,
ValidatedRequest = new ValidatedEndSessionRequest()
{
Client = new Client()
}
});
var identityServerOptions = Options.Create(new IdentityServerOptions());
identityServerOptions.Value.Authentication.CookieAuthenticationScheme = IdentityConstants.ApplicationScheme;
identityServerOptions.Value.UserInteraction.LogoutUrl = "/Identity/Account/Logout";
identityServerOptions.Value.UserInteraction.ErrorUrl = "/Identity/Error";
var endpoint = new AutoRedirectEndSessionEndpoint(new TestLogger<AutoRedirectEndSessionEndpoint>(), endSessionValidator.Object, identityServerOptions, session.Object);
var ctx = new DefaultHttpContext();
SetupRequestServices(ctx);
ctx.Request.Method = HttpMethods.Post;
ctx.Request.ContentType = "application/x-www-form-urlencoded";
// Act
var response = await endpoint.ProcessAsync(ctx);
// Assert
Assert.NotNull(response);
var redirect = Assert.IsType<AutoRedirectEndSessionEndpoint.RedirectResult>(response);
Assert.Equal("/Identity/Account/Logout", redirect.Url);
await response.ExecuteAsync(ctx);
Assert.Equal(StatusCodes.Status302Found, ctx.Response.StatusCode);
Assert.Equal("/Identity/Account/Logout", ctx.Response.Headers[HeaderNames.Location]);
}
[Fact]
public async Task AutoRedirectSessionEndpoint_RedirectsToLogoutUri_WhenTheValidationRequestDoesNotContainAClient()
{
// Arrange
var session = new Mock<IUserSession>();
session.Setup(s => s.GetUserAsync()).ReturnsAsync(new ClaimsPrincipal());
var endSessionValidator = new Mock<IEndSessionRequestValidator>();
endSessionValidator.Setup(esv => esv.ValidateAsync(It.IsAny<NameValueCollection>(), It.IsAny<ClaimsPrincipal>()))
.ReturnsAsync(new EndSessionValidationResult()
{
IsError = false,
ValidatedRequest = new ValidatedEndSessionRequest()
});
var identityServerOptions = Options.Create(new IdentityServerOptions());
identityServerOptions.Value.Authentication.CookieAuthenticationScheme = IdentityConstants.ApplicationScheme;
identityServerOptions.Value.UserInteraction.LogoutUrl = "/Identity/Account/Logout";
identityServerOptions.Value.UserInteraction.ErrorUrl = "/Identity/Error";
var endpoint = new AutoRedirectEndSessionEndpoint(new TestLogger<AutoRedirectEndSessionEndpoint>(), endSessionValidator.Object, identityServerOptions, session.Object);
var ctx = new DefaultHttpContext();
SetupRequestServices(ctx);
ctx.Request.Method = HttpMethods.Post;
ctx.Request.ContentType = "application/x-www-form-urlencoded";
// Act
var response = await endpoint.ProcessAsync(ctx);
// Assert
Assert.NotNull(response);
var redirect = Assert.IsType<AutoRedirectEndSessionEndpoint.RedirectResult>(response);
Assert.Equal("/Identity/Account/Logout", redirect.Url);
await response.ExecuteAsync(ctx);
Assert.Equal(StatusCodes.Status302Found, ctx.Response.StatusCode);
Assert.Equal("/Identity/Account/Logout", ctx.Response.Headers[HeaderNames.Location]);
}
[Theory]
[InlineData("PUT")]
[InlineData("DELETE")]
[InlineData("PATCH")]
[InlineData("OPTIONS")]
[InlineData("HEAD")]
public async Task AutoRedirectSessionEndpoint_ReturnsBadRequest_WhenMethodIsNotPostOrGet(string method)
{
// Arrange
var session = new Mock<IUserSession>();
var endSessionValidator = new Mock<IEndSessionRequestValidator>();
var identityServerOptions = Options.Create(new IdentityServerOptions());
var endpoint = new AutoRedirectEndSessionEndpoint(new TestLogger<AutoRedirectEndSessionEndpoint>(), endSessionValidator.Object, identityServerOptions, session.Object);
var ctx = new DefaultHttpContext();
SetupRequestServices(ctx);
ctx.Request.Method = method;
// Act
var response = await endpoint.ProcessAsync(ctx);
// Assert
Assert.NotNull(response);
var statusCode = Assert.IsType<StatusCodeResult>(response);
Assert.Equal(StatusCodes.Status400BadRequest, statusCode.StatusCode);
}
[Fact]
public async Task AutoRedirectSessionEndpoint_ReturnsBadRequest_WhenCannotReadTheRequestBody()
{
// Arrange
var session = new Mock<IUserSession>();
var endSessionValidator = new Mock<IEndSessionRequestValidator>();
var identityServerOptions = Options.Create(new IdentityServerOptions());
var endpoint = new AutoRedirectEndSessionEndpoint(new TestLogger<AutoRedirectEndSessionEndpoint>(), endSessionValidator.Object, identityServerOptions, session.Object);
var ctx = new DefaultHttpContext();
SetupRequestServices(ctx);
ctx.Request.Method = HttpMethods.Post;
// Act & Assert
var response = await endpoint.ProcessAsync(ctx);
// Assert
Assert.NotNull(response);
var statusCode = Assert.IsType<StatusCodeResult>(response);
Assert.Equal(StatusCodes.Status400BadRequest, statusCode.StatusCode);
}
private void SetupRequestServices(DefaultHttpContext ctx)
{
var collection = new ServiceCollection();
var authService = new Mock<IAuthenticationService>();
authService.Setup(service => service.SignOutAsync(It.IsAny<HttpContext>(), It.IsAny<string>(), It.IsAny<AuthenticationProperties>()))
.Returns(Task.CompletedTask);
collection.AddSingleton(authService.Object);
ctx.RequestServices = collection.BuildServiceProvider();
}
}
}

View File

@ -0,0 +1,226 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using IdentityServer4.Models;
using Microsoft.AspNetCore.Http;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
public class RelativeRedirectUriValidatorTests
{
[Fact]
public async Task IsRedirectUriValidAsync_ConvertsRelativeUrisIntoAbsoluteUris_ForLocalSPAsAsync()
{
// Arrange
var expectedRelativeUri = "/authenticate";
var providedFullUrl = "https://localhost:5001/authenticate";
var expectedClient = new Client
{
RedirectUris = { expectedRelativeUri },
Properties = new Dictionary<string, string>
{
[ApplicationProfilesPropertyNames.Profile] = ApplicationProfiles.IdentityServerSPA,
}
};
var factory = new TestUrlFactory(expectedRelativeUri, providedFullUrl);
var redirectUriValidator = new RelativeRedirectUriValidator(factory);
// Act
var validator = await redirectUriValidator.IsRedirectUriValidAsync(providedFullUrl, expectedClient);
// Assert
Assert.True(validator);
}
[Fact]
public async Task IsRedirectUriValidAsync_RejectsIfTheRelativeUriIsNotRegistered_ForLocalSPAsAsync()
{
// Arrange
var expectedRelativeUri = "/authenticate";
var providedFullUrl = "https://localhost:5001/notregistered";
var expectedClient = new Client
{
RedirectUris = { expectedRelativeUri },
Properties = new Dictionary<string, string>
{
[ApplicationProfilesPropertyNames.Profile] = ApplicationProfiles.IdentityServerSPA,
}
};
var factory = new TestUrlFactory();
var redirectUriValidator = new RelativeRedirectUriValidator(factory);
// Act
var validator = await redirectUriValidator.IsRedirectUriValidAsync(providedFullUrl, expectedClient);
// Assert
Assert.False(validator);
}
[Fact]
public async Task IsRedirectUriValidAsync_CallsBaseAndSucceeds_ForValidRedirectUrisOnRegularClients()
{
// Arrange
var providedFullUrl = "https://localhost:5001/authenticate";
var expectedClient = new Client
{
RedirectUris = { "https://localhost:5001/authenticate" },
};
var factory = new TestUrlFactory();
var redirectUriValidator = new RelativeRedirectUriValidator(factory);
// Act
var validator = await redirectUriValidator.IsRedirectUriValidAsync(providedFullUrl, expectedClient);
// Assert
Assert.True(validator);
}
[Fact]
public async Task IsRedirectUriValidAsync_CallsBaseAndFails_ForInvalidRedirectUrisOnRegularClients()
{
// Arrange
var providedFullUrl = "https://localhost:5001/notregistered";
var expectedClient = new Client
{
RedirectUris = { "https://localhost:5001/authenticate" },
};
var factory = new TestUrlFactory();
var redirectUriValidator = new RelativeRedirectUriValidator(factory);
// Act
var validator = await redirectUriValidator.IsRedirectUriValidAsync(providedFullUrl, expectedClient);
// Assert
Assert.False(validator);
}
[Fact]
public async Task IsPostLogoutRedirectUriValidAsync_ConvertsRelativeUrisIntoAbsoluteUris_ForLocalSPAsAsync()
{
// Arrange
var expectedRelativeUri = "/logout";
var providedFullUrl = "https://localhost:5001/logout";
var expectedClient = new Client
{
PostLogoutRedirectUris = { expectedRelativeUri },
Properties = new Dictionary<string, string>
{
[ApplicationProfilesPropertyNames.Profile] = ApplicationProfiles.IdentityServerSPA,
}
};
var factory = new TestUrlFactory(expectedRelativeUri,providedFullUrl);
var redirectUriValidator = new RelativeRedirectUriValidator(factory);
// Act
var validator = await redirectUriValidator.IsPostLogoutRedirectUriValidAsync(providedFullUrl, expectedClient);
// Assert
Assert.True(validator);
}
[Fact]
public async Task IsPostLogoutRedirectUriValidAsync_RejectsIfTheRelativeUriIsNotRegistered_ForLocalSPAsAsync()
{
// Arrange
var expectedRelativeUri = "/logout";
var providedFullUrl = "https://localhost:5001/notregistered";
var expectedClient = new Client
{
PostLogoutRedirectUris = { expectedRelativeUri },
Properties = new Dictionary<string, string>
{
[ApplicationProfilesPropertyNames.Profile] = ApplicationProfiles.IdentityServerSPA,
}
};
var factory = new TestUrlFactory();
var redirectUriValidator = new RelativeRedirectUriValidator(factory);
// Act
var validator = await redirectUriValidator.IsPostLogoutRedirectUriValidAsync(providedFullUrl, expectedClient);
// Assert
Assert.False(validator);
}
[Fact]
public async Task IsPostLogoutRedirectUriValidAsync_CallsBaseAndSucceeds_ForValidPostLogoutRedirectUrisOnRegularClients()
{
// Arrange
var providedFullUrl = "https://localhost:5001/logout";
var expectedClient = new Client
{
PostLogoutRedirectUris = { "https://localhost:5001/logout" },
};
var factory = new TestUrlFactory();
var redirectUriValidator = new RelativeRedirectUriValidator(factory);
// Act
var validator = await redirectUriValidator.IsPostLogoutRedirectUriValidAsync(providedFullUrl, expectedClient);
// Assert
Assert.True(validator);
}
[Fact]
public async Task IsPostLogoutRedirectUriValidAsync_CallsBaseAndFails_ForInvalidPostLogoutRedirectUrisOnRegularClients()
{
// Arrange
var providedFullUrl = "https://localhost:5001/notregistered";
var expectedClient = new Client
{
PostLogoutRedirectUris = { "https://localhost:5001/logout" },
};
var factory = new TestUrlFactory();
var redirectUriValidator = new RelativeRedirectUriValidator(factory);
// Act
var validator = await redirectUriValidator.IsPostLogoutRedirectUriValidAsync(providedFullUrl, expectedClient);
// Assert
Assert.False(validator);
}
private class TestUrlFactory : IAbsoluteUrlFactory
{
private readonly string _path;
private readonly string _result;
public TestUrlFactory()
{
}
public TestUrlFactory(string path, string result)
{
_path = path;
_result = result;
}
public string GetAbsoluteUrl(string path)
{
if (_path == null || _result == null)
{
return null;
}
if (_path == path)
{
return _result;
}
return path;
}
public string GetAbsoluteUrl(HttpContext context, string path)
{
return GetAbsoluteUrl(path);
}
}
}
}

View File

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\ApiAuth.IS\Microsoft.AspNetCore.ApiAuthorization.IdentityServer.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="current.pfx">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="expired.pfx">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="future.pfx">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="test.pfx">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,63 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.`
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
public class ClientParametersTagHelperTests
{
[Fact]
public void ProcessThrows_WhenClientIdNotFound()
{
// Arrange
var clientRequestParametersProvider = new Mock<IClientRequestParametersProvider>();
clientRequestParametersProvider.Setup(c => c.GetClientParameters(It.IsAny<HttpContext>(), It.IsAny<string>())).Returns<IDictionary<string, string>>(null);
var tagHelperContext = new TagHelperContext(new TagHelperAttributeList(), new Dictionary<object, object>(), "id");
var tagHelperOutput = new TagHelperOutput("meta", new TagHelperAttributeList(), (something, encoder) => Task.FromResult<TagHelperContent>(null));
var tagHelper = new ClientParametersTagHelper(clientRequestParametersProvider.Object);
tagHelper.ClientId = "id";
tagHelper.ViewContext = new ViewContext() { HttpContext = new DefaultHttpContext() };
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => tagHelper.Process(tagHelperContext, tagHelperOutput));
Assert.Equal("Parameters for client 'id' not found.", exception.Message);
}
[Fact]
public void ProcessAddsAttributesToTag_WhenClientIdFound()
{
// Arrange
var clientRequestParametersProvider = new Mock<IClientRequestParametersProvider>();
clientRequestParametersProvider.Setup(c => c.GetClientParameters(It.IsAny<HttpContext>(), It.IsAny<string>()))
.Returns(new Dictionary<string, string>()
{
["client_id"] = "SampleApp",
["scope"] = "SampleAPI openid",
["redirect_uri"] = "https://www.example.com/auth-callback",
["response_type"] = "id_token code"
});
var tagHelperContext = new TagHelperContext(new TagHelperAttributeList(), new Dictionary<object, object>(), "id");
var tagHelperOutput = new TagHelperOutput("meta", new TagHelperAttributeList(), (something, encoder) => Task.FromResult<TagHelperContent>(null));
var tagHelper = new ClientParametersTagHelper(clientRequestParametersProvider.Object);
tagHelper.ViewContext = new ViewContext() { HttpContext = new DefaultHttpContext() };
// Act
tagHelper.Process(tagHelperContext, tagHelperOutput);
// Assert
Assert.Contains(tagHelperOutput.Attributes, th => th.Name == "data-client_id" && th.Value is string value && value == "SampleApp");
Assert.Contains(tagHelperOutput.Attributes, th => th.Name == "data-scope" && th.Value is string value && value == "SampleAPI openid");
Assert.Contains(tagHelperOutput.Attributes, th => th.Name == "data-redirect_uri" && th.Value is string value && value == "https://www.example.com/auth-callback");
Assert.Contains(tagHelperOutput.Attributes, th => th.Name == "data-response_type" && th.Value is string value && value == "id_token code");
}
}
}

View File

@ -0,0 +1,29 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Extensions.Logging;
using System;
namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
{
internal class TestLogger<TCategory> : ILogger<TCategory>, IDisposable
{
public IDisposable BeginScope<TState>(TState state)
{
return this;
}
public void Dispose()
{
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.