├── .gitignore ├── AspNetCore.Authentication.WsFederation ├── AspNetCore.Authentication.WsFederation.csproj ├── Events │ ├── AuthenticationFailedContext.cs │ ├── BaseWsFederationContext.cs │ ├── IWsFederationEvents.cs │ ├── MessageReceivedContext.cs │ ├── RedirectContext.cs │ ├── SecurityTokenContext.cs │ ├── SecurityTokenValidatedContext.cs │ └── WsFederationEvents.cs ├── TaskCache.cs ├── WsFederationAppBuilderExtensions.cs ├── WsFederationAuthenticationDefaults.cs ├── WsFederationAuthenticationHandler.cs ├── WsFederationAuthenticationMiddleware.cs └── WsFederationAuthenticationOptions.cs ├── AspNetCoreWsFed.sln ├── LICENSE.md ├── README.md └── Sample ├── .bowerrc ├── Controllers ├── AccountController.cs └── HomeController.cs ├── Program.cs ├── Properties └── launchSettings.json ├── Sample.csproj ├── Startup.cs ├── Views ├── Home │ ├── About.cshtml │ ├── Claims.cshtml │ ├── Contact.cshtml │ └── Index.cshtml ├── Shared │ ├── Error.cshtml │ ├── _Layout.cshtml │ └── _ValidationScriptsPartial.cshtml ├── _ViewImports.cshtml └── _ViewStart.cshtml ├── appsettings.Development.json ├── appsettings.json ├── bower.json ├── bundleconfig.json └── wwwroot ├── css └── site.css ├── favicon.ico ├── images ├── banner1.svg ├── banner2.svg ├── banner3.svg └── banner4.svg └── js └── site.js /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | *.sap 88 | 89 | # TFS 2012 Local Workspace 90 | $tf/ 91 | 92 | # Guidance Automation Toolkit 93 | *.gpState 94 | 95 | # ReSharper is a .NET coding add-in 96 | _ReSharper*/ 97 | *.[Rr]e[Ss]harper 98 | *.DotSettings.user 99 | 100 | # JustCode is a .NET coding add-in 101 | .JustCode 102 | 103 | # TeamCity is a build add-in 104 | _TeamCity* 105 | 106 | # DotCover is a Code Coverage Tool 107 | *.dotCover 108 | 109 | # NCrunch 110 | _NCrunch_* 111 | .*crunch*.local.xml 112 | nCrunchTemp_* 113 | 114 | # MightyMoose 115 | *.mm.* 116 | AutoTest.Net/ 117 | 118 | # Web workbench (sass) 119 | .sass-cache/ 120 | 121 | # Installshield output folder 122 | [Ee]xpress/ 123 | 124 | # DocProject is a documentation generator add-in 125 | DocProject/buildhelp/ 126 | DocProject/Help/*.HxT 127 | DocProject/Help/*.HxC 128 | DocProject/Help/*.hhc 129 | DocProject/Help/*.hhk 130 | DocProject/Help/*.hhp 131 | DocProject/Help/Html2 132 | DocProject/Help/html 133 | 134 | # Click-Once directory 135 | publish/ 136 | 137 | # Publish Web Output 138 | *.[Pp]ublish.xml 139 | *.azurePubxml 140 | # TODO: Comment the next line if you want to checkin your web deploy settings 141 | # but database connection strings (with potential passwords) will be unencrypted 142 | *.pubxml 143 | *.publishproj 144 | 145 | # NuGet Packages 146 | *.nupkg 147 | # The packages folder can be ignored because of Package Restore 148 | **/packages/* 149 | # except build/, which is used as an MSBuild target. 150 | !**/packages/build/ 151 | # Uncomment if necessary however generally it will be regenerated when needed 152 | #!**/packages/repositories.config 153 | 154 | # Windows Azure Build Output 155 | csx/ 156 | *.build.csdef 157 | 158 | # Windows Store app package directory 159 | AppPackages/ 160 | 161 | # Visual Studio cache files 162 | # files ending in .cache can be ignored 163 | *.[Cc]ache 164 | # but keep track of directories ending in .cache 165 | !*.[Cc]ache/ 166 | 167 | # Others 168 | ClientBin/ 169 | [Ss]tyle[Cc]op.* 170 | ~$* 171 | *~ 172 | *.dbmdl 173 | *.dbproj.schemaview 174 | *.pfx 175 | *.publishsettings 176 | node_modules/ 177 | orleans.codegen.cs 178 | 179 | # RIA/Silverlight projects 180 | Generated_Code/ 181 | 182 | # Backup & report files from converting an old project file 183 | # to a newer Visual Studio version. Backup files are not needed, 184 | # because we have git ;-) 185 | _UpgradeReport_Files/ 186 | Backup*/ 187 | UpgradeLog*.XML 188 | UpgradeLog*.htm 189 | 190 | # SQL Server files 191 | *.mdf 192 | *.ldf 193 | 194 | # Business Intelligence projects 195 | *.rdl.data 196 | *.bim.layout 197 | *.bim_*.settings 198 | 199 | # Microsoft Fakes 200 | FakesAssemblies/ 201 | 202 | # Node.js Tools for Visual Studio 203 | .ntvs_analysis.dat 204 | 205 | # Visual Studio 6 build log 206 | *.plg 207 | 208 | # Visual Studio 6 workspace options file 209 | *.opt 210 | 211 | # Visual Studio LightSwitch build output 212 | **/*.HTMLClient/GeneratedArtifacts 213 | **/*.DesktopClient/GeneratedArtifacts 214 | **/*.DesktopClient/ModelManifest.xml 215 | **/*.Server/GeneratedArtifacts 216 | **/*.Server/ModelManifest.xml 217 | _Pvt_Extensions 218 | 219 | Sample/wwwroot/lib/ 220 | Sample/wwwroot/js/site.min.js 221 | Sample/wwwroot/css/site.min.css 222 | -------------------------------------------------------------------------------- /AspNetCore.Authentication.WsFederation/AspNetCore.Authentication.WsFederation.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net452 5 | chrisdrobison 6 | 7 | A port of the Katana WsFederation middleware for ASP.NET Core. 8 | https://github.com/chrisdrobison/aspnetcore-wsfed/blob/master/LICENSE.md 9 | https://github.com/chrisdrobison/aspnetcore-wsfed 10 | https://github.com/chrisdrobison/aspnetcore-wsfed 11 | 1.1.0 12 | 1.0.0.0 13 | 1.0.0.0 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /AspNetCore.Authentication.WsFederation/Events/AuthenticationFailedContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace AspNetCore.Authentication.WsFederation 5 | { 6 | public class AuthenticationFailedContext : BaseWsFederationContext 7 | { 8 | public AuthenticationFailedContext(HttpContext context, WsFederationAuthenticationOptions options) 9 | : base(context, options) 10 | { 11 | } 12 | 13 | public Exception Exception { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /AspNetCore.Authentication.WsFederation/Events/BaseWsFederationContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Authentication; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.IdentityModel.Protocols; 5 | 6 | namespace AspNetCore.Authentication.WsFederation 7 | { 8 | public class BaseWsFederationContext : BaseControlContext 9 | { 10 | public BaseWsFederationContext(HttpContext context, WsFederationAuthenticationOptions options) : base(context) 11 | { 12 | Options = options ?? throw new ArgumentNullException(nameof(options)); 13 | } 14 | 15 | public WsFederationAuthenticationOptions Options { get; } 16 | 17 | public WsFederationMessage ProtocolMessage { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /AspNetCore.Authentication.WsFederation/Events/IWsFederationEvents.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Authentication; 3 | using AspNetCore.Authentication.WsFederation.Events; 4 | 5 | namespace AspNetCore.Authentication.WsFederation 6 | { 7 | /// 8 | /// Specifies events which the invokes to enable developer control over the authentication process. /> 9 | /// 10 | public interface IWsFederationEvents : IRemoteAuthenticationEvents 11 | { 12 | /// 13 | /// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed. 14 | /// 15 | Task AuthenticationFailed(AuthenticationFailedContext context); 16 | 17 | /// 18 | /// Invoked when a protocol message is first received. 19 | /// 20 | Task MessageReceived(MessageReceivedContext context); 21 | 22 | /// 23 | /// Invoked to manipulate redirects to the identity provider for SignIn, SignOut, or Challenge. 24 | /// 25 | Task RedirectToIdentityProvider(RedirectContext context); 26 | 27 | /// 28 | /// Invoked with the security token that has been extracted from the protocol message. 29 | /// 30 | Task SecurityTokenReceived(SecurityTokenContext context); 31 | 32 | /// 33 | /// Invoked after the security token has passed validation and a ClaimsIdentity has been generated. 34 | /// 35 | Task SecurityTokenValidated(SecurityTokenValidatedContext context); 36 | } 37 | } -------------------------------------------------------------------------------- /AspNetCore.Authentication.WsFederation/Events/MessageReceivedContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | 3 | namespace AspNetCore.Authentication.WsFederation.Events 4 | { 5 | public class MessageReceivedContext : BaseWsFederationContext 6 | { 7 | public MessageReceivedContext(HttpContext context, WsFederationAuthenticationOptions options) 8 | : base(context, options) 9 | { 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /AspNetCore.Authentication.WsFederation/Events/RedirectContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Http.Authentication; 3 | 4 | namespace AspNetCore.Authentication.WsFederation 5 | { 6 | public class RedirectContext : BaseWsFederationContext 7 | { 8 | public RedirectContext(HttpContext context, WsFederationAuthenticationOptions options) : base(context, options) 9 | { 10 | } 11 | 12 | public AuthenticationProperties Properties { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /AspNetCore.Authentication.WsFederation/Events/SecurityTokenContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | 3 | namespace AspNetCore.Authentication.WsFederation.Events 4 | { 5 | public class SecurityTokenContext : BaseWsFederationContext 6 | { 7 | public SecurityTokenContext(HttpContext context, WsFederationAuthenticationOptions options) 8 | : base(context, options) 9 | { 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /AspNetCore.Authentication.WsFederation/Events/SecurityTokenValidatedContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authentication; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace AspNetCore.Authentication.WsFederation.Events 5 | { 6 | public class SecurityTokenValidatedContext : BaseWsFederationContext 7 | { 8 | public SecurityTokenValidatedContext(HttpContext context, WsFederationAuthenticationOptions options) 9 | : base(context, options) 10 | { 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /AspNetCore.Authentication.WsFederation/Events/WsFederationEvents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Authentication; 4 | using AspNetCore.Authentication.WsFederation.Events; 5 | 6 | namespace AspNetCore.Authentication.WsFederation 7 | { 8 | /// 9 | /// Specifies events which the invokes to enable developer control over the authentication process. /> 10 | /// 11 | public class WsFederationEvents : RemoteAuthenticationEvents, IWsFederationEvents 12 | { 13 | /// 14 | /// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed. 15 | /// 16 | public Func OnAuthenticationFailed { get; set; } = 17 | context => TaskCache.CompletedTask; 18 | 19 | /// 20 | /// Invoked when a protocol message is first received. 21 | /// 22 | public Func OnMessageReceived { get; set; } = 23 | context => TaskCache.CompletedTask; 24 | 25 | /// 26 | /// Invoked to manipulate redirects to the identity provider for SignIn, SignOut, or Challenge. 27 | /// 28 | public Func OnRedirectToIdentityProvider { get; set; } = 29 | context => TaskCache.CompletedTask; 30 | 31 | /// 32 | /// Invoked with the security token that has been extracted from the protocol message. 33 | /// 34 | public Func OnSecurityTokenReceived { get; set; } = 35 | context => TaskCache.CompletedTask; 36 | 37 | /// 38 | /// Invoked after the security token has passed validation and a ClaimsIdentity has been generated. 39 | /// 40 | public Func OnSecurityTokenValidated { get; set; } = 41 | context => TaskCache.CompletedTask; 42 | 43 | 44 | public virtual Task AuthenticationFailed(AuthenticationFailedContext context) => OnAuthenticationFailed(context); 45 | 46 | public virtual Task MessageReceived(MessageReceivedContext context) => OnMessageReceived(context); 47 | 48 | public Task RedirectToIdentityProvider(RedirectContext context) => OnRedirectToIdentityProvider(context); 49 | 50 | public Task SecurityTokenReceived(SecurityTokenContext context) => OnSecurityTokenReceived(context); 51 | 52 | public Task SecurityTokenValidated(SecurityTokenValidatedContext context) => OnSecurityTokenValidated(context); 53 | } 54 | } -------------------------------------------------------------------------------- /AspNetCore.Authentication.WsFederation/TaskCache.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace AspNetCore.Authentication.WsFederation 4 | { 5 | internal static class TaskCache 6 | { 7 | /// 8 | /// A that's already completed successfully. 9 | /// 10 | /// 11 | /// We're caching this in a static readonly field to make it more inlinable and avoid the volatile lookup done 12 | /// by Task.CompletedTask. 13 | /// 14 | #if NET451 || NET452 15 | public static readonly Task CompletedTask = Task.FromResult(0); 16 | #else 17 | public static readonly Task CompletedTask = Task.CompletedTask; 18 | #endif 19 | } 20 | } -------------------------------------------------------------------------------- /AspNetCore.Authentication.WsFederation/WsFederationAppBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AspNetCore.Authentication.WsFederation; 3 | using Microsoft.Extensions.Options; 4 | 5 | namespace Microsoft.AspNetCore.Builder 6 | { 7 | public static class WsFederationAppBuilderExtensions 8 | { 9 | public static IApplicationBuilder UseWsFederationAuthentication(this IApplicationBuilder app, string wtrealm, 10 | string metadataAddress) 11 | { 12 | if (app == null) 13 | { 14 | throw new ArgumentNullException(nameof(app)); 15 | } 16 | if (string.IsNullOrEmpty(wtrealm)) 17 | { 18 | throw new ArgumentNullException(nameof(wtrealm)); 19 | } 20 | if (string.IsNullOrEmpty(metadataAddress)) 21 | { 22 | throw new ArgumentNullException(nameof(metadataAddress)); 23 | } 24 | 25 | return 26 | app.UseMiddleware( 27 | Options.Create(new WsFederationAuthenticationOptions 28 | { 29 | Wtrealm = wtrealm, 30 | MetadataAddress = metadataAddress 31 | })); 32 | } 33 | 34 | public static IApplicationBuilder UseWsFederationAuthentication(this IApplicationBuilder app, 35 | WsFederationAuthenticationOptions options) 36 | { 37 | if (app == null) 38 | { 39 | throw new ArgumentNullException(nameof(app)); 40 | } 41 | if (options == null) 42 | { 43 | throw new ArgumentNullException(nameof(options)); 44 | } 45 | 46 | if (string.IsNullOrWhiteSpace(options.TokenValidationParameters.ValidAudience)) 47 | { 48 | options.TokenValidationParameters.ValidAudience = options.Wtrealm; 49 | } 50 | 51 | return app.UseMiddleware(Options.Create(options)); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /AspNetCore.Authentication.WsFederation/WsFederationAuthenticationDefaults.cs: -------------------------------------------------------------------------------- 1 | namespace AspNetCore.Authentication.WsFederation 2 | { 3 | /// 4 | /// Default values related to WsFederation authentication middleware 5 | /// 6 | public static class WsFederationAuthenticationDefaults 7 | { 8 | /// 9 | /// The default value used for WsFederationAuthenticationOptions.AuthenticationType 10 | /// 11 | public const string AuthenticationType = "Federation"; 12 | 13 | /// 14 | /// The prefix used to provide a default WsFederationAuthenticationOptions.CookieName 15 | /// 16 | public const string CookiePrefix = "WsFederation."; 17 | 18 | /// 19 | /// The prefix used to provide a default WsFederationAuthenticationOptions.CookieName 20 | /// 21 | public const string CookieName = "WsFederationAuth"; 22 | 23 | /// 24 | /// The default value for WsFederationAuthenticationOptions.Caption. 25 | /// 26 | public const string Caption = "WsFederation"; 27 | 28 | internal const string WctxKey = "WsFedOwinState"; 29 | } 30 | } -------------------------------------------------------------------------------- /AspNetCore.Authentication.WsFederation/WsFederationAuthenticationHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IdentityModel.Tokens; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Security.Claims; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Xml; 10 | using Microsoft.AspNetCore.Authentication; 11 | using AspNetCore.Authentication.WsFederation.Events; 12 | using Microsoft.AspNetCore.Http.Authentication; 13 | using Microsoft.AspNetCore.Http.Features.Authentication; 14 | using Microsoft.Extensions.Logging; 15 | using Microsoft.IdentityModel.Extensions; 16 | using Microsoft.IdentityModel.Protocols; 17 | 18 | namespace AspNetCore.Authentication.WsFederation 19 | { 20 | public class WsFederationAuthenticationHandler : RemoteAuthenticationHandler 21 | { 22 | private readonly ILogger _logger; 23 | private WsFederationConfiguration _configuration; 24 | 25 | public WsFederationAuthenticationHandler(ILogger logger) 26 | { 27 | _logger = logger; 28 | } 29 | 30 | /// 31 | /// Authenticate the user identity with the identity provider. 32 | /// The method process the request on the endpoint defined by CallbackPath. 33 | /// 34 | protected override async Task HandleRemoteAuthenticateAsync() 35 | { 36 | // Allow login to be constrained to a specific path. 37 | if (Options.CallbackPath.HasValue && !Options.CallbackPath.Equals(Request.PathBase + Request.Path, StringComparison.OrdinalIgnoreCase)) 38 | { 39 | // Not for us. 40 | return AuthenticateResult.Skip(); 41 | } 42 | 43 | WsFederationMessage wsFederationMessage = null; 44 | 45 | // assumption: if the ContentType is "application/x-www-form-urlencoded" it should be safe to read as it is small. 46 | if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase) 47 | && !string.IsNullOrWhiteSpace(Request.ContentType) 48 | // May have media/type; charset=utf-8, allow partial match. 49 | && 50 | Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase) 51 | && Request.Body.CanRead) 52 | { 53 | if (!Request.Body.CanSeek) 54 | { 55 | Logger.LogDebug("Buffering request body"); 56 | // Buffer in case this body was not meant for us. 57 | var memoryStream = new MemoryStream(); 58 | await Request.Body.CopyToAsync(memoryStream); 59 | memoryStream.Seek(0, SeekOrigin.Begin); 60 | Request.Body = memoryStream; 61 | } 62 | var form = await Request.ReadFormAsync(); 63 | Request.Body.Seek(0, SeekOrigin.Begin); 64 | 65 | // TODO: a delegate on WsFederationAuthenticationOptions would allow for users to hook their own custom message. 66 | wsFederationMessage = new WsFederationMessage( 67 | form.Select(pair => new KeyValuePair(pair.Key, pair.Value.ToArray()))); 68 | } 69 | 70 | if (wsFederationMessage == null || !wsFederationMessage.IsSignInMessage) 71 | { 72 | if (Options.SkipUnrecognizedRequests) 73 | { 74 | // Not for us? 75 | return AuthenticateResult.Skip(); 76 | } 77 | return AuthenticateResult.Fail("No message"); 78 | } 79 | 80 | try 81 | { 82 | var messageReceivedContext = await RunMessageReceivedEventAsync(wsFederationMessage); 83 | AuthenticateResult result; 84 | if (messageReceivedContext.CheckEventResult(out result)) 85 | { 86 | return result; 87 | } 88 | 89 | if (wsFederationMessage.Wresult == null) 90 | { 91 | return AuthenticateResult.Fail("Received a sign-in message without a WResult."); 92 | } 93 | 94 | var token = wsFederationMessage.GetToken(); 95 | if (string.IsNullOrWhiteSpace(token)) 96 | { 97 | return AuthenticateResult.Fail("Received a sign-in message without a token."); 98 | } 99 | 100 | var securityTokenContext = await RunSecurityTokenReceivedEventAsync(wsFederationMessage); 101 | if (securityTokenContext.CheckEventResult(out result)) 102 | { 103 | return result; 104 | } 105 | 106 | if (_configuration == null) 107 | { 108 | _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted); 109 | } 110 | 111 | // Copy and augment to avoid cross request race conditions for updated configurations. 112 | var tvp = Options.TokenValidationParameters.Clone(); 113 | IEnumerable issuers = new[] {_configuration.Issuer}; 114 | tvp.ValidIssuers = tvp.ValidIssuers?.Concat(issuers) ?? issuers; 115 | tvp.IssuerSigningKeys = tvp.IssuerSigningKeys?.Concat(_configuration.SigningKeys) ?? 116 | _configuration.SigningKeys; 117 | 118 | SecurityToken parsedToken; 119 | var principal = Options.SecurityTokenHandlers.ValidateToken(token, tvp, out parsedToken); 120 | 121 | if (!string.IsNullOrEmpty(Options.BootStrapTokenClaimName) && parsedToken != null) 122 | { 123 | ClaimsIdentity identity = principal.Identity as ClaimsIdentity; 124 | if (identity != null) 125 | { 126 | StringBuilder sb = new StringBuilder(); 127 | var writer = XmlWriter.Create(new StringWriter(sb), new XmlWriterSettings 128 | { 129 | OmitXmlDeclaration = true 130 | }); 131 | Options.SecurityTokenHandlers[parsedToken].WriteToken(writer, parsedToken); 132 | writer.Flush(); 133 | identity.AddClaim(new Claim(Options.BootStrapTokenClaimName, Convert.ToBase64String(Encoding.UTF8.GetBytes(sb.ToString())))); 134 | } 135 | } 136 | 137 | // Retrieve our cached redirect uri 138 | var state = wsFederationMessage.Wctx; 139 | // WsFed allows for uninitiated logins, state may be missing. 140 | var properties = GetPropertiesFromWctx(state); 141 | var ticket = new AuthenticationTicket(principal, properties, 142 | Options.AuthenticationScheme); 143 | 144 | if (Options.UseTokenLifetime) 145 | { 146 | // Override any session persistence to match the token lifetime. 147 | var issued = parsedToken.ValidFrom; 148 | if (issued != DateTime.MinValue) 149 | { 150 | ticket.Properties.IssuedUtc = issued.ToUniversalTime(); 151 | } 152 | var expires = parsedToken.ValidTo; 153 | if (expires != DateTime.MinValue) 154 | { 155 | ticket.Properties.ExpiresUtc = expires.ToUniversalTime(); 156 | } 157 | ticket.Properties.AllowRefresh = false; 158 | } 159 | 160 | var securityTokenValidatedNotification = await RunSecurityTokenValidatedEventAsync(wsFederationMessage, 161 | ticket); 162 | return securityTokenValidatedNotification.CheckEventResult(out result) 163 | ? result 164 | : AuthenticateResult.Success(ticket); 165 | } 166 | catch (Exception exception) 167 | { 168 | _logger.LogError("Exception occurred while processing message: ", exception); 169 | 170 | // Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the notification. 171 | if (Options.RefreshOnIssuerKeyNotFound && 172 | exception.GetType() == typeof(SecurityTokenSignatureKeyNotFoundException)) 173 | { 174 | Options.ConfigurationManager.RequestRefresh(); 175 | } 176 | 177 | var authenticationFailedNotification = await RunAuthenticationFailedEventAsync(wsFederationMessage, 178 | exception); 179 | return authenticationFailedNotification.CheckEventResult(out AuthenticateResult result) 180 | ? result 181 | : AuthenticateResult.Fail(exception); 182 | } 183 | } 184 | 185 | /// 186 | /// Override this method to deal with 401 challenge concerns, if an authentication scheme in question 187 | /// deals an authentication interaction as part of it's request flow. (like adding a response header, or 188 | /// changing the 401 result to 302 of a login page or external sign-in location.) 189 | /// 190 | /// 191 | /// True if no other handlers should be called 192 | protected override async Task HandleUnauthorizedAsync(ChallengeContext context) 193 | { 194 | if (context == null) 195 | { 196 | throw new ArgumentNullException(nameof(context)); 197 | } 198 | 199 | Logger.LogTrace($"Entering {nameof(WsFederationAuthenticationHandler)}'s HandleUnauthorizedAsync"); 200 | 201 | if (_configuration == null) 202 | { 203 | _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted); 204 | } 205 | 206 | var baseUri = 207 | Request.Scheme + 208 | Uri.SchemeDelimiter + 209 | Request.Host + 210 | Request.PathBase; 211 | 212 | var currentUri = 213 | baseUri + 214 | Request.Path + 215 | Request.QueryString; 216 | 217 | var properties = new AuthenticationProperties(context.Properties); 218 | if (string.IsNullOrEmpty(properties.RedirectUri)) 219 | { 220 | properties.RedirectUri = currentUri; 221 | } 222 | 223 | var wsFederationMessage = new WsFederationMessage 224 | { 225 | IssuerAddress = _configuration.TokenEndpoint ?? string.Empty, 226 | Wtrealm = Options.Wtrealm, 227 | Wctx = 228 | $"{WsFederationAuthenticationDefaults.WctxKey}={Uri.EscapeDataString(Options.StateDataFormat.Protect(properties))}", 229 | Wa = WsFederationActions.SignIn, 230 | Wreply = BuildWreply(Options.CallbackPath) 231 | }; 232 | 233 | if (!string.IsNullOrWhiteSpace(Options.Wreply)) 234 | { 235 | wsFederationMessage.Wreply = Options.Wreply; 236 | } 237 | 238 | var redirectContext = new RedirectContext(Context, Options) 239 | { 240 | ProtocolMessage = wsFederationMessage, 241 | Properties = properties 242 | }; 243 | 244 | await Options.Events.RedirectToIdentityProvider(redirectContext); 245 | if (redirectContext.HandledResponse) 246 | { 247 | Logger.LogDebug("RedirectContext.HandledResponse"); 248 | return true; 249 | } 250 | if (redirectContext.Skipped) 251 | { 252 | Logger.LogDebug("RedirectContext.Skipped"); 253 | return false; 254 | } 255 | 256 | var redirectUri = redirectContext.ProtocolMessage.CreateSignInUrl(); 257 | if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute)) 258 | { 259 | Logger.LogWarning($"The sign-in redirect URI is malformed: {redirectUri}"); 260 | } 261 | Response.Redirect(redirectUri); 262 | return true; 263 | } 264 | 265 | /// 266 | /// Handles signout 267 | /// 268 | /// 269 | /// 270 | protected override async Task HandleSignOutAsync(SignOutContext context) 271 | { 272 | if (context == null) 273 | { 274 | return; 275 | } 276 | 277 | Logger.LogTrace($"Entering {nameof(WsFederationAuthenticationHandler)}'s HandleSignOutAsync"); 278 | 279 | if (_configuration == null && Options.ConfigurationManager != null) 280 | { 281 | _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted); 282 | } 283 | 284 | var wsFederationMessage = new WsFederationMessage 285 | { 286 | IssuerAddress = _configuration.TokenEndpoint ?? string.Empty, 287 | Wtrealm = Options.Wtrealm, 288 | Wa = WsFederationActions.SignOut 289 | }; 290 | 291 | var properties = new AuthenticationProperties(context.Properties); 292 | if (!string.IsNullOrEmpty(properties?.RedirectUri)) 293 | { 294 | wsFederationMessage.Wreply = properties.RedirectUri; 295 | } 296 | else if (!string.IsNullOrWhiteSpace(Options.SignOutWreply)) 297 | { 298 | wsFederationMessage.Wreply = Options.SignOutWreply; 299 | } 300 | else if (!string.IsNullOrWhiteSpace(Options.Wreply)) 301 | { 302 | wsFederationMessage.Wreply = Options.Wreply; 303 | } 304 | 305 | var redirectContext = new RedirectContext(Context, Options) 306 | { 307 | ProtocolMessage = wsFederationMessage 308 | }; 309 | await Options.Events.RedirectToIdentityProvider(redirectContext); 310 | if (redirectContext.HandledResponse) 311 | { 312 | Logger.LogDebug("RedirectContext.HandledResponse"); 313 | return; 314 | } 315 | if (redirectContext.Skipped) 316 | { 317 | Logger.LogDebug("RedirectContext.Skipped"); 318 | return; 319 | } 320 | 321 | var redirectUri = redirectContext.ProtocolMessage.CreateSignOutUrl(); 322 | if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute)) 323 | { 324 | Logger.LogWarning($"The sign-out redirect URI is malformed: {redirectUri}"); 325 | } 326 | Response.Redirect(redirectUri); 327 | } 328 | 329 | private AuthenticationProperties GetPropertiesFromWctx(string state) 330 | { 331 | AuthenticationProperties properties = null; 332 | if (!string.IsNullOrEmpty(state)) 333 | { 334 | var pairs = ParseDelimited(state); 335 | List values; 336 | if (pairs.TryGetValue(WsFederationAuthenticationDefaults.WctxKey, out values) && values.Count > 0) 337 | { 338 | var value = values.First(); 339 | properties = Options.StateDataFormat.Unprotect(value); 340 | } 341 | } 342 | return properties; 343 | } 344 | 345 | private async Task RunMessageReceivedEventAsync(WsFederationMessage message) 346 | { 347 | Logger.LogTrace($"MessageReceived: {message.BuildRedirectUrl()}"); 348 | var messageReceivedContext = new MessageReceivedContext(Context, Options) 349 | { 350 | ProtocolMessage = message 351 | }; 352 | 353 | await Options.Events.MessageReceived(messageReceivedContext); 354 | if (messageReceivedContext.HandledResponse) 355 | { 356 | Logger.LogDebug("MessageReceivedContext.HandledResponse"); 357 | } 358 | else if (messageReceivedContext.Skipped) 359 | { 360 | Logger.LogDebug("MessageReceivedContext.Skipped"); 361 | } 362 | 363 | return messageReceivedContext; 364 | } 365 | 366 | private async Task RunSecurityTokenReceivedEventAsync(WsFederationMessage message) 367 | { 368 | Logger.LogTrace($"SecurityTokenReceived: {message.GetToken()}"); 369 | var securityTokenContext = new SecurityTokenContext(Context, Options) 370 | { 371 | ProtocolMessage = message 372 | }; 373 | 374 | await Options.Events.SecurityTokenReceived(securityTokenContext); 375 | if (securityTokenContext.HandledResponse) 376 | { 377 | Logger.LogDebug("SecurityTokenContext.HandledResponse"); 378 | } 379 | else if (securityTokenContext.Skipped) 380 | { 381 | Logger.LogDebug("SecurityTokenContext.HandledResponse"); 382 | } 383 | 384 | return securityTokenContext; 385 | } 386 | 387 | private async Task RunSecurityTokenValidatedEventAsync( 388 | WsFederationMessage message, 389 | AuthenticationTicket ticket) 390 | { 391 | Logger.LogTrace($"SecurityTokenValidated: {ticket.AuthenticationScheme} {ticket.Principal.Identity.Name}"); 392 | var securityTokenValidateContext = new SecurityTokenValidatedContext(Context, Options) 393 | { 394 | ProtocolMessage = message, 395 | Ticket = ticket 396 | }; 397 | 398 | await Options.Events.SecurityTokenValidated(securityTokenValidateContext); 399 | if (securityTokenValidateContext.HandledResponse) 400 | { 401 | Logger.LogDebug("SecurityTokenValidatedContext.HandledResponse"); 402 | } 403 | else if (securityTokenValidateContext.Skipped) 404 | { 405 | Logger.LogDebug("SecurityTokenValidatedContext.Skipped"); 406 | } 407 | 408 | return securityTokenValidateContext; 409 | } 410 | 411 | private async Task RunAuthenticationFailedEventAsync(WsFederationMessage message, 412 | Exception exception) 413 | { 414 | Logger.LogTrace("AuthenticationFailed"); 415 | var authenticationFailedContext = new AuthenticationFailedContext(Context, Options) 416 | { 417 | ProtocolMessage = message, 418 | Exception = exception 419 | }; 420 | 421 | await Options.Events.AuthenticationFailed(authenticationFailedContext); 422 | if (authenticationFailedContext.HandledResponse) 423 | { 424 | Logger.LogDebug("AuthenticationFailedContext.HandledResponse"); 425 | } 426 | else if (authenticationFailedContext.Skipped) 427 | { 428 | Logger.LogDebug("AuthenticationFailedContext.Skipped"); 429 | } 430 | 431 | return authenticationFailedContext; 432 | } 433 | 434 | private string BuildWreply(string targetPath) 435 | { 436 | return Request.Scheme + "://" + Request.Host + OriginalPathBase + targetPath; 437 | } 438 | 439 | private static IDictionary> ParseDelimited(string text) 440 | { 441 | char[] delimiters = {'&', ';'}; 442 | var accumulator = new Dictionary>(StringComparer.OrdinalIgnoreCase); 443 | var textLength = text.Length; 444 | var equalIndex = text.IndexOf('='); 445 | if (equalIndex == -1) 446 | { 447 | equalIndex = textLength; 448 | } 449 | var scanIndex = 0; 450 | while (scanIndex < textLength) 451 | { 452 | var delimiterIndex = text.IndexOfAny(delimiters, scanIndex); 453 | if (delimiterIndex == -1) 454 | { 455 | delimiterIndex = textLength; 456 | } 457 | if (equalIndex < delimiterIndex) 458 | { 459 | while (scanIndex != equalIndex && char.IsWhiteSpace(text[scanIndex])) 460 | ++scanIndex; 461 | var name = text.Substring(scanIndex, equalIndex - scanIndex); 462 | var value = text.Substring(equalIndex + 1, delimiterIndex - equalIndex - 1); 463 | 464 | name = Uri.UnescapeDataString(name.Replace('+', ' ')); 465 | value = Uri.UnescapeDataString(value.Replace('+', ' ')); 466 | 467 | List existing; 468 | if (!accumulator.TryGetValue(name, out existing)) 469 | { 470 | accumulator.Add(name, new List(1) {value}); 471 | } 472 | else 473 | { 474 | existing.Add(value); 475 | } 476 | 477 | equalIndex = text.IndexOf('=', delimiterIndex); 478 | if (equalIndex == -1) 479 | { 480 | equalIndex = textLength; 481 | } 482 | } 483 | scanIndex = delimiterIndex + 1; 484 | } 485 | return accumulator; 486 | } 487 | } 488 | } -------------------------------------------------------------------------------- /AspNetCore.Authentication.WsFederation/WsFederationAuthenticationMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Net.Http; 4 | using System.Net.Security; 5 | using System.Text.Encodings.Web; 6 | using Microsoft.AspNetCore.Authentication; 7 | using Microsoft.AspNetCore.DataProtection; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.Extensions.Logging; 10 | using Microsoft.Extensions.Options; 11 | using Microsoft.IdentityModel.Extensions; 12 | using Microsoft.IdentityModel.Protocols; 13 | 14 | namespace AspNetCore.Authentication.WsFederation 15 | { 16 | public class WsFederationAuthenticationMiddleware : AuthenticationMiddleware 17 | { 18 | public WsFederationAuthenticationMiddleware(RequestDelegate next, 19 | IOptions options, 20 | IOptions sharedOptions, 21 | ILoggerFactory loggerFactory, 22 | IDataProtectionProvider dataProtectionProvider, 23 | UrlEncoder encoder) 24 | : base(next, options, loggerFactory, encoder) 25 | { 26 | if (string.IsNullOrEmpty(Options.SignInScheme)) 27 | { 28 | Options.SignInScheme = sharedOptions.Value.SignInScheme; 29 | } 30 | if (string.IsNullOrEmpty(Options.SignInScheme)) 31 | { 32 | throw new ArgumentException("Options.SignInScheme is required."); 33 | } 34 | 35 | if (string.IsNullOrWhiteSpace(Options.TokenValidationParameters.AuthenticationType)) 36 | { 37 | Options.TokenValidationParameters.AuthenticationType = Options.SignInScheme; 38 | } 39 | 40 | if (Options.StateDataFormat == null) 41 | { 42 | var dataProtector = dataProtectionProvider.CreateProtector( 43 | typeof(WsFederationAuthenticationMiddleware).FullName, 44 | typeof(string).FullName, 45 | Options.AuthenticationScheme, 46 | "v1" 47 | ); 48 | Options.StateDataFormat = new PropertiesDataFormat(dataProtector); 49 | } 50 | 51 | if (Options.SecurityTokenHandlers == null) 52 | { 53 | Options.SecurityTokenHandlers = SecurityTokenHandlerCollectionExtensions.GetDefaultHandlers(); 54 | } 55 | 56 | if (Options.Events == null) 57 | { 58 | Options.Events = new WsFederationEvents(); 59 | } 60 | 61 | Uri wreply; 62 | if (!Options.CallbackPath.HasValue && !string.IsNullOrEmpty(Options.Wreply) && 63 | Uri.TryCreate(Options.Wreply, UriKind.Absolute, out wreply)) 64 | { 65 | Options.CallbackPath = PathString.FromUriComponent(wreply); 66 | } 67 | 68 | if (Options.ConfigurationManager == null) 69 | { 70 | if (Options.Configuration != null) 71 | { 72 | Options.ConfigurationManager = 73 | new StaticConfigurationManager(Options.Configuration); 74 | } 75 | else 76 | { 77 | var httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) 78 | { 79 | Timeout = Options.BackchannelTimeout, 80 | MaxResponseContentBufferSize = 1024 * 1024 * 10 81 | }; 82 | // 10 MB 83 | Options.ConfigurationManager = 84 | new ConfigurationManager(Options.MetadataAddress, httpClient); 85 | } 86 | } 87 | } 88 | 89 | protected override AuthenticationHandler CreateHandler() 90 | { 91 | return new WsFederationAuthenticationHandler(Logger); 92 | } 93 | 94 | private static HttpMessageHandler ResolveHttpMessageHandler(WsFederationAuthenticationOptions options) 95 | { 96 | var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); 97 | 98 | // If they provided a validator, apply it or fail. 99 | if (options.BackchannelCertificateValidator != null) 100 | { 101 | // Set the cert validate callback 102 | var webRequestHandler = handler as WebRequestHandler; 103 | if (webRequestHandler == null) 104 | { 105 | throw new InvalidOperationException( 106 | "An BackchannelCertificateValidator cannot be specified at the same " + 107 | "time as an HttpMessageHandler unless it is a WebRequestHandler."); 108 | } 109 | webRequestHandler.ServerCertificateValidationCallback = 110 | new RemoteCertificateValidationCallback(options.BackchannelCertificateValidator); 111 | } 112 | 113 | return handler; 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /AspNetCore.Authentication.WsFederation/WsFederationAuthenticationOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IdentityModel.Tokens; 3 | using System.Net.Security; 4 | using System.Security.Cryptography.X509Certificates; 5 | using Microsoft.AspNetCore.Authentication; 6 | using Microsoft.AspNetCore.Builder; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.AspNetCore.Http.Authentication; 9 | using Microsoft.IdentityModel.Protocols; 10 | 11 | namespace AspNetCore.Authentication.WsFederation 12 | { 13 | public class WsFederationAuthenticationOptions : RemoteAuthenticationOptions 14 | { 15 | private SecurityTokenHandlerCollection _securityTokenHandlers; 16 | 17 | /// 18 | /// Initializes a new 19 | /// 20 | public WsFederationAuthenticationOptions() 21 | : this(WsFederationAuthenticationDefaults.AuthenticationType) 22 | { 23 | } 24 | 25 | /// 26 | /// Initializes a new 27 | /// 28 | /// corresponds to the IIdentity AuthenticationType property. . 29 | public WsFederationAuthenticationOptions(string authenticationScheme) 30 | { 31 | AutomaticAuthenticate = true; 32 | AuthenticationScheme = authenticationScheme; 33 | CallbackPath = new PathString("/signin-wsfed"); 34 | DisplayName = WsFederationAuthenticationDefaults.Caption; 35 | BackchannelTimeout = TimeSpan.FromMinutes(1); 36 | UseTokenLifetime = true; 37 | RefreshOnIssuerKeyNotFound = true; 38 | Events = new WsFederationEvents(); 39 | } 40 | 41 | /// 42 | /// Gets or sets the a pinned certificate validator to use to validate the endpoints used 43 | /// when retrieving metadata. 44 | /// 45 | /// 46 | /// The pinned certificate validator. 47 | /// 48 | /// If this property is null then the default certificate checks are performed, 49 | /// validating the subject name and if the signing chain is a trusted party. 50 | public Func BackchannelCertificateValidator { get; set; } 51 | 52 | /// 53 | /// Configuration provided directly by the developer. If provided, then MetadataAddress and the Backchannel properties 54 | /// will not be used. This information should not be updated during request processing. 55 | /// 56 | public WsFederationConfiguration Configuration { get; set; } 57 | 58 | /// 59 | /// Gets or sets the address to retrieve the wsFederation metadata 60 | /// 61 | public string MetadataAddress { get; set; } 62 | 63 | /// 64 | /// Responsible for retrieving, caching, and refreshing the configuration from metadata. 65 | /// If not provided, then one will be created using the MetadataAddress and Backchannel properties. 66 | /// 67 | public IConfigurationManager ConfigurationManager { get; set; } 68 | 69 | /// 70 | /// Indicates that the authentication session lifetime (e.g. cookies) should match that of the authentication token. 71 | /// If the token does not provide lifetime information then normal session lifetimes will be used. 72 | /// This is disabled by default. 73 | /// 74 | public bool UseTokenLifetime { get; set; } 75 | 76 | /// 77 | /// Gets or sets if a metadata refresh should be attempted after a SecurityTokenSignatureKeyNotFoundException. This allows for automatic 78 | /// recovery in the event of a signature key rollover. This is enabled by default. 79 | /// 80 | public bool RefreshOnIssuerKeyNotFound { get; set; } 81 | 82 | /// 83 | /// Gets or sets the to call when processing WsFederation messages. 84 | /// 85 | public new IWsFederationEvents Events { get; set; } 86 | 87 | /// 88 | /// Gets or sets the of s used to read and validate s. 89 | /// 90 | public SecurityTokenHandlerCollection SecurityTokenHandlers 91 | { 92 | get { return _securityTokenHandlers; } 93 | set 94 | { 95 | _securityTokenHandlers = value ?? throw new ArgumentNullException("SecurityTokenHandlers"); 96 | } 97 | } 98 | 99 | /// 100 | /// Gets or sets the type used to secure data handled by the middleware. 101 | /// 102 | public ISecureDataFormat StateDataFormat { get; set; } 103 | 104 | /// 105 | /// Gets or sets the parameters used to validate identity tokens. 106 | /// 107 | /// Contains the types and definitions required for validating a token. 108 | public TokenValidationParameters TokenValidationParameters { get; set; } = new TokenValidationParameters(); 109 | 110 | /// 111 | /// Indicates if requests to the CallbackPath may also be for other components. 112 | /// This is disabled by default. 113 | /// 114 | public bool SkipUnrecognizedRequests { get; set; } = false; 115 | 116 | /// 117 | /// Gets or sets the 'wreply'. 118 | /// 119 | public string Wreply { get; set; } 120 | 121 | /// 122 | /// Gets or sets the 'wreply' value used during sign-out. 123 | /// If none is specified then the value from the Wreply field is used. 124 | /// 125 | public string SignOutWreply { get; set; } 126 | 127 | /// 128 | /// Gets or sets the 'wtrealm'. 129 | /// 130 | public string Wtrealm { get; set; } 131 | 132 | /// 133 | /// Save the bootstrap token as a claim in the ClaimPrincipal object during authentication. 134 | /// If none (the default) or empty then the token is not saved. 135 | /// 136 | public string BootStrapTokenClaimName { get; set; } 137 | } 138 | } -------------------------------------------------------------------------------- /AspNetCoreWsFed.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26228.9 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCore.Authentication.WsFederation", "AspNetCore.Authentication.WsFederation\AspNetCore.Authentication.WsFederation.csproj", "{6FF4993E-7DE4-4B05-B314-8CDE49E67373}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample", "Sample\Sample.csproj", "{C0CA1680-E8BC-4523-85AF-4F96A0CD0D98}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {6FF4993E-7DE4-4B05-B314-8CDE49E67373}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {6FF4993E-7DE4-4B05-B314-8CDE49E67373}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {6FF4993E-7DE4-4B05-B314-8CDE49E67373}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {6FF4993E-7DE4-4B05-B314-8CDE49E67373}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {C0CA1680-E8BC-4523-85AF-4F96A0CD0D98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {C0CA1680-E8BC-4523-85AF-4F96A0CD0D98}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {C0CA1680-E8BC-4523-85AF-4F96A0CD0D98}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {C0CA1680-E8BC-4523-85AF-4F96A0CD0D98}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "{}" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright {yyyy} {name of copyright owner} 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # !!Deprecated!! 2 | 3 | This library was only ever intented to fill a temporary gap in functionality that existed in ASP.NET MVC, but had not quite made it to ASP.NET Core. That has now been addressed. As such I will no longer be maintaining this code base. Please consider using the official WsFed library here: 4 | 5 | https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.WsFederation 6 | 7 | # WsFederation for ASP.NET Core 8 | 9 | This is a port of the Katana WsFederation middleware for ASP.NET Core. This project has a hard dependency on the full .NET Framework as many of the required BCL classes do not exist in the .NET Standard. 10 | 11 | # Installation 12 | 13 | ``` 14 | PM> Install-Package AspNetCore.Authentication.WsFederation 15 | ``` 16 | -------------------------------------------------------------------------------- /Sample/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "wwwroot/lib" 3 | } 4 | -------------------------------------------------------------------------------- /Sample/Controllers/AccountController.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using AspNetCore.Authentication.WsFederation; 3 | using Microsoft.AspNetCore.Authentication.Cookies; 4 | using Microsoft.AspNetCore.Http.Authentication; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | // For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 8 | 9 | namespace Sample.Controllers 10 | { 11 | public class AccountController : Controller 12 | { 13 | // GET: // 14 | public IActionResult Login(string source = "/") 15 | { 16 | if (User.Identities.Any(identity => identity.IsAuthenticated)) 17 | { 18 | return Redirect(source); 19 | } 20 | 21 | return Challenge(new AuthenticationProperties { RedirectUri = source }, 22 | WsFederationAuthenticationDefaults.AuthenticationType); 23 | } 24 | 25 | public IActionResult Logout() 26 | { 27 | return SignOut(new AuthenticationProperties { RedirectUri = "http://localhost:8550/" }, 28 | CookieAuthenticationDefaults.AuthenticationScheme, 29 | WsFederationAuthenticationDefaults.AuthenticationType); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /Sample/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Authorization; 7 | 8 | namespace Sample.Controllers 9 | { 10 | public class HomeController : Controller 11 | { 12 | public IActionResult Index() 13 | { 14 | return View(); 15 | } 16 | 17 | public IActionResult About() 18 | { 19 | ViewData["Message"] = "Your application description page."; 20 | 21 | return View(); 22 | } 23 | 24 | public IActionResult Contact() 25 | { 26 | ViewData["Message"] = "Your contact page."; 27 | 28 | return View(); 29 | } 30 | [Authorize] 31 | public IActionResult Claims() 32 | { 33 | return View(); 34 | } 35 | 36 | public IActionResult Error() 37 | { 38 | return View(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Hosting; 7 | 8 | namespace Sample 9 | { 10 | public class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | var host = new WebHostBuilder() 15 | .UseKestrel() 16 | .UseContentRoot(Directory.GetCurrentDirectory()) 17 | .UseIISIntegration() 18 | .UseStartup() 19 | .UseApplicationInsights() 20 | .Build(); 21 | 22 | host.Run(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:8550/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "Sample": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "http://localhost:8551" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sample/Sample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net47 5 | 6 | 7 | 8 | $(PackageTargetFallback);portable-net45+win8+wp8+wpa81; 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Sample/Startup.cs: -------------------------------------------------------------------------------- 1 | using AspNetCore.Authentication.WsFederation; 2 | using Microsoft.AspNetCore.Authentication.Cookies; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace Sample 10 | { 11 | public class Startup 12 | { 13 | public Startup(IHostingEnvironment env) 14 | { 15 | var builder = new ConfigurationBuilder() 16 | .SetBasePath(env.ContentRootPath) 17 | .AddJsonFile("appsettings.json", false, true) 18 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true) 19 | .AddEnvironmentVariables(); 20 | Configuration = builder.Build(); 21 | } 22 | 23 | public IConfigurationRoot 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 | // Add framework services. 29 | services.AddMvc(); 30 | services.AddAuthentication( 31 | options => options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme); 32 | } 33 | 34 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 35 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 36 | { 37 | loggerFactory.AddConsole(Configuration.GetSection("Logging")); 38 | loggerFactory.AddDebug(); 39 | 40 | if (env.IsDevelopment()) 41 | { 42 | app.UseDeveloperExceptionPage(); 43 | app.UseBrowserLink(); 44 | } 45 | else 46 | { 47 | app.UseExceptionHandler("/Home/Error"); 48 | } 49 | 50 | app.UseCookieAuthentication(new CookieAuthenticationOptions() 51 | { 52 | AutomaticChallenge = true, 53 | ReturnUrlParameter = "source" 54 | }); 55 | 56 | app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions 57 | { 58 | Wtrealm = "https://cdrobisonxactware.onmicrosoft.com/eb81d25f-9da5-4ed9-af4d-709769a91c6a", 59 | MetadataAddress = 60 | "https://login.windows.net/96708752-4aec-4318-beca-c25ff9bffc1f/FederationMetadata/2007-06/FederationMetadata.xml" 61 | }); 62 | 63 | app.UseStaticFiles(); 64 | 65 | app.UseMvc(routes => 66 | { 67 | routes.MapRoute( 68 | "default", 69 | "{controller=Home}/{action=Index}/{id?}"); 70 | }); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /Sample/Views/Home/About.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "About"; 3 | } 4 |

@ViewData["Title"].

5 |

@ViewData["Message"]

6 | 7 |

Use this area to provide additional information.

8 | -------------------------------------------------------------------------------- /Sample/Views/Home/Claims.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Claims"; 3 | } 4 | 5 |

Claims

6 |
    7 | @foreach (var claim in User.Claims) 8 | { 9 |
  • @claim.Type - @claim.Value
  • 10 | } 11 |
12 | -------------------------------------------------------------------------------- /Sample/Views/Home/Contact.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Contact"; 3 | } 4 |

@ViewData["Title"].

5 |

@ViewData["Message"]

6 | 7 |
8 | One Microsoft Way
9 | Redmond, WA 98052-6399
10 | P: 11 | 425.555.0100 12 |
13 | 14 |
15 | Support: Support@example.com
16 | Marketing: Marketing@example.com 17 |
18 | -------------------------------------------------------------------------------- /Sample/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Home Page"; 3 | } 4 | 5 | 67 | 68 |
69 |
70 |

Application uses

71 |
    72 |
  • Sample pages using ASP.NET Core MVC
  • 73 |
  • Bower for managing client-side libraries
  • 74 |
  • Theming using Bootstrap
  • 75 |
76 |
77 | 88 | 100 |
101 |

Run & Deploy

102 | 107 |
108 |
109 | -------------------------------------------------------------------------------- /Sample/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Error"; 3 | } 4 | 5 |

Error.

6 |

An error occurred while processing your request.

7 | 8 |

Development Mode

9 |

10 | Swapping to Development environment will display more detailed information about the error that occurred. 11 |

12 |

13 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 14 |

15 | -------------------------------------------------------------------------------- /Sample/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @inject Microsoft.ApplicationInsights.AspNetCore.JavaScriptSnippet JavaScriptSnippet 2 | 3 | 4 | 5 | 6 | 7 | @ViewData["Title"] - Sample 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | @Html.Raw(JavaScriptSnippet.FullScript) 20 | 21 | 22 | 45 |
46 | @RenderBody() 47 |
48 |
49 |

© 2017 - Sample

50 |
51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 71 | 72 | 73 | 74 | @RenderSection("Scripts", required: false) 75 | 76 | 77 | -------------------------------------------------------------------------------- /Sample/Views/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /Sample/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Sample 2 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 3 | -------------------------------------------------------------------------------- /Sample/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /Sample/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Sample/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asp.net", 3 | "private": true, 4 | "dependencies": { 5 | "bootstrap": "3.3.7", 6 | "jquery": "2.2.0", 7 | "jquery-validation": "1.14.0", 8 | "jquery-validation-unobtrusive": "3.2.6" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sample/bundleconfig.json: -------------------------------------------------------------------------------- 1 | // Configure bundling and minification for the project. 2 | // More info at https://go.microsoft.com/fwlink/?LinkId=808241 3 | [ 4 | { 5 | "outputFileName": "wwwroot/css/site.min.css", 6 | // An array of relative input file paths. Globbing patterns supported 7 | "inputFiles": [ 8 | "wwwroot/css/site.css" 9 | ] 10 | }, 11 | { 12 | "outputFileName": "wwwroot/js/site.min.js", 13 | "inputFiles": [ 14 | "wwwroot/js/site.js" 15 | ], 16 | // Optionally specify minification options 17 | "minify": { 18 | "enabled": true, 19 | "renameLocals": true 20 | }, 21 | // Optionally generate .map file 22 | "sourceMap": false 23 | } 24 | ] 25 | -------------------------------------------------------------------------------- /Sample/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | padding-bottom: 20px; 4 | } 5 | 6 | /* Wrapping element */ 7 | /* Set some basic padding to keep content from hitting the edges */ 8 | .body-content { 9 | padding-left: 15px; 10 | padding-right: 15px; 11 | } 12 | 13 | /* Set widths on the form inputs since otherwise they're 100% wide */ 14 | input, 15 | select, 16 | textarea { 17 | max-width: 280px; 18 | } 19 | 20 | /* Carousel */ 21 | .carousel-caption p { 22 | font-size: 20px; 23 | line-height: 1.4; 24 | } 25 | 26 | /* Make .svg files in the carousel display properly in older browsers */ 27 | .carousel-inner .item img[src$=".svg"] { 28 | width: 100%; 29 | } 30 | 31 | /* Hide/rearrange for smaller screens */ 32 | @media screen and (max-width: 767px) { 33 | /* Hide captions */ 34 | .carousel-caption { 35 | display: none; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sample/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisdrobison/aspnetcore-wsfed/507c0d0cdaf6831757aec907bb750133dac7eb89/Sample/wwwroot/favicon.ico -------------------------------------------------------------------------------- /Sample/wwwroot/images/banner1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Sample/wwwroot/images/banner2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Sample/wwwroot/images/banner3.svg: -------------------------------------------------------------------------------- 1 | banner3b -------------------------------------------------------------------------------- /Sample/wwwroot/images/banner4.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Sample/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Write your Javascript code. 2 | --------------------------------------------------------------------------------