From f53f5bd63ef5ae42548dd09576604d70add0d0df Mon Sep 17 00:00:00 2001 From: Felisia Date: Mon, 23 Oct 2017 21:42:51 +0100 Subject: [PATCH] Added OpenIdConnect authentication and JWT token parsing to retrieve the user data. --- BMI/BMI/Authorisation/ITokenHandler.cs | 13 ++++++ BMI/BMI/Authorisation/TokenHandler.cs | 43 +++++++++++++++++ BMI/BMI/Controllers/HomeController.cs | 64 +++++++++++++++++++------- BMI/BMI/Models/ErrorViewModel.cs | 11 ----- BMI/BMI/Program.cs | 11 +---- BMI/BMI/{ => Reporting}/BMIReport.cs | 2 +- BMI/BMI/{ => Reporting}/BmiCategory.cs | 2 +- BMI/BMI/{ => Reporting}/CsvReader.cs | 2 +- BMI/BMI/{ => Reporting}/IBmiReport.cs | 2 +- BMI/BMI/{ => Reporting}/ICsvReader.cs | 2 +- BMI/BMI/Startup.cs | 50 +++++++++++++++----- BMI/BMI/Views/Home/BmiReport.cshtml | 31 +++++++++++++ BMI/BMI/Views/Home/GuestUser.cshtml | 32 +------------ BMI/BMI/Views/Home/Index.cshtml | 10 ++-- BMI/BMI/Views/Home/UserInput.cshtml | 23 +++++---- BMI/BMI_Tests/BmiReportTests.cs | 2 +- 16 files changed, 196 insertions(+), 104 deletions(-) create mode 100644 BMI/BMI/Authorisation/ITokenHandler.cs create mode 100644 BMI/BMI/Authorisation/TokenHandler.cs delete mode 100644 BMI/BMI/Models/ErrorViewModel.cs rename BMI/BMI/{ => Reporting}/BMIReport.cs (98%) rename BMI/BMI/{ => Reporting}/BmiCategory.cs (92%) rename BMI/BMI/{ => Reporting}/CsvReader.cs (97%) rename BMI/BMI/{ => Reporting}/IBmiReport.cs (94%) rename BMI/BMI/{ => Reporting}/ICsvReader.cs (86%) create mode 100644 BMI/BMI/Views/Home/BmiReport.cshtml diff --git a/BMI/BMI/Authorisation/ITokenHandler.cs b/BMI/BMI/Authorisation/ITokenHandler.cs new file mode 100644 index 0000000..e295a34 --- /dev/null +++ b/BMI/BMI/Authorisation/ITokenHandler.cs @@ -0,0 +1,13 @@ +using System.IdentityModel.Tokens.Jwt; +using BMI.Models; + +namespace BMI.Authorisation +{ + public interface ITokenHandler + { + JwtSecurityToken GetJwtSecurityToken(string tokenId); + bool IsAuthorised(JwtSecurityToken jwtToken); + UserDetails GetUserDetailsFromClaims(JwtSecurityToken jwtToken); + string GetUserName(JwtSecurityToken jwtToken); + } +} \ No newline at end of file diff --git a/BMI/BMI/Authorisation/TokenHandler.cs b/BMI/BMI/Authorisation/TokenHandler.cs new file mode 100644 index 0000000..55b5c19 --- /dev/null +++ b/BMI/BMI/Authorisation/TokenHandler.cs @@ -0,0 +1,43 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using BMI.Models; + +namespace BMI.Authorisation +{ + public class TokenHandler : ITokenHandler + { + public JwtSecurityToken GetJwtSecurityToken(string tokenId) + { + var handler = new JwtSecurityTokenHandler(); + return handler.ReadToken(tokenId) as JwtSecurityToken; + } + + public bool IsAuthorised(JwtSecurityToken jwtToken) + { + var access = GetClaim(jwtToken, "extension_Access"); + + return access.Equals("True"); + } + + public UserDetails GetUserDetailsFromClaims(JwtSecurityToken jwtToken) + { + return new UserDetails + { + Height = double.Parse(GetClaim(jwtToken, "extension_Height")), + Weight = double.Parse(GetClaim(jwtToken, "extension_Weight")) + }; + } + + public string GetUserName(JwtSecurityToken jwtToken) + { + return GetClaim(jwtToken, "family_name"); + } + + private static string GetClaim( + JwtSecurityToken jwtToken, + string claimType) + { + return jwtToken.Claims.First(claim => claim.Type == claimType).Value; + } + } +} diff --git a/BMI/BMI/Controllers/HomeController.cs b/BMI/BMI/Controllers/HomeController.cs index dc4195f..b21c648 100644 --- a/BMI/BMI/Controllers/HomeController.cs +++ b/BMI/BMI/Controllers/HomeController.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; +using System.Threading; +using BMI.Authorisation; using Microsoft.AspNetCore.Mvc; using BMI.Models; -using Microsoft.Extensions.Logging; +using BMI.Reporting; +using Microsoft.AspNetCore.Authorization; namespace BMI.Controllers { @@ -12,20 +14,50 @@ public class HomeController : Controller { private readonly IBmiReport _bmiReport; private readonly ICsvReader _csvReader; + private readonly ITokenHandler _tokenHandler; public HomeController( IBmiReport bmiReport, - ICsvReader csvReader) + ICsvReader csvReader, + ITokenHandler tokenHandler) { _bmiReport = bmiReport; _csvReader = csvReader; + _tokenHandler = tokenHandler; } - + + [Authorize] public IActionResult Index() { - return View(); } + + [HttpPost] + public IActionResult Index(CancellationToken token) + { + try + { + Request.Form.TryGetValue("id_token", out var idToken); + + var jwtToken = _tokenHandler.GetJwtSecurityToken(idToken); + if (!_tokenHandler.IsAuthorised(jwtToken)) + { + return View("UserInput"); + } + + ViewData["UserName"] = _tokenHandler.GetUserName(jwtToken); + BuildBMIReport( + _tokenHandler.GetUserDetailsFromClaims(jwtToken)); + + return View(); + } + catch (Exception ex) + { + Console.WriteLine("Failure happen during validating id_token."); + } + + return View("UserInput"); + } public IActionResult GuestUser() { @@ -41,6 +73,13 @@ public IActionResult GuestUser(UserDetails details) return View(); } + BuildBMIReport(details); + + return View(); + } + + private void BuildBMIReport(UserDetails details) + { var bmiIndex = _bmiReport.GetBmiIndex(details.Height, details.Weight); var bmiCategory = _bmiReport.GetBmiCategory(bmiIndex); @@ -57,18 +96,16 @@ public IActionResult GuestUser(UserDetails details) ViewData["PopulationReport"] = report .Select(o => new ReportModel - { - Category = o.Key, - Count = o.Value - }) + { + Category = o.Key, + Count = o.Value + }) .ToList(); var usersRanking = _bmiReport.GetUsersPercentile(population, bmiIndex); ViewData["UsersRanking"] = usersRanking; } - - return View(); } private List TryParseCsv() @@ -84,10 +121,5 @@ private List TryParseCsv() } return population; } - - public IActionResult Error() - { - return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); - } } } diff --git a/BMI/BMI/Models/ErrorViewModel.cs b/BMI/BMI/Models/ErrorViewModel.cs deleted file mode 100644 index b063fec..0000000 --- a/BMI/BMI/Models/ErrorViewModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace BMI.Models -{ - public class ErrorViewModel - { - public string RequestId { get; set; } - - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - } -} \ No newline at end of file diff --git a/BMI/BMI/Program.cs b/BMI/BMI/Program.cs index 14628b8..f2e1116 100644 --- a/BMI/BMI/Program.cs +++ b/BMI/BMI/Program.cs @@ -1,12 +1,5 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore; +using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; namespace BMI { @@ -20,7 +13,7 @@ public static void Main(string[] args) public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) - .UseUrls("https://localhost:44316") + .UseUrls("https://localhost:44316") .UseStartup() .Build(); } diff --git a/BMI/BMI/BMIReport.cs b/BMI/BMI/Reporting/BMIReport.cs similarity index 98% rename from BMI/BMI/BMIReport.cs rename to BMI/BMI/Reporting/BMIReport.cs index 3adff6a..98216b6 100644 --- a/BMI/BMI/BMIReport.cs +++ b/BMI/BMI/Reporting/BMIReport.cs @@ -3,7 +3,7 @@ using System.Linq; using BMI.Models; -namespace BMI +namespace BMI.Reporting { public class BmiReport : IBmiReport { diff --git a/BMI/BMI/BmiCategory.cs b/BMI/BMI/Reporting/BmiCategory.cs similarity index 92% rename from BMI/BMI/BmiCategory.cs rename to BMI/BMI/Reporting/BmiCategory.cs index e474ac1..992a3aa 100644 --- a/BMI/BMI/BmiCategory.cs +++ b/BMI/BMI/Reporting/BmiCategory.cs @@ -1,4 +1,4 @@ -namespace BMI +namespace BMI.Reporting { public class BmiCategory { diff --git a/BMI/BMI/CsvReader.cs b/BMI/BMI/Reporting/CsvReader.cs similarity index 97% rename from BMI/BMI/CsvReader.cs rename to BMI/BMI/Reporting/CsvReader.cs index 9458a72..e15bad5 100644 --- a/BMI/BMI/CsvReader.cs +++ b/BMI/BMI/Reporting/CsvReader.cs @@ -2,7 +2,7 @@ using System.IO; using BMI.Models; -namespace BMI +namespace BMI.Reporting { public class CsvReader : ICsvReader { diff --git a/BMI/BMI/IBmiReport.cs b/BMI/BMI/Reporting/IBmiReport.cs similarity index 94% rename from BMI/BMI/IBmiReport.cs rename to BMI/BMI/Reporting/IBmiReport.cs index 052ea83..589ff26 100644 --- a/BMI/BMI/IBmiReport.cs +++ b/BMI/BMI/Reporting/IBmiReport.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using BMI.Models; -namespace BMI +namespace BMI.Reporting { public interface IBmiReport { diff --git a/BMI/BMI/ICsvReader.cs b/BMI/BMI/Reporting/ICsvReader.cs similarity index 86% rename from BMI/BMI/ICsvReader.cs rename to BMI/BMI/Reporting/ICsvReader.cs index a28fccc..6d10868 100644 --- a/BMI/BMI/ICsvReader.cs +++ b/BMI/BMI/Reporting/ICsvReader.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using BMI.Models; -namespace BMI +namespace BMI.Reporting { public interface ICsvReader { diff --git a/BMI/BMI/Startup.cs b/BMI/BMI/Startup.cs index 3c25d52..681cfae 100644 --- a/BMI/BMI/Startup.cs +++ b/BMI/BMI/Startup.cs @@ -8,8 +8,13 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Rewrite; using System.IdentityModel.Tokens.Jwt; +using System.Threading.Tasks; +using BMI.Authorisation; +using BMI.Reporting; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Protocols.OpenIdConnect; -using Microsoft.IdentityModel.Tokens; namespace BMI @@ -26,10 +31,10 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddMvc(); - + // Add application services services.AddTransient(); services.AddTransient(); + services.AddTransient(); // enforce all requests to use ssl - global config services.Configure(options => @@ -37,20 +42,39 @@ public void ConfigureServices(IServiceCollection services) options.Filters.Add(new RequireHttpsAttribute()); }); - + // Add framework services + services.AddMvc(); + + services.AddAuthentication(options => + { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; + }) + .AddCookie() + .AddOpenIdConnect(options => + { + options.Authority = Configuration["Authority"]; + options.ClientId = Configuration["Client"]; + options.CallbackPath = Configuration["CallbackPath"]; + + options.ResponseType = OpenIdConnectResponseType.IdToken; + options.AuthenticationMethod = OpenIdConnectRedirectBehavior.FormPost; + + options.GetClaimsFromUserInfoEndpoint = true; + options.RequireHttpsMetadata = false; + options.MetadataAddress = Configuration["MetadataAddress"]; + + options.SaveTokens = true; + }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IHostingEnvironment env) + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } - else - { - app.UseExceptionHandler("/Home/Error"); - } app.UseStaticFiles(); @@ -62,9 +86,11 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) }); // Redirects all HTTP requests to HTTPS: - var options = new RewriteOptions() - .AddRedirectToHttps(); - app.UseRewriter(options); + app.UseRewriter( + new RewriteOptions() + .AddRedirectToHttps()); + + app.UseAuthentication(); } } } diff --git a/BMI/BMI/Views/Home/BmiReport.cshtml b/BMI/BMI/Views/Home/BmiReport.cshtml new file mode 100644 index 0000000..248ebad --- /dev/null +++ b/BMI/BMI/Views/Home/BmiReport.cshtml @@ -0,0 +1,31 @@ + +

@ViewData["Result"]

+ +

Population BMI Report

+
+ + + + + + + + + @foreach (var item in (List)ViewData["PopulationReport"]) + { + + + + + } + +
+ Category + + Count +
+ @Html.DisplayFor(modelItem => item.Category) + + @Html.DisplayFor(modelItem => item.Count) +
+

You rank in @ViewData["UsersRanking"]% percentile of the population.

\ No newline at end of file diff --git a/BMI/BMI/Views/Home/GuestUser.cshtml b/BMI/BMI/Views/Home/GuestUser.cshtml index 281076c..40390e5 100644 --- a/BMI/BMI/Views/Home/GuestUser.cshtml +++ b/BMI/BMI/Views/Home/GuestUser.cshtml @@ -2,36 +2,6 @@ ViewData["Title"] = "Guest User"; } -@using BMI.Models - @Html.Partial("UserInput") -

@ViewData["Result"]

- -

Population BMI Report

-
- - - - - - - - @foreach (var item in (List) ViewData["PopulationReport"]) { - - - - - } - -
- Category - - Count -
- @Html.DisplayFor(modelItem => item.Category) - - @Html.DisplayFor(modelItem => item.Count) -
- -

You rank in @ViewData["UsersRanking"]% percentile of the population.

\ No newline at end of file +@Html.Partial("BmiReport") \ No newline at end of file diff --git a/BMI/BMI/Views/Home/Index.cshtml b/BMI/BMI/Views/Home/Index.cshtml index 5cca6fa..356a45a 100644 --- a/BMI/BMI/Views/Home/Index.cshtml +++ b/BMI/BMI/Views/Home/Index.cshtml @@ -2,10 +2,6 @@ ViewData["Title"] = "Home Page"; } -
- @foreach (var claim in User.Claims) - { -
@claim.Type
-
@claim.Value
- } -
\ No newline at end of file +

Hi, @ViewData["UserName"]

+ +@Html.Partial("BmiReport") \ No newline at end of file diff --git a/BMI/BMI/Views/Home/UserInput.cshtml b/BMI/BMI/Views/Home/UserInput.cshtml index 1bf67b7..04228c2 100644 --- a/BMI/BMI/Views/Home/UserInput.cshtml +++ b/BMI/BMI/Views/Home/UserInput.cshtml @@ -1,33 +1,32 @@  @model UserDetails -@using (Html.BeginForm("GuestUser", "Home", FormMethod.Post)) +@using (@Html.BeginForm("GuestUser", "Home", FormMethod.Post)) {
- @Html.LabelFor(model => model.Height, htmlAttributes: new { @class = "control-label col-md-2" }) + @Html.LabelFor(model => model.Height, htmlAttributes: new {@class = "control-label col-md-2"})

Put your height in metres

-
- @Html.EditorFor(model => model.Height, new { htmlAttributes = new { @class = "form-control" } }) - @Html.ValidationMessageFor(model => model.Height, "", new { @class = "text-danger" }) +
+ @Html.EditorFor(model => model.Height, new {htmlAttributes = new {@class = "form-control"}}) + @Html.ValidationMessageFor(model => model.Height, "", new {@class = "text-danger"})
- @Html.LabelFor(model => model.Weight, htmlAttributes: new { @class = "control-label col-md-2" }) + @Html.LabelFor(model => model.Weight, htmlAttributes: new {@class = "control-label col-md-2"})

Put your weight in kilograms

-
- @Html.EditorFor(model => model.Weight, new { htmlAttributes = new { @class = "form-control" } }) - @Html.ValidationMessageFor(model => model.Weight, "", new { @class = "text-danger" }) +
+ @Html.EditorFor(model => model.Weight, new {htmlAttributes = new {@class = "form-control"}}) + @Html.ValidationMessageFor(model => model.Weight, "", new {@class = "text-danger"})

@ViewBag.Error

- +
-} - +} \ No newline at end of file diff --git a/BMI/BMI_Tests/BmiReportTests.cs b/BMI/BMI_Tests/BmiReportTests.cs index 538f38c..c2334af 100644 --- a/BMI/BMI_Tests/BmiReportTests.cs +++ b/BMI/BMI_Tests/BmiReportTests.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -using BMI; using BMI.Models; +using BMI.Reporting; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace BMI_Tests