/// <summary> /// Console Loop /// </summary> static void ConsoleLoop() { const int MOD_PAYLOAD_OPTION = 99; // load the list of available users var users = UserController.LoadUsers(); // Build a menu of Scenarions that can used to test StringBuilder sb = new StringBuilder(); int userIdx = 1; sb.AppendLine(); sb.AppendLine("DataCap POC for Auth via JWT Headers"); sb.AppendLine(); sb.AppendLine(" List of Test Users"); sb.AppendLine("===================="); sb.AppendLine($" [0] Anonymous (Do not send an Auth Header)"); foreach (var user in users) { sb.AppendLine($" [{userIdx}] {user.User} " + (user.IsEnabled ? string.Empty : "(Disabled)")); userIdx++; } // pick a user to use for the Modified Payload var goodTestUser = users.Where(u => u.IsEnabled == true).FirstOrDefault(); sb.AppendLine($" [{MOD_PAYLOAD_OPTION}] {goodTestUser.User} (Modified Body)"); sb.AppendLine(" <enter> to exit"); sb.AppendLine(); // define variables outside the while loop to reduce GC pressure string choiceText = string.Empty; int choiceIndex = 0; UserIdentityBE selectedUser; bool shouldContinue = true; AuthCRequestBE authcRequest = null; string httpBody = string.Empty; Stopwatch stw = null; string jwtToken = string.Empty; long create1stJWTElapsed = 0; // ========================= // start the message loop // ========================= while (shouldContinue) { // reset variables choiceIndex = -1; selectedUser = null; // clear the screen Console.Clear(); // display the menu Console.WriteLine(sb.ToString()); Console.Write("Choice: > "); choiceText = Console.ReadLine(); if (string.IsNullOrEmpty(choiceText)) { // blank == exit shouldContinue = false; } else { // if the entry is a number and within the list, find the associated user Int32.TryParse(choiceText, out choiceIndex); if (choiceIndex == 0) { selectedUser = new UserIdentityBE() { User = @"Anonymous" }; Console.WriteLine($" ==> Ready to send anonymous request"); } else if (choiceIndex == MOD_PAYLOAD_OPTION) { selectedUser = goodTestUser; Console.WriteLine($" ==> Ready to send request with tampered body"); } else if (choiceIndex > 0 && choiceIndex <= users.Count) { selectedUser = users[choiceIndex - 1]; Console.WriteLine($" ==> Ready to send request for: {selectedUser.User} [{selectedUser.Company}]."); } if (selectedUser == null) { Console.WriteLine($" [{choiceText}] is not at valid selection."); Console.WriteLine($" ==> Press <enter> to continue"); } else { // ================================================== // this should be as short as possible to limit ability to fradualently reuse, // but long enough to tolerate clock skew between client and service // ================================================== int ttlMinutes = 5; authcRequest = IsoMsgBuilder.GetAuthMsg(); httpBody = authcRequest.ToString(); stw = new Stopwatch(); stw.Start(); jwtToken = (choiceIndex > 0) ? JwtController.CreateJWTToken(selectedUser.User, selectedUser.Company, JwtController.AUDIENCE, ttlMinutes, httpBody) : string.Empty; stw.Stop(); create1stJWTElapsed = stw.ElapsedMilliseconds; // test ben token //string benToken = JwtController.CreateBENJWTToken(selectedUser.User, selectedUser.Company, JwtController.AUDIENCE, ttlMinutes); //stw.Reset(); //stw.Start(); //jwtToken = (choiceIndex > 0) ? JwtController.CreateJWTToken(selectedUser.User, selectedUser.Company, JwtController.AUDIENCE, ttlMinutes, httpBody) : string.Empty; //stw.Stop(); //var create2ndJWTElapsed = stw.ElapsedMilliseconds; //string downstreamJwtToken = (choiceIndex > 0) ? JwtController.CreateDownstreamJWTToken(selectedUser.User, selectedUser.Company, JwtController.AUDIENCE, ttlMinutes, jwtToken) : string.Empty; //stw.Reset(); //stw.Start(); //var dsT = JwtController.ValidateJWTToken(downstreamJwtToken, JwtController.AUDIENCE); //stw.Stop(); //var validate1stJWTElapsed = stw.ElapsedMilliseconds; //stw.Reset(); //stw.Start(); //dsT = JwtController.ValidateJWTToken(downstreamJwtToken, JwtController.AUDIENCE); //stw.Stop(); //var validate2ndJWTElapsed = stw.ElapsedMilliseconds; if (choiceIndex == MOD_PAYLOAD_OPTION) { // simulate man-in-the middle tampering with the payload httpBody = httpBody + "123"; } // make the call (HttpStatusCode responseCode, IList <Parameter> responseHeaders, string responseContent) = CallWebApiPost(jwtToken, httpBody); // evaluate the response if (responseCode == HttpStatusCode.OK) { Console.ForegroundColor = ConsoleColor.Green; } else if (responseCode == HttpStatusCode.Unauthorized) { Console.ForegroundColor = ConsoleColor.Red; } else { Console.ForegroundColor = ConsoleColor.Cyan; } Console.WriteLine(); Console.WriteLine($"responseCode: [{responseCode.ToString()}]"); Console.WriteLine(); Console.WriteLine($"headers"); foreach (var responseHeader in responseHeaders) { Console.WriteLine($" {responseHeader.Name} : {responseHeader.Value}"); } Console.WriteLine(); Console.WriteLine($"content: [{responseContent}]"); Console.ResetColor(); Console.WriteLine($"Create 1st JWT: [{create1stJWTElapsed} mSec]"); //Console.WriteLine($"Create 2nd JWT: [{create2ndJWTElapsed} mSec]"); //Console.WriteLine($"Valdiate 1st JWT: [{validate1stJWTElapsed} mSec]"); //Console.WriteLine($"Valdiate 2nd JWT: [{validate2ndJWTElapsed} mSec]"); Console.WriteLine(); Console.WriteLine($" ==> Press <enter> to continue"); } Console.ReadLine(); } } }
//public ActionResult<string> Post([FromBody] string rawRequest) public async Task <ActionResult <string> > Post() { // note: I specficially choose NOT to use the [FromBody] approach to make debugging easier // if we use the [FromBody] approach this method never get control if the body cannot be correctly deserialized // this approach lets us capture the raw body content passed string httpBody = Request.GetRawBodyString(); AuthCRequestBE authRequest = null; try { authRequest = JsonConvert.DeserializeObject <AuthCRequestBE>(httpBody); } catch (Exception ex) { // you could do addl logging here to support debugging return(BadRequest()); } // look for an Auth Header // we expect it to look like: // Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFERTJERjQ2NkQyMTg4RDMyRjc0ODdCMjlCQzc2OTExNURDNTM0NzIiLCJ0eXAiOiJKV1QifQ.eyJzdWIiOiJodHRwczovL3d3dy5kYXRhY2Fwc3lzdGVtcy5jb20vIiwiYXVkIjoiaHR0cHM6Ly93d3cud29ybGRwYXkuY29tLyIsImV4cCI6MTU2NDk0MjEwN30.mwwaFczSPP1_EMDcgAdXIbf3hwHw26nTv-kG4b1_EH9q8TFrNMmPMjayyWzHDizbwF-As-6AppaNlMbEQFp-ilXLCx_MAgvff1vNA_qA_wh_t0rcsUO_Evbn5lapoDOCom97cddSIywUnb4zA14TRlrttfuOnpkj08WaR2WM38unpKjBpIHYZJYrrG5Gzyyjs2uzPfCydOCcXVuv3xcVTbmgDGVraDswDMF0xVKHwrFNG9HLfCsJhgA14_puVELPRceuXa_o-u9o05U8-BRrzvyEOxobpXc_z6c0FlnA5OcTGbVDChCASal-8kXjaZYzk1dF-FBQxK3Sj75wCi3IYg string authHeader = Request.Headers["Authorization"]; if (string.IsNullOrEmpty(authHeader)) { return(Unauthorized()); } else { // parse the header var parts = authHeader.Split(" "); string jwtTokenString = parts[1]; (bool isJwtTokenValid, string username, JwtSecurityToken jwtToken) = JwtController.ValidateJWTToken(jwtTokenString, JwtController.AUDIENCE); if (!isJwtTokenValid) { return(Unauthorized()); } else { // at this point this controller would add the identity of the authenticated caller as a new HTTP header and call a downstream webapi // this part would typically be in the downstream client and called using await // validate that the body has not body modified string hash = httpBody.SHA256Hash(); (string hashType, string bodyHash) = JwtController.GetBodyHashInfo(jwtToken.Payload); if (!string.IsNullOrEmpty(hashType)) { if (hashType.ToLower() != @"sha256") { return(BadRequest($"hash type: [{hashType}] is NOT supported, use sha256 instead!")); } if (httpBody.SHA256Hash() != bodyHash) { return(BadRequest($"Post Body has been modified")); } } // in this POC just return the identity that we derived from the JWT var authCResult = new AuthCResultBE() { IsValid = isJwtTokenValid, User = username, JwtToken = jwtTokenString }; //return Ok(JsonConvert.SerializeObject(authCResult, Formatting.Indented)); return(Ok(authCResult)); } } }