|
| 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 | +} |
0 commit comments