From 704641934219fb5d3a8fdb60c119093c1c53bb29 Mon Sep 17 00:00:00 2001 From: Oyvind Timian Dokk Husveg Date: Wed, 24 Sep 2025 09:30:44 +0200 Subject: [PATCH 1/7] Test --- .../Handlers/UserExistsHandler.cs | 4 ++-- exercise.wwwapi/Endpoints/UserEndpoints.cs | 21 ++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/exercise.wwwapi/Authorization/Handlers/UserExistsHandler.cs b/exercise.wwwapi/Authorization/Handlers/UserExistsHandler.cs index 5610e1d..81140f1 100644 --- a/exercise.wwwapi/Authorization/Handlers/UserExistsHandler.cs +++ b/exercise.wwwapi/Authorization/Handlers/UserExistsHandler.cs @@ -27,8 +27,8 @@ protected override async Task HandleRequirementAsync( //_logger.LogWarning("Available claims in token: {Claims}", string.Join(", ", claims)); // Get user ID from claims - var userIdClaim = context.User.FindFirst(ClaimTypes.NameIdentifier) - ?? context.User.FindFirst(ClaimTypes.Sid); + var userIdClaim = context.User.FindFirst(ClaimTypes.Sid) + ?? context.User.FindFirst(ClaimTypes.NameIdentifier); if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out int userId)) diff --git a/exercise.wwwapi/Endpoints/UserEndpoints.cs b/exercise.wwwapi/Endpoints/UserEndpoints.cs index 01b5a71..42041d7 100644 --- a/exercise.wwwapi/Endpoints/UserEndpoints.cs +++ b/exercise.wwwapi/Endpoints/UserEndpoints.cs @@ -13,6 +13,7 @@ using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; +using System.Threading.Tasks; namespace exercise.wwwapi.EndPoints { @@ -21,14 +22,25 @@ public static class UserEndpoints public static void ConfigureAuthApi(this WebApplication app) { app.MapPost("/login", Login).WithSummary("Localhost Login"); + app.MapGet("/me", Me).WithSummary("Return user associated with token"); var users = app.MapGroup("users"); users.MapPost("/", Register).WithSummary("Create user"); users.MapGet("/", GetUsers).WithSummary("Get all users by first name if provided"); users.MapGet("/{id:int}", GetUserById).WithSummary("Get user by user id"); users.MapPatch("/{id:int}", UpdateUser).WithSummary("Update a user"); + } - + + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + private static IResult Me(IRepository repository, IMapper mapper, ClaimsPrincipal claims) + { + int? id = claims.UserRealId(); + User? user = repository.GetById(id); + return TypedResults.Ok(); + } + /// /// Retrieves users, optionally filtered by a case-insensitive search on first name, last name, or full name. /// @@ -51,11 +63,6 @@ public static void ConfigureAuthApi(this WebApplication app) private static async Task GetUsers(IRepository repository, ClaimsPrincipal claims, string? name) { int? id = claims.UserRealId(); - if (id == null) - { - return TypedResults.Ok(new ResponseDTO() - { Message = "Invalid token" }); - } IEnumerable results = await repository.Get(); string? search = name?.Trim().ToLower(); @@ -161,7 +168,7 @@ private static IResult Login(IRepository repository, IMapper mapper, Login return Results.Ok(response); } - + [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] From e06fece031d075a5dbc11475085f21cb39cecb0b Mon Sep 17 00:00:00 2001 From: Oyvind Timian Dokk Husveg Date: Wed, 24 Sep 2025 09:40:26 +0200 Subject: [PATCH 2/7] test --- exercise.wwwapi/Endpoints/UserEndpoints.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/exercise.wwwapi/Endpoints/UserEndpoints.cs b/exercise.wwwapi/Endpoints/UserEndpoints.cs index 42041d7..0f68a97 100644 --- a/exercise.wwwapi/Endpoints/UserEndpoints.cs +++ b/exercise.wwwapi/Endpoints/UserEndpoints.cs @@ -38,6 +38,10 @@ private static IResult Me(IRepository repository, IMapper mapper, ClaimsPr { int? id = claims.UserRealId(); User? user = repository.GetById(id); + if (user == null) { + return TypedResults.BadRequest(); + } + //UserDTO userDTO = Mapper.Map(user); return TypedResults.Ok(); } From bb4381b6ecc8561961880b9739987f94db20a1e8 Mon Sep 17 00:00:00 2001 From: Oyvind Timian Dokk Husveg Date: Wed, 24 Sep 2025 10:01:44 +0200 Subject: [PATCH 3/7] Working on variable token lifespan --- exercise.wwwapi/DTOs/Login/LoginRequestDTO.cs | 1 + exercise.wwwapi/Endpoints/UserEndpoints.cs | 20 +++++-------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/exercise.wwwapi/DTOs/Login/LoginRequestDTO.cs b/exercise.wwwapi/DTOs/Login/LoginRequestDTO.cs index 3c59203..201eae1 100644 --- a/exercise.wwwapi/DTOs/Login/LoginRequestDTO.cs +++ b/exercise.wwwapi/DTOs/Login/LoginRequestDTO.cs @@ -7,5 +7,6 @@ public class LoginRequestDTO { public string? email { get; set; } public string? password { get; set; } + public bool? longlifetoken { get; set; } } } diff --git a/exercise.wwwapi/Endpoints/UserEndpoints.cs b/exercise.wwwapi/Endpoints/UserEndpoints.cs index 0f68a97..ae0a318 100644 --- a/exercise.wwwapi/Endpoints/UserEndpoints.cs +++ b/exercise.wwwapi/Endpoints/UserEndpoints.cs @@ -22,7 +22,6 @@ public static class UserEndpoints public static void ConfigureAuthApi(this WebApplication app) { app.MapPost("/login", Login).WithSummary("Localhost Login"); - app.MapGet("/me", Me).WithSummary("Return user associated with token"); var users = app.MapGroup("users"); users.MapPost("/", Register).WithSummary("Create user"); @@ -32,19 +31,6 @@ public static void ConfigureAuthApi(this WebApplication app) } - [Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - private static IResult Me(IRepository repository, IMapper mapper, ClaimsPrincipal claims) - { - int? id = claims.UserRealId(); - User? user = repository.GetById(id); - if (user == null) { - return TypedResults.BadRequest(); - } - //UserDTO userDTO = Mapper.Map(user); - return TypedResults.Ok(); - } - /// /// Retrieves users, optionally filtered by a case-insensitive search on first name, last name, or full name. /// @@ -156,7 +142,11 @@ private static IResult Login(IRepository repository, IMapper mapper, Login return Results.BadRequest(new ResponseDTO() { Message = "Invalid email and/or password provided" }); } - string token = CreateToken(user, config); + string token; + token = CreateToken(user, config); + //if (request.longlifetoken != null && request.longlifetoken) token = CreateToken(user, config); + //else token = CreateToken(user, config); + ResponseDTO response = new ResponseDTO { From 1e92f2c8c9129fe978b286854f5957834367ea44 Mon Sep 17 00:00:00 2001 From: Oyvind Timian Dokk Husveg Date: Wed, 24 Sep 2025 10:05:35 +0200 Subject: [PATCH 4/7] test --- exercise.wwwapi/Endpoints/UserEndpoints.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/exercise.wwwapi/Endpoints/UserEndpoints.cs b/exercise.wwwapi/Endpoints/UserEndpoints.cs index ae0a318..f5aa884 100644 --- a/exercise.wwwapi/Endpoints/UserEndpoints.cs +++ b/exercise.wwwapi/Endpoints/UserEndpoints.cs @@ -143,9 +143,8 @@ private static IResult Login(IRepository repository, IMapper mapper, Login } string token; - token = CreateToken(user, config); - //if (request.longlifetoken != null && request.longlifetoken) token = CreateToken(user, config); - //else token = CreateToken(user, config); + //if (request.longlifetoken.GetValueOrDefault()) token = CreateToken(user, config, 7); + //else token = CreateToken(user, config, ); ResponseDTO response = new ResponseDTO From 8bec281ceb948adb5f7173720cf3de8e8ac21af7 Mon Sep 17 00:00:00 2001 From: Oyvind Timian Dokk Husveg Date: Wed, 24 Sep 2025 10:06:47 +0200 Subject: [PATCH 5/7] t --- exercise.wwwapi/Endpoints/UserEndpoints.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/exercise.wwwapi/Endpoints/UserEndpoints.cs b/exercise.wwwapi/Endpoints/UserEndpoints.cs index f5aa884..83bb3ea 100644 --- a/exercise.wwwapi/Endpoints/UserEndpoints.cs +++ b/exercise.wwwapi/Endpoints/UserEndpoints.cs @@ -143,11 +143,13 @@ private static IResult Login(IRepository repository, IMapper mapper, Login } string token; + + token = CreateToken(user, config); //if (request.longlifetoken.GetValueOrDefault()) token = CreateToken(user, config, 7); //else token = CreateToken(user, config, ); - ResponseDTO response = new ResponseDTO + ResponseDTO response = new ResponseDTO { Message = "success", Data = new LoginSuccessDTO() From 14b5fdb98e098d40ae8d58e83e49f3ab6437869a Mon Sep 17 00:00:00 2001 From: Oyvind Timian Dokk Husveg Date: Wed, 24 Sep 2025 10:20:43 +0200 Subject: [PATCH 6/7] Making tests --- .../IntegrationTests/BaseIntegrationTest.cs | 2 +- exercise.tests/IntegrationTests/TokenTests.cs | 35 +++++++++++++++++++ exercise.wwwapi/Endpoints/UserEndpoints.cs | 10 +++--- 3 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 exercise.tests/IntegrationTests/TokenTests.cs diff --git a/exercise.tests/IntegrationTests/BaseIntegrationTest.cs b/exercise.tests/IntegrationTests/BaseIntegrationTest.cs index ed38d6b..29f68ea 100644 --- a/exercise.tests/IntegrationTests/BaseIntegrationTest.cs +++ b/exercise.tests/IntegrationTests/BaseIntegrationTest.cs @@ -72,7 +72,7 @@ FROM users u protected const int StudentCommentID2 = 3; - protected async Task LoginAndGetToken(string email, string password, bool success = true) + protected async Task LoginAndGetToken(string email, string password, bool success = true, bool longlife = false) { var loginBody = new LoginRequestDTO { email = email, password = password }; var loginRequestBody = new StringContent( diff --git a/exercise.tests/IntegrationTests/TokenTests.cs b/exercise.tests/IntegrationTests/TokenTests.cs new file mode 100644 index 0000000..973580b --- /dev/null +++ b/exercise.tests/IntegrationTests/TokenTests.cs @@ -0,0 +1,35 @@ +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace exercise.tests.IntegrationTests +{ + public class TokenTests : BaseIntegrationTest + { + [Test] + public async Task CreateToken_ShouldGenerateValidJwt() + { + string token = await LoginAndGetToken(TeacherEmail, TeacherPassword); + var handler = new JwtSecurityTokenHandler(); + var jwt = handler.ReadJwtToken(token); + + Console.WriteLine(jwt); + } + + [Test] + public async Task CreateToken_LongLife_ShouldExpireLater() { + string token = await LoginAndGetToken(TeacherEmail, TeacherPassword, true, true); + } + + [Test] + public async Task CreateToken_NormalLife_ShouldExpireLater() + { + string token = await LoginAndGetToken(TeacherEmail, TeacherPassword, true, true); + } + + } +} diff --git a/exercise.wwwapi/Endpoints/UserEndpoints.cs b/exercise.wwwapi/Endpoints/UserEndpoints.cs index 83bb3ea..4606c96 100644 --- a/exercise.wwwapi/Endpoints/UserEndpoints.cs +++ b/exercise.wwwapi/Endpoints/UserEndpoints.cs @@ -144,9 +144,9 @@ private static IResult Login(IRepository repository, IMapper mapper, Login string token; - token = CreateToken(user, config); - //if (request.longlifetoken.GetValueOrDefault()) token = CreateToken(user, config, 7); - //else token = CreateToken(user, config, ); + //token = CreateToken(user, config); + if (request.longlifetoken.GetValueOrDefault()) token = CreateToken(user, config, 7); + else token = CreateToken(user, config, 0.0416666666666666666666666666666666666667); ResponseDTO response = new ResponseDTO @@ -271,7 +271,7 @@ private static async Task UpdateUser(IRepository repository, Clai } // Helper, creates jwt tokens - private static string CreateToken(User user, IConfigurationSettings config) + private static string CreateToken(User user, IConfigurationSettings config, double days) { List claims = [ @@ -286,7 +286,7 @@ private static string CreateToken(User user, IConfigurationSettings config) var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature); var token = new JwtSecurityToken( claims: claims, - expires: DateTime.Now.AddDays(1), + expires: DateTime.Now.AddDays(days), signingCredentials: credentials ); var jwt = new JwtSecurityTokenHandler().WriteToken(token); From 0226996bb14e4f8907852ebbbdc83b3bc4521dc2 Mon Sep 17 00:00:00 2001 From: Oyvind Timian Dokk Husveg Date: Wed, 24 Sep 2025 10:59:04 +0200 Subject: [PATCH 7/7] Added longlife token to tests and token generation --- .../IntegrationTests/BaseIntegrationTest.cs | 2 +- exercise.tests/IntegrationTests/TokenTests.cs | 50 ++++++++++++++++++- exercise.wwwapi/Endpoints/UserEndpoints.cs | 6 +-- 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/exercise.tests/IntegrationTests/BaseIntegrationTest.cs b/exercise.tests/IntegrationTests/BaseIntegrationTest.cs index 29f68ea..504f28b 100644 --- a/exercise.tests/IntegrationTests/BaseIntegrationTest.cs +++ b/exercise.tests/IntegrationTests/BaseIntegrationTest.cs @@ -74,7 +74,7 @@ FROM users u protected async Task LoginAndGetToken(string email, string password, bool success = true, bool longlife = false) { - var loginBody = new LoginRequestDTO { email = email, password = password }; + var loginBody = new LoginRequestDTO { email = email, password = password, longlifetoken = longlife }; var loginRequestBody = new StringContent( JsonSerializer.Serialize(loginBody), Encoding.UTF8, diff --git a/exercise.tests/IntegrationTests/TokenTests.cs b/exercise.tests/IntegrationTests/TokenTests.cs index 973580b..1d2b0d8 100644 --- a/exercise.tests/IntegrationTests/TokenTests.cs +++ b/exercise.tests/IntegrationTests/TokenTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; +using System.Security.Claims; using System.Text; using System.Threading.Tasks; @@ -17,18 +18,63 @@ public async Task CreateToken_ShouldGenerateValidJwt() var handler = new JwtSecurityTokenHandler(); var jwt = handler.ReadJwtToken(token); - Console.WriteLine(jwt); + + //var realid = jwt.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Sid)?.Value; + string? email = jwt.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value; + string? role = jwt.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Role)?.Value; + var expClaim = jwt.Claims.FirstOrDefault(c => c.Type == "exp")?.Value; + + + using (Assert.EnterMultipleScope()) + { + Assert.That(email, Is.EqualTo(TeacherEmail)); + Assert.That(role, Is.EqualTo("1")); + Assert.That(jwt.ValidTo, Is.GreaterThan(DateTime.UtcNow)); + } } [Test] public async Task CreateToken_LongLife_ShouldExpireLater() { string token = await LoginAndGetToken(TeacherEmail, TeacherPassword, true, true); + var handler = new JwtSecurityTokenHandler(); + var jwt = handler.ReadJwtToken(token); + + //var realid = jwt.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Sid)?.Value; + string? email = jwt.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value; + string? role = jwt.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Role)?.Value; + var expClaim = jwt.Claims.FirstOrDefault(c => c.Type == "exp")?.Value; + + //Console.WriteLine(expClaim); + Assert.Multiple(() => + { + Assert.That(email, Is.EqualTo(TeacherEmail)); + Assert.That(role, Is.EqualTo("1")); + Assert.That(jwt.ValidTo, Is.GreaterThan(DateTime.UtcNow.AddDays(6.5))); + Assert.That(jwt.ValidTo, Is.LessThan(DateTime.UtcNow.AddDays(7.5))); + }); } [Test] public async Task CreateToken_NormalLife_ShouldExpireLater() { - string token = await LoginAndGetToken(TeacherEmail, TeacherPassword, true, true); + string token = await LoginAndGetToken(TeacherEmail, TeacherPassword); + var handler = new JwtSecurityTokenHandler(); + var jwt = handler.ReadJwtToken(token); + + //var realid = jwt.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Sid)?.Value; + string? email = jwt.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value; + string? role = jwt.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Role)?.Value; + var expClaim = jwt.Claims.FirstOrDefault(c => c.Type == "exp")?.Value; + + //Console.WriteLine(expClaim); + + using (Assert.EnterMultipleScope()) + { + Assert.That(email, Is.EqualTo(TeacherEmail)); + Assert.That(role, Is.EqualTo("1")); + Assert.That(jwt.ValidTo, Is.GreaterThan(DateTime.UtcNow.AddMinutes(50))); + Assert.That(jwt.ValidTo, Is.LessThan(DateTime.UtcNow.AddHours(2))); + } } } diff --git a/exercise.wwwapi/Endpoints/UserEndpoints.cs b/exercise.wwwapi/Endpoints/UserEndpoints.cs index 4606c96..3341469 100644 --- a/exercise.wwwapi/Endpoints/UserEndpoints.cs +++ b/exercise.wwwapi/Endpoints/UserEndpoints.cs @@ -143,10 +143,8 @@ private static IResult Login(IRepository repository, IMapper mapper, Login } string token; - - //token = CreateToken(user, config); if (request.longlifetoken.GetValueOrDefault()) token = CreateToken(user, config, 7); - else token = CreateToken(user, config, 0.0416666666666666666666666666666666666667); + else token = CreateToken(user, config, 1.0 / 24); ResponseDTO response = new ResponseDTO @@ -286,7 +284,7 @@ private static string CreateToken(User user, IConfigurationSettings config, doub var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature); var token = new JwtSecurityToken( claims: claims, - expires: DateTime.Now.AddDays(days), + expires: DateTime.UtcNow.AddDays(days), signingCredentials: credentials ); var jwt = new JwtSecurityTokenHandler().WriteToken(token);