using System; using System.Collections.Generic; using System.Globalization; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Net.Http; using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Newtonsoft.Json.Linq; namespace OpenIdConnectSample { public class Startup { public Startup(IHostingEnvironment env) { Environment = env; var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath); if (env.IsDevelopment()) { // For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709 builder.AddUserSecrets(); } builder.AddEnvironmentVariables(); Configuration = builder.Build(); } public IConfiguration Configuration { get; set; } public IHostingEnvironment Environment { get; set; } public void ConfigureServices(IServiceCollection services) { JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); services.AddAuthentication(sharedOptions => { sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie() .AddOpenIdConnect(o => { o.ClientId = Configuration["oidc:clientid"]; o.ClientSecret = Configuration["oidc:clientsecret"]; // for code flow o.Authority = Configuration["oidc:authority"]; o.ResponseType = OpenIdConnectResponseType.CodeIdToken; o.SaveTokens = true; o.GetClaimsFromUserInfoEndpoint = true; o.ClaimActions.MapAllExcept("aud", "iss", "iat", "nbf", "exp", "aio", "c_hash", "uti", "nonce"); o.Events = new OpenIdConnectEvents() { OnAuthenticationFailed = c => { c.HandleResponse(); c.Response.StatusCode = 500; c.Response.ContentType = "text/plain"; if (Environment.IsDevelopment()) { // Debug only, in production do not share exceptions with the remote host. return c.Response.WriteAsync(c.Exception.ToString()); } return c.Response.WriteAsync("An error occurred processing your authentication."); } }; }); } public void Configure(IApplicationBuilder app, IOptionsMonitor optionsMonitor) { app.UseDeveloperExceptionPage(); app.UseAuthentication(); app.Run(async context => { var response = context.Response; if (context.Request.Path.Equals("/signedout")) { await WriteHtmlAsync(response, async res => { await res.WriteAsync($"

You have been signed out.

"); await res.WriteAsync("Home"); }); return; } if (context.Request.Path.Equals("/signout")) { await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); await WriteHtmlAsync(response, async res => { await res.WriteAsync($"

Signed out {HtmlEncode(context.User.Identity.Name)}

"); await res.WriteAsync("Home"); }); return; } if (context.Request.Path.Equals("/signout-remote")) { // Redirects await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); await context.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties() { RedirectUri = "/signedout" }); return; } if (context.Request.Path.Equals("/Account/AccessDenied")) { await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); await WriteHtmlAsync(response, async res => { await res.WriteAsync($"

Access Denied for user {HtmlEncode(context.User.Identity.Name)} to resource '{HtmlEncode(context.Request.Query["ReturnUrl"])}'

"); await res.WriteAsync("Sign Out"); await res.WriteAsync("Home"); }); return; } // DefaultAuthenticateScheme causes User to be set // var user = context.User; // This is what [Authorize] calls var userResult = await context.AuthenticateAsync(); var user = userResult.Principal; var props = userResult.Properties; // This is what [Authorize(ActiveAuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme)] calls // var user = await context.AuthenticateAsync(OpenIdConnectDefaults.AuthenticationScheme); // Not authenticated if (user == null || !user.Identities.Any(identity => identity.IsAuthenticated)) { // This is what [Authorize] calls await context.ChallengeAsync(); // This is what [Authorize(ActiveAuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme)] calls // await context.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme); return; } // Authenticated, but not authorized if (context.Request.Path.Equals("/restricted") && !user.Identities.Any(identity => identity.HasClaim("special", "true"))) { await context.ForbidAsync(); return; } if (context.Request.Path.Equals("/refresh")) { var refreshToken = props.GetTokenValue("refresh_token"); if (string.IsNullOrEmpty(refreshToken)) { await WriteHtmlAsync(response, async res => { await res.WriteAsync($"No refresh_token is available.
"); await res.WriteAsync("Sign Out"); }); return; } var options = optionsMonitor.Get(OpenIdConnectDefaults.AuthenticationScheme); var metadata = await options.ConfigurationManager.GetConfigurationAsync(context.RequestAborted); var pairs = new Dictionary() { { "client_id", options.ClientId }, { "client_secret", options.ClientSecret }, { "grant_type", "refresh_token" }, { "refresh_token", refreshToken } }; var content = new FormUrlEncodedContent(pairs); var tokenResponse = await options.Backchannel.PostAsync(metadata.TokenEndpoint, content, context.RequestAborted); tokenResponse.EnsureSuccessStatusCode(); var payload = JObject.Parse(await tokenResponse.Content.ReadAsStringAsync()); // Persist the new acess token props.UpdateTokenValue("access_token", payload.Value("access_token")); props.UpdateTokenValue("refresh_token", payload.Value("refresh_token")); if (int.TryParse(payload.Value("expires_in"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var seconds)) { var expiresAt = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(seconds); props.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture)); } await context.SignInAsync(user, props); await WriteHtmlAsync(response, async res => { await res.WriteAsync($"

Refreshed.

"); await res.WriteAsync("Refresh tokens"); await res.WriteAsync("Home"); await res.WriteAsync("

Tokens:

"); await WriteTableHeader(res, new string[] { "Token Type", "Value" }, props.GetTokens().Select(token => new string[] { token.Name, token.Value })); await res.WriteAsync("

Payload:

"); await res.WriteAsync(HtmlEncoder.Default.Encode(payload.ToString()).Replace(",", ",
") + "
"); }); return; } if (context.Request.Path.Equals("/login-challenge")) { // Challenge the user authentication, and force a login prompt by overwriting the // "prompt". This could be used for example to require the user to re-enter their // credentials at the authentication provider, to add an extra confirmation layer. await context.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new OpenIdConnectChallengeProperties() { Prompt = "login", // it is also possible to specify different scopes, e.g. // Scope = new string[] { "openid", "profile", "other" } }); return; } await WriteHtmlAsync(response, async res => { await res.WriteAsync($"

Hello Authenticated User {HtmlEncode(user.Identity.Name)}

"); await res.WriteAsync("Refresh tokens"); await res.WriteAsync("Restricted"); await res.WriteAsync("Login challenge"); await res.WriteAsync("Sign Out"); await res.WriteAsync("Sign Out Remote"); await res.WriteAsync("

Claims:

"); await WriteTableHeader(res, new string[] { "Claim Type", "Value" }, context.User.Claims.Select(c => new string[] { c.Type, c.Value })); await res.WriteAsync("

Tokens:

"); await WriteTableHeader(res, new string[] { "Token Type", "Value" }, props.GetTokens().Select(token => new string[] { token.Name, token.Value })); }); }); } private static async Task WriteHtmlAsync(HttpResponse response, Func writeContent) { var bootstrap = ""; response.ContentType = "text/html"; await response.WriteAsync($"{bootstrap}
"); await writeContent(response); await response.WriteAsync("
"); } private static async Task WriteTableHeader(HttpResponse response, IEnumerable columns, IEnumerable> data) { await response.WriteAsync(""); await response.WriteAsync(""); foreach (var column in columns) { await response.WriteAsync($""); } await response.WriteAsync(""); foreach (var row in data) { await response.WriteAsync(""); foreach (var column in row) { await response.WriteAsync($""); } await response.WriteAsync(""); } await response.WriteAsync("
{HtmlEncode(column)}
{HtmlEncode(column)}
"); } private static string HtmlEncode(string content) => string.IsNullOrEmpty(content) ? string.Empty : HtmlEncoder.Default.Encode(content); } }