Skip to content

Commit d95109c

Browse files
committed
#43 Add Microsoft.AspNetCore.Authentication.WsFederation, samples, and tests.
#1443 Block unsolicited wsfed logins by default. #1520 Update WsFed to use the 2.0 event structure #1425 Implement WsFed remote signout cleanup Rework WsFed RemoteSignOutPath logic to work with ADFS #1581 Update versions, dependencies.
1 parent 2b1dab2 commit d95109c

31 files changed

+2614
-1
lines changed

Security.sln

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Microsoft Visual Studio Solution File, Format Version 12.00
22
# Visual Studio 15
3-
VisualStudioVersion = 15.0.27004.2002
3+
VisualStudioVersion = 15.0.27130.2027
44
MinimumVisualStudioVersion = 15.0.26730.03
55
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4D2B6A51-2F9F-44F5-8131-EA5CAC053652}"
66
ProjectSection(SolutionItems) = preProject
@@ -75,6 +75,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Author
7575
EndProject
7676
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CookiePolicySample", "samples\CookiePolicySample\CookiePolicySample.csproj", "{24A28F5D-E5A9-4CA8-B0D2-924A1F8BE14E}"
7777
EndProject
78+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authentication.WsFederation", "src\Microsoft.AspNetCore.Authentication.WsFederation\Microsoft.AspNetCore.Authentication.WsFederation.csproj", "{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}"
79+
EndProject
80+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WsFedSample", "samples\WsFedSample\WsFedSample.csproj", "{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}"
81+
EndProject
7882
Global
7983
GlobalSection(SolutionConfigurationPlatforms) = preSolution
8084
Debug|Any CPU = Debug|Any CPU
@@ -481,6 +485,38 @@ Global
481485
{24A28F5D-E5A9-4CA8-B0D2-924A1F8BE14E}.Release|x64.Build.0 = Release|Any CPU
482486
{24A28F5D-E5A9-4CA8-B0D2-924A1F8BE14E}.Release|x86.ActiveCfg = Release|Any CPU
483487
{24A28F5D-E5A9-4CA8-B0D2-924A1F8BE14E}.Release|x86.Build.0 = Release|Any CPU
488+
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
489+
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Debug|Any CPU.Build.0 = Debug|Any CPU
490+
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
491+
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
492+
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Debug|x64.ActiveCfg = Debug|Any CPU
493+
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Debug|x64.Build.0 = Debug|Any CPU
494+
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Debug|x86.ActiveCfg = Debug|Any CPU
495+
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Debug|x86.Build.0 = Debug|Any CPU
496+
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Release|Any CPU.ActiveCfg = Release|Any CPU
497+
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Release|Any CPU.Build.0 = Release|Any CPU
498+
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
499+
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Release|Mixed Platforms.Build.0 = Release|Any CPU
500+
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Release|x64.ActiveCfg = Release|Any CPU
501+
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Release|x64.Build.0 = Release|Any CPU
502+
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Release|x86.ActiveCfg = Release|Any CPU
503+
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29}.Release|x86.Build.0 = Release|Any CPU
504+
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
505+
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Debug|Any CPU.Build.0 = Debug|Any CPU
506+
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
507+
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
508+
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Debug|x64.ActiveCfg = Debug|Any CPU
509+
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Debug|x64.Build.0 = Debug|Any CPU
510+
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Debug|x86.ActiveCfg = Debug|Any CPU
511+
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Debug|x86.Build.0 = Debug|Any CPU
512+
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|Any CPU.ActiveCfg = Release|Any CPU
513+
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|Any CPU.Build.0 = Release|Any CPU
514+
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
515+
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|Mixed Platforms.Build.0 = Release|Any CPU
516+
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|x64.ActiveCfg = Release|Any CPU
517+
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|x64.Build.0 = Release|Any CPU
518+
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|x86.ActiveCfg = Release|Any CPU
519+
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|x86.Build.0 = Release|Any CPU
484520
EndGlobalSection
485521
GlobalSection(SolutionProperties) = preSolution
486522
HideSolutionNode = FALSE
@@ -511,6 +547,8 @@ Global
511547
{51563775-C659-4907-9BAF-9995BAB87D01} = {7BF11F3A-60B6-4796-B504-579C67FFBA34}
512548
{58194599-F07D-47A3-9DF2-E21A22C5EF9E} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
513549
{24A28F5D-E5A9-4CA8-B0D2-924A1F8BE14E} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF}
550+
{B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
551+
{5EC2E398-E46A-430D-8E4B-E91C8FC3E800} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF}
514552
EndGlobalSection
515553
GlobalSection(ExtensibilityGlobals) = postSolution
516554
SolutionGuid = {ABF8089E-43D0-4010-84A7-7A9DCFE49357}

build/dependencies.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,15 @@
3232
<MicrosoftExtensionsWebEncodersPackageVersion>2.1.0-preview2-30187</MicrosoftExtensionsWebEncodersPackageVersion>
3333
<MicrosoftIdentityModelClientsActiveDirectoryPackageVersion>3.14.2</MicrosoftIdentityModelClientsActiveDirectoryPackageVersion>
3434
<MicrosoftIdentityModelProtocolsOpenIdConnectPackageVersion>5.2.0</MicrosoftIdentityModelProtocolsOpenIdConnectPackageVersion>
35+
<MicrosoftIdentityModelProtocolsWsFederationPackageVersion>5.2.0</MicrosoftIdentityModelProtocolsWsFederationPackageVersion>
3536
<MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion>
3637
<MicrosoftNETCoreApp21PackageVersion>2.1.0-preview2-26130-04</MicrosoftNETCoreApp21PackageVersion>
3738
<MicrosoftNETTestSdkPackageVersion>15.6.0</MicrosoftNETTestSdkPackageVersion>
3839
<MicrosoftOwinSecurityCookiesPackageVersion>3.0.1</MicrosoftOwinSecurityCookiesPackageVersion>
3940
<MicrosoftOwinSecurityPackageVersion>3.0.1</MicrosoftOwinSecurityPackageVersion>
4041
<MicrosoftOwinTestingPackageVersion>3.0.1</MicrosoftOwinTestingPackageVersion>
4142
<NewtonsoftJsonPackageVersion>10.0.1</NewtonsoftJsonPackageVersion>
43+
<SystemIdentityModelTokensJwtPackageVersion>5.2.0</SystemIdentityModelTokensJwtPackageVersion>
4244
<XunitAnalyzersPackageVersion>0.8.0</XunitAnalyzersPackageVersion>
4345
<XunitPackageVersion>2.3.1</XunitPackageVersion>
4446
<XunitRunnerVisualStudioPackageVersion>2.4.0-beta.1.build3945</XunitRunnerVisualStudioPackageVersion>

samples/WsFedSample/Program.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Net;
6+
using System.Reflection;
7+
using System.Security.Cryptography.X509Certificates;
8+
using System.Threading.Tasks;
9+
using Microsoft.AspNetCore;
10+
using Microsoft.AspNetCore.Hosting;
11+
using Microsoft.Extensions.Configuration;
12+
using Microsoft.Extensions.FileProviders;
13+
using Microsoft.Extensions.Logging;
14+
15+
namespace WsFedSample
16+
{
17+
public class Program
18+
{
19+
public static void Main(string[] args)
20+
{
21+
var host = new WebHostBuilder()
22+
.ConfigureLogging(factory =>
23+
{
24+
factory.AddConsole();
25+
factory.AddDebug();
26+
factory.AddFilter("Console", level => level >= LogLevel.Information);
27+
factory.AddFilter("Debug", level => level >= LogLevel.Information);
28+
})
29+
.UseKestrel(options =>
30+
{
31+
options.Listen(IPAddress.Loopback, 44307, listenOptions =>
32+
{
33+
// Configure SSL
34+
var serverCertificate = LoadCertificate();
35+
listenOptions.UseHttps(serverCertificate);
36+
});
37+
})
38+
.UseContentRoot(Directory.GetCurrentDirectory())
39+
.UseIISIntegration()
40+
.UseStartup<Startup>()
41+
.Build();
42+
43+
host.Run();
44+
}
45+
46+
private static X509Certificate2 LoadCertificate()
47+
{
48+
var assembly = typeof(Startup).GetTypeInfo().Assembly;
49+
var embeddedFileProvider = new EmbeddedFileProvider(assembly, "WsFedSample");
50+
var certificateFileInfo = embeddedFileProvider.GetFileInfo("compiler/resources/cert.pfx");
51+
using (var certificateStream = certificateFileInfo.CreateReadStream())
52+
{
53+
byte[] certificatePayload;
54+
using (var memoryStream = new MemoryStream())
55+
{
56+
certificateStream.CopyTo(memoryStream);
57+
certificatePayload = memoryStream.ToArray();
58+
}
59+
60+
return new X509Certificate2(certificatePayload, "testPassword");
61+
}
62+
}
63+
}
64+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"iisSettings": {
3+
"windowsAuthentication": false,
4+
"anonymousAuthentication": true,
5+
"iisExpress": {
6+
"applicationUrl": "https://localhost:44307/",
7+
"sslPort": 44318
8+
}
9+
},
10+
"profiles": {
11+
"IIS Express": {
12+
"commandName": "IISExpress",
13+
"launchBrowser": true,
14+
"launchUrl": "https://localhost:44307/",
15+
"environmentVariables": {
16+
"ASPNETCORE_ENVIRONMENT": "Development"
17+
}
18+
},
19+
"WsFedSample": {
20+
"commandName": "Project",
21+
"launchBrowser": true,
22+
"applicationUrl": "https://localhost:44307/",
23+
"environmentVariables": {
24+
"ASPNETCORE_ENVIRONMENT": "Development"
25+
}
26+
}
27+
}
28+
}

samples/WsFedSample/Startup.cs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text.Encodings.Web;
5+
using System.Threading.Tasks;
6+
using Microsoft.AspNetCore.Authentication;
7+
using Microsoft.AspNetCore.Authentication.Cookies;
8+
using Microsoft.AspNetCore.Authentication.WsFederation;
9+
using Microsoft.AspNetCore.Builder;
10+
using Microsoft.AspNetCore.Http;
11+
using Microsoft.Extensions.Configuration;
12+
using Microsoft.Extensions.DependencyInjection;
13+
14+
namespace WsFedSample
15+
{
16+
public class Startup
17+
{
18+
public Startup(IConfiguration configuration)
19+
{
20+
Configuration = configuration;
21+
}
22+
23+
public IConfiguration Configuration { get; }
24+
25+
// This method gets called by the runtime. Use this method to add services to the container.
26+
public void ConfigureServices(IServiceCollection services)
27+
{
28+
services.AddAuthentication(sharedOptions =>
29+
{
30+
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
31+
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
32+
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
33+
})
34+
.AddWsFederation(options =>
35+
{
36+
options.Wtrealm = "https://Tratcheroutlook.onmicrosoft.com/WsFedSample";
37+
options.MetadataAddress = "https://login.windows.net/cdc690f9-b6b8-4023-813a-bae7143d1f87/FederationMetadata/2007-06/FederationMetadata.xml";
38+
// options.CallbackPath = "/";
39+
// options.SkipUnrecognizedRequests = true;
40+
})
41+
.AddCookie();
42+
}
43+
44+
public void Configure(IApplicationBuilder app)
45+
{
46+
app.UseDeveloperExceptionPage();
47+
app.UseAuthentication();
48+
49+
app.Run(async context =>
50+
{
51+
if (context.Request.Path.Equals("/signedout"))
52+
{
53+
await WriteHtmlAsync(context.Response, async res =>
54+
{
55+
await res.WriteAsync($"<h1>You have been signed out.</h1>");
56+
await res.WriteAsync("<a class=\"btn btn-link\" href=\"/\">Sign In</a>");
57+
});
58+
return;
59+
}
60+
61+
if (context.Request.Path.Equals("/signout"))
62+
{
63+
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
64+
await WriteHtmlAsync(context.Response, async res =>
65+
{
66+
await context.Response.WriteAsync($"<h1>Signed out {HtmlEncode(context.User.Identity.Name)}</h1>");
67+
await context.Response.WriteAsync("<a class=\"btn btn-link\" href=\"/\">Sign In</a>");
68+
});
69+
return;
70+
}
71+
72+
if (context.Request.Path.Equals("/signout-remote"))
73+
{
74+
// Redirects
75+
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
76+
await context.SignOutAsync(WsFederationDefaults.AuthenticationScheme, new AuthenticationProperties()
77+
{
78+
RedirectUri = "/signedout"
79+
});
80+
return;
81+
}
82+
83+
if (context.Request.Path.Equals("/Account/AccessDenied"))
84+
{
85+
await WriteHtmlAsync(context.Response, async res =>
86+
{
87+
await context.Response.WriteAsync($"<h1>Access Denied for user {HtmlEncode(context.User.Identity.Name)} to resource '{HtmlEncode(context.Request.Query["ReturnUrl"])}'</h1>");
88+
await context.Response.WriteAsync("<a class=\"btn btn-link\" href=\"/signout\">Sign Out</a>");
89+
});
90+
return;
91+
}
92+
93+
// DefaultAuthenticateScheme causes User to be set
94+
var user = context.User;
95+
96+
// This is what [Authorize] calls
97+
// var user = await context.AuthenticateAsync();
98+
99+
// This is what [Authorize(ActiveAuthenticationSchemes = WsFederationDefaults.AuthenticationScheme)] calls
100+
// var user = await context.AuthenticateAsync(WsFederationDefaults.AuthenticationScheme);
101+
102+
// Not authenticated
103+
if (user == null || !user.Identities.Any(identity => identity.IsAuthenticated))
104+
{
105+
// This is what [Authorize] calls
106+
await context.ChallengeAsync();
107+
108+
// This is what [Authorize(ActiveAuthenticationSchemes = WsFederationDefaults.AuthenticationScheme)] calls
109+
// await context.ChallengeAsync(WsFederationDefaults.AuthenticationScheme);
110+
111+
return;
112+
}
113+
114+
// Authenticated, but not authorized
115+
if (context.Request.Path.Equals("/restricted") && !user.Identities.Any(identity => identity.HasClaim("special", "true")))
116+
{
117+
await context.ForbidAsync();
118+
return;
119+
}
120+
121+
await WriteHtmlAsync(context.Response, async response =>
122+
{
123+
await response.WriteAsync($"<h1>Hello Authenticated User {HtmlEncode(user.Identity.Name)}</h1>");
124+
await response.WriteAsync("<a class=\"btn btn-default\" href=\"/restricted\">Restricted</a>");
125+
await response.WriteAsync("<a class=\"btn btn-default\" href=\"/signout\">Sign Out</a>");
126+
await response.WriteAsync("<a class=\"btn btn-default\" href=\"/signout-remote\">Sign Out Remote</a>");
127+
128+
await response.WriteAsync("<h2>Claims:</h2>");
129+
await WriteTableHeader(response, new string[] { "Claim Type", "Value" }, context.User.Claims.Select(c => new string[] { c.Type, c.Value }));
130+
});
131+
});
132+
}
133+
134+
private static async Task WriteHtmlAsync(HttpResponse response, Func<HttpResponse, Task> writeContent)
135+
{
136+
var bootstrap = "<link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css\" integrity=\"sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u\" crossorigin=\"anonymous\">";
137+
138+
response.ContentType = "text/html";
139+
await response.WriteAsync($"<html><head>{bootstrap}</head><body><div class=\"container\">");
140+
await writeContent(response);
141+
await response.WriteAsync("</div></body></html>");
142+
}
143+
144+
private static async Task WriteTableHeader(HttpResponse response, IEnumerable<string> columns, IEnumerable<IEnumerable<string>> data)
145+
{
146+
await response.WriteAsync("<table class=\"table table-condensed\">");
147+
await response.WriteAsync("<tr>");
148+
foreach (var column in columns)
149+
{
150+
await response.WriteAsync($"<th>{HtmlEncode(column)}</th>");
151+
}
152+
await response.WriteAsync("</tr>");
153+
foreach (var row in data)
154+
{
155+
await response.WriteAsync("<tr>");
156+
foreach (var column in row)
157+
{
158+
await response.WriteAsync($"<td>{HtmlEncode(column)}</td>");
159+
}
160+
await response.WriteAsync("</tr>");
161+
}
162+
await response.WriteAsync("</table>");
163+
}
164+
165+
private static string HtmlEncode(string content) =>
166+
string.IsNullOrEmpty(content) ? string.Empty : HtmlEncoder.Default.Encode(content);
167+
}
168+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Authentication.Cookies\Microsoft.AspNetCore.Authentication.Cookies.csproj" />
9+
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Authentication.WsFederation\Microsoft.AspNetCore.Authentication.WsFederation.csproj" />
10+
</ItemGroup>
11+
12+
<ItemGroup>
13+
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(MicrosoftAspNetCoreServerIISIntegrationPackageVersion)" />
14+
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
15+
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Https" Version="$(MicrosoftAspNetCoreServerKestrelHttpsPackageVersion)" />
16+
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="$(MicrosoftExtensionsConfigurationEnvironmentVariablesPackageVersion)" />
17+
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="$(MicrosoftAspNetCoreDiagnosticsPackageVersion)" />
18+
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="$(MicrosoftExtensionsFileProvidersEmbeddedPackageVersion)" />
19+
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
20+
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(MicrosoftExtensionsLoggingDebugPackageVersion)" />
21+
</ItemGroup>
22+
23+
<ItemGroup>
24+
<EmbeddedResource Include="compiler\resources\cert.pfx" />
25+
</ItemGroup>
26+
27+
</Project>
2.42 KB
Binary file not shown.

0 commit comments

Comments
 (0)