diff --git a/OdeToFoodRider/OdeToFoodRider/Controllers/HomeController.cs b/OdeToFoodRider/OdeToFoodRider/Controllers/HomeController.cs index f079fa8..7263e88 100644 --- a/OdeToFoodRider/OdeToFoodRider/Controllers/HomeController.cs +++ b/OdeToFoodRider/OdeToFoodRider/Controllers/HomeController.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using OdeToFoodRider.Models; using OdeToFoodRider.Services; @@ -6,6 +7,7 @@ namespace OdeToFoodRider.Controllers { + [Authorize] public class HomeController : Controller { // VSRD: This time, "Initialize field from constructor" does exactly what we want. @@ -19,6 +21,7 @@ public HomeController(IRestaurantData restaurantData, IGreeter greeter) } + [AllowAnonymous] public IActionResult Index() { // VSRD: "Use object initialization" available in both IDEs but in Visual Studio, it's only available on the constructor call, not on further variable usages. diff --git a/OdeToFoodRider/OdeToFoodRider/Pages/Restaurants/Edit.cshtml.cs b/OdeToFoodRider/OdeToFoodRider/Pages/Restaurants/Edit.cshtml.cs index 363288d..f5d82c9 100644 --- a/OdeToFoodRider/OdeToFoodRider/Pages/Restaurants/Edit.cshtml.cs +++ b/OdeToFoodRider/OdeToFoodRider/Pages/Restaurants/Edit.cshtml.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using OdeToFoodRider.Models; @@ -5,6 +6,7 @@ namespace OdeToFoodRider.Pages.Restaurants { + [Authorize] public class EditModel : PageModel { private readonly IRestaurantData _restaurantData; diff --git a/OdeToFoodRider/OdeToFoodRider/Program.cs b/OdeToFoodRider/OdeToFoodRider/Program.cs index 6536e3d..aadd761 100644 --- a/OdeToFoodRider/OdeToFoodRider/Program.cs +++ b/OdeToFoodRider/OdeToFoodRider/Program.cs @@ -21,6 +21,7 @@ public static void Main(string[] args) .AddEnvironmentVariables() .AddJsonFile("certificate.json", optional: true, reloadOnChange: true) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile("github.json", optional: false, reloadOnChange: true) .Build(); var certificateSettings = config.GetSection("certificateSettings"); diff --git a/OdeToFoodRider/OdeToFoodRider/Startup.cs b/OdeToFoodRider/OdeToFoodRider/Startup.cs index 99882d0..345e0e1 100644 --- a/OdeToFoodRider/OdeToFoodRider/Startup.cs +++ b/OdeToFoodRider/OdeToFoodRider/Startup.cs @@ -1,7 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Claims; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.OAuth; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -12,6 +18,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; using OdeToFoodRider.Data; using OdeToFoodRider.Services; @@ -30,6 +37,49 @@ public Startup(IConfiguration configuration) // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = "GitHub"; + }) + .AddCookie() + .AddOAuth("GitHub", options => + { + + options.ClientId = _configuration.GetSection("GitHub").GetValue("ClientId"); + options.ClientSecret = _configuration.GetSection("GitHub").GetValue("ClientSecret"); + options.CallbackPath = new PathString("/signin-oidc"); + + options.AuthorizationEndpoint = "https://github.com/login/oauth/authorize"; + options.TokenEndpoint = "https://github.com/login/oauth/access_token"; + options.UserInformationEndpoint = "https://api.github.com/user"; + + options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id"); + options.ClaimActions.MapJsonKey(ClaimTypes.Name, "name"); + options.ClaimActions.MapJsonKey("urn:github:login", "login"); + options.ClaimActions.MapJsonKey("urn:github:url", "html_url"); + options.ClaimActions.MapJsonKey("urn:github:avatar", "avatar_url"); + + options.Events = new OAuthEvents + { + OnCreatingTicket = async context => + { + var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken); + + var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted); + response.EnsureSuccessStatusCode(); + + var user = JObject.Parse(await response.Content.ReadAsStringAsync()); + + context.RunClaimActions(user); + } + }; + + }) + ; services.AddSingleton(); services.AddDbContext(options => options.UseSqlServer(_configuration.GetConnectionString("OdeToFood"))); @@ -58,6 +108,7 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, IGreeter app.UseRewriter(new RewriteOptions().AddRedirectToHttpsPermanent()); app.UseStaticFiles(); + app.UseAuthentication(); app.UseMvc(ConfigureRoutes); app.Run(async (context) => diff --git a/OdeToFoodVisualStudio/OdeToFoodVisualStudio/Controllers/HomeController.cs b/OdeToFoodVisualStudio/OdeToFoodVisualStudio/Controllers/HomeController.cs index b8b3e61..cc543b0 100644 --- a/OdeToFoodVisualStudio/OdeToFoodVisualStudio/Controllers/HomeController.cs +++ b/OdeToFoodVisualStudio/OdeToFoodVisualStudio/Controllers/HomeController.cs @@ -1,10 +1,12 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; using OdeToFoodVisualStudio.Models; using OdeToFoodVisualStudio.Services; using OdeToFoodVisualStudio.ViewModels; namespace OdeToFoodVisualStudio.Controllers { + [Authorize] public class HomeController : Controller { private IRestaurantData _restaurantData; @@ -21,6 +23,7 @@ public HomeController(IRestaurantData restaurantData, IGreeter greeter) _greeter = greeter; } + [AllowAnonymous] public IActionResult Index() { var model = new HomeIndexViewModel(); diff --git a/OdeToFoodVisualStudio/OdeToFoodVisualStudio/Pages/Restaurants/Edit.cshtml.cs b/OdeToFoodVisualStudio/OdeToFoodVisualStudio/Pages/Restaurants/Edit.cshtml.cs index 68b934f..8a7d023 100644 --- a/OdeToFoodVisualStudio/OdeToFoodVisualStudio/Pages/Restaurants/Edit.cshtml.cs +++ b/OdeToFoodVisualStudio/OdeToFoodVisualStudio/Pages/Restaurants/Edit.cshtml.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using OdeToFoodVisualStudio.Models; @@ -9,6 +10,7 @@ namespace OdeToFoodVisualStudio.Pages.Restaurants { + [Authorize] public class EditModel : PageModel { private IRestaurantData _restaurantData; diff --git a/OdeToFoodVisualStudio/OdeToFoodVisualStudio/Program.cs b/OdeToFoodVisualStudio/OdeToFoodVisualStudio/Program.cs index e4df3bc..5dcc437 100644 --- a/OdeToFoodVisualStudio/OdeToFoodVisualStudio/Program.cs +++ b/OdeToFoodVisualStudio/OdeToFoodVisualStudio/Program.cs @@ -12,14 +12,23 @@ namespace OdeToFoodVisualStudio { public class Program { + public static void Main(string[] args) { BuildWebHost(args).Run(); } - public static IWebHost BuildWebHost(string[] args) => - WebHost.CreateDefaultBuilder(args) + public static IWebHost BuildWebHost(string[] args) + { + IConfigurationBuilder config = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("github.json", optional: false, reloadOnChange: true) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); + + return WebHost.CreateDefaultBuilder(args) + .UseConfiguration(config.Build()) .UseStartup() .Build(); + } } } diff --git a/OdeToFoodVisualStudio/OdeToFoodVisualStudio/Startup.cs b/OdeToFoodVisualStudio/OdeToFoodVisualStudio/Startup.cs index 4526be7..ea8865b 100644 --- a/OdeToFoodVisualStudio/OdeToFoodVisualStudio/Startup.cs +++ b/OdeToFoodVisualStudio/OdeToFoodVisualStudio/Startup.cs @@ -1,4 +1,8 @@ -using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.OAuth; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite; @@ -7,9 +11,13 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; using OdeToFoodVisualStudio.Data; using OdeToFoodVisualStudio.Services; using System; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Claims; namespace OdeToFoodVisualStudio { @@ -26,6 +34,48 @@ public Startup(IConfiguration configuration) // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = "GitHub"; + }) + .AddCookie() + .AddOAuth("GitHub", options => + { + + options.ClientId = _configuration.GetSection("GitHub").GetValue("ClientId"); + options.ClientSecret = _configuration.GetSection("GitHub").GetValue("ClientSecret"); + options.CallbackPath = new PathString("/signin-oidc"); + + options.AuthorizationEndpoint = "https://github.com/login/oauth/authorize"; + options.TokenEndpoint = "https://github.com/login/oauth/access_token"; + options.UserInformationEndpoint = "https://api.github.com/user"; + + options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id"); + options.ClaimActions.MapJsonKey(ClaimTypes.Name, "name"); + options.ClaimActions.MapJsonKey("urn:github:login", "login"); + options.ClaimActions.MapJsonKey("urn:github:url", "html_url"); + options.ClaimActions.MapJsonKey("urn:github:avatar", "avatar_url"); + + options.Events = new OAuthEvents + { + OnCreatingTicket = async context => + { + var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken); + + var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted); + response.EnsureSuccessStatusCode(); + + var user = JObject.Parse(await response.Content.ReadAsStringAsync()); + + context.RunClaimActions(user); + } + }; + }); + // RDVS: Rider's completion works better with this generic method as it additionally adds the parentheses; additionally, VS overlaps the completion list inside the generic brackets // with with parameter info services.AddSingleton(); // singleton scope @@ -44,7 +94,11 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, IGreeter // RDVS: RewriteOptions suggested as FQN in Visual Studio's completion: Scott had to erase the FQN part and import namespace with a quick action app.UseRewriter(new RewriteOptions().AddRedirectToHttpsPermanent()); + app.UseStaticFiles(); + + app.UseAuthentication(); + app.UseMvc(ConfigureRoutes); app.Run(async (context) =>