// Assert that returned token pair is valid private void checkActionResult(IActionResult actionResult, JwtIssuerOptions jwtIssuerOptions, SymmetricSecurityKey signingKey) { var actionResultType = actionResult.GetType().ToString(); Assert.Equal("Microsoft.AspNetCore.Mvc.OkObjectResult", actionResultType); // I should get back two tokens as json objects var typedActionResult = actionResult as Microsoft.AspNetCore.Mvc.OkObjectResult; Assert.NotNull(typedActionResult); if (typedActionResult == null) { return; } string body = typedActionResult.Value as string; Assert.NotNull(body); if (body == null) { return; } dynamic objResponses = null; bool bDeserialized = true; try { objResponses = JsonConvert.DeserializeObject <List <dynamic> >(body); } catch { bDeserialized = false; } Assert.True(bDeserialized); if (!bDeserialized) { return; } // objResponses should be a list of dynamic objects, the first of // which should be an access token, the second of which should be // a refresh token int count = 0; foreach (var objResponse in objResponses) { count++; } Assert.Equal(2, count); if (count != 2) { return; } var objResponse0 = objResponses[0]; // Access token wrapper var objResponse1 = objResponses[1]; // Refresh token wrapper string sAccessToken = AppUtility.GetDynamicPropertyValueAsString(objResponse0, "access_token"); string sRefreshToken = AppUtility.GetDynamicPropertyValueAsString(objResponse1, "refresh_token"); Int64 iAccessExpiresIn = AppUtility.GetDynamicPropertyValueAsInt64(objResponse0, "expires_in", -1); Int64 iRefreshExpiresIn = AppUtility.GetDynamicPropertyValueAsInt64(objResponse1, "expires_in", -1); // Token strings should not be empty Assert.NotEqual(0, sAccessToken.Length); Assert.NotEqual(0, sRefreshToken.Length); // Expires values should not be null, Refresh value should always be greater than Access value Assert.True(iAccessExpiresIn > 0); Assert.True(iRefreshExpiresIn > 0); Assert.True(iRefreshExpiresIn > iAccessExpiresIn); // I should be able to decode those tokens, using the known secrets // and confirm that the details in the tokens are correct var handler = new JwtSecurityTokenHandler(); var accessTokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = jwtIssuerOptions.Issuer, ValidateAudience = true, ValidAudience = jwtIssuerOptions.Audience, ValidateIssuerSigningKey = true, IssuerSigningKey = signingKey, RequireExpirationTime = true, ValidateLifetime = true, ClockSkew = TimeSpan.FromSeconds(jwtIssuerOptions.AccessClockSkew) }; var refreshTokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = jwtIssuerOptions.Issuer, ValidateAudience = true, ValidAudience = jwtIssuerOptions.Audience, ValidateIssuerSigningKey = true, IssuerSigningKey = signingKey, RequireExpirationTime = true, ValidateLifetime = true, ClockSkew = TimeSpan.FromSeconds(jwtIssuerOptions.RefreshClockSkew) }; SecurityToken validatedRefreshToken = null; var refreshClaimsPrincipal = handler.ValidateToken(sRefreshToken, refreshTokenValidationParameters, out validatedRefreshToken); Assert.NotNull(validatedRefreshToken); SecurityToken validatedAccessToken = null; var accessClaimsPrincipal = handler.ValidateToken(sAccessToken, accessTokenValidationParameters, out validatedAccessToken); Assert.NotNull(validatedAccessToken); // Cast to JwtSecurityToken var validatedRefreshJwtSecurityToken = validatedRefreshToken as JwtSecurityToken; Assert.NotNull(validatedRefreshJwtSecurityToken); var validatedAccessJwtSecurityToken = validatedAccessToken as JwtSecurityToken; Assert.NotNull(validatedAccessJwtSecurityToken); // AccessClaimsPrincipal should have // Identies collection count of 1 // Identity with Name property of Alice // Identity property with some claims, including role of "User" int countIdentitiesA = 0; foreach (var possibleIdentityA in accessClaimsPrincipal.Identities) { ++countIdentitiesA; } Assert.Equal(1, countIdentitiesA); Assert.Equal("Alice", accessClaimsPrincipal.Identity.Name); int countClaimsA = 0; foreach (var possibleClaimA in accessClaimsPrincipal.Claims) { ++countClaimsA; } Assert.Equal(9, countClaimsA); Assert.True(accessClaimsPrincipal.HasClaim(ClaimTypes.Role, "User")); Assert.True(accessClaimsPrincipal.HasClaim(ClaimTypes.Name, "Alice")); // RefreshClaimsPrincipal should have Name, Guid and IP to validate against database. // Name and Guid may be strings, IP may be empty. int countIdentitiesR = 0; foreach (var possibleIdentityR in refreshClaimsPrincipal.Identities) { ++countIdentitiesR; } Assert.Equal(1, countIdentitiesR); int countClaimsR = 0; foreach (var possibleClaimR in refreshClaimsPrincipal.Claims) { ++countClaimsR; } Assert.Equal(6, countClaimsR); Assert.True(refreshClaimsPrincipal.HasClaim(c => c.Type == JwtController.GUIDKEY)); Assert.True(refreshClaimsPrincipal.HasClaim(JwtController.NAMEKEY, "Alice")); Assert.True(refreshClaimsPrincipal.HasClaim(c => c.Type == JwtController.IPKEY)); }