I am looking for a tutorial on how to mock authentication tokens for .net core 6 web services. Some years ago, at a previous job, I looked up how to do this with .net 3 and got it to work. But 6 removed the startup.cs file and seems to have shifted things around quite a bit. There are very few examples of doing this for 6.
I am in the process of creating a series of unit tests for the endpoints in a new web application. I am starting over from bare bones. Here is the code example. I know that it is possible to recreate the Startup.cs file, but for the time being I would prefer to do it without that. Are there any examples of this for a .net 6 specific architecture?
internal class DssiApiTest : WebApplicationFactory<Program>
{
private readonly string _environment;
public DssiApiTest(string environment = "Development")
{
_environment = environment;
}
protected override IHost CreateHost(IHostBuilder builder)
{
builder.UseEnvironment(_environment);
var settings = new ApiSettings();
// Add mock/test services to the builder here
builder.ConfigureServices(services =>
{
services.AddScoped(sp =>
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase("Tests")
.UseApplicationServiceProvider(sp)
.Options;
using (var context = new ApplicationDbContext(options,
MakeMockTenantService(),
(Microsoft.Extensions.Options.IOptions<ApiSettings>)settings))
{
context.PartCustomers.Add(CreatePartCustomer(1, 1));
context.PartCustomers.Add(CreatePartCustomer(2, 1));
context.SaveChanges();
}
// Replace SQLite with in-memory database for tests
return options;
});
});
return base.CreateHost(builder);
}
}
I think I found the answer to what I needed to do. I have not been able to fully test it yet, however, because I am running into another issue that is throwing an error. Will have to ask another question for that one. Here is what I have so far. If anyone can elaborate or correct this please feel free to do so. Like I said, I have not fully tested it and don't want to lead others astray. Will update once I have it worked out.
I added a new TestAuthHandler to the virtual client that returns a mock auth result like so :
using var application = new TestWebApplicationFactory();
var client = application.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddAuthentication("Test")
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
"Test", options => { });
});
})
.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false,
});
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Test");
Here is the code for the TestAuthHandler
public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public TestAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var claims = new[] { new Claim(ClaimTypes.Name, "Test user") };
var identity = new ClaimsIdentity(claims, "Test");
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, "Test");
var result = AuthenticateResult.Success(ticket);
return Task.FromResult(result);
}
}
Related
I have a queue processing application which is workign fine. I am now trying to persuade the queue trigger to only process one item at a time. My host.json is set up correctly, I think:
But when I run the app (either in Azure as a web job, or locally in Visual Studio), I see this:
I suspect that I am missing something really obvious, so wondering whether anyone has come across this before. I have found a few articles, but nothing that gives me any insight into what I am doing wrong.
Adding the contents of program.cs. I have tried adding "host.json" after the AddAzureAppConfiguration entry, but that makes no difference.
class Program
{
static async Task Main()
{
//var builder = new HostBuilder();
var builder = Host.CreateDefaultBuilder();
builder.ConfigureLogging((context, a) =>
{
a.AddConsole();
});
builder.ConfigureAppConfiguration((hostContext, config) =>
{
config.AddUserSecrets(
"5aa19112-5ff7-467b-b062-f37c3654872d"); // This is automatic for a web app, but not for a console app
var settings = config.Build();
var connectionString = settings.GetConnectionString("AzureAppConfiguration");
config.AddAzureAppConfiguration(connectionString);
});
builder.ConfigureWebJobs(b =>
{
b.AddAzureStorageCoreServices();
b.AddAzureStorage();
});
builder.ConfigureServices((hostContext, services) =>
{
services.AddMemoryCache();
});
var host = builder.Build();
using (host)
{
await host.RunAsync();
}
}
}
I think you are using the Azure WebJobs SDK v3.x. In v3.x, hosts.json does not work for WebJob.
Instead, version 3.x uses the standard ASP.NET Core APIs, so you need to configure it using the ConfigureWebJobs method:
static async Task Main()
{
var builder = new HostBuilder();
builder.ConfigureWebJobs(b =>
{
b.AddAzureStorageCoreServices();
b.AddAzureStorage(a => {
a.BatchSize = 8;
a.NewBatchThreshold = 4;
a.MaxDequeueCount = 4;
a.MaxPollingInterval = TimeSpan.FromSeconds(15);
});
});
var host = builder.Build();
using (host)
{
await host.RunAsync();
}
}
Docs: https://learn.microsoft.com/pt-pt/azure/app-service/webjobs-sdk-how-to#queue-storage-trigger-configuration
I have an Angular2 app. It is running within ASP.NET 5 (Core).
It makes Http calls to the controller which is working fine.
But now I need to establish Cross Site Scripting projection.
How do I generate a new token on each Http request and then subsequently perform the AntiForgeryToken check in Angular2 apps?
Note: My data forms in Angular are not produced from an MVC view but entirely written in Angular2 and call web services only.
All the examples I have seen are out dated and do not work / do not work fully.
How do I integrate AntiForgeryToken checks in Angular2 against ASP.NET 5 where forms are pure Angular?
Thanks.
A custom action filter is not necessary. It can all be wired up in Startup.cs.
using Microsoft.AspNetCore.Antiforgery;
(...)
public void ConfigureServices(IServiceCollection services)
{
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
(...)
}
public void Configure(IApplicationBuilder app, IAntiforgery antiforgery)
{
app.Use(next => context =>
{
if (context.Request.Path == "/")
{
//send the request token as a JavaScript-readable cookie, and Angular will use it by default
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions { HttpOnly = false });
}
return next(context);
});
(...)
}
Then all you need in your controllers is the [ValidateAntiForgeryToken] decorator wherever you want to enforce that a token is provided.
For reference, I found this solution here - AspNet AntiForgery Github Issue 29.
I am using a action filter to send the request tokens.
Simply apply it to the actions you want a new antiforgery token, e.g. Angular2 SPA, WebAPI action, etc.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class AngularAntiForgeryTokenAttribute : ActionFilterAttribute
{
private const string CookieName = "XSRF-TOKEN";
private readonly IAntiforgery antiforgery;
public AngularAntiForgeryTokenAttribute(IAntiforgery antiforgery)
{
this.antiforgery = antiforgery;
}
public override void OnResultExecuting(ResultExecutingContext context)
{
base.OnResultExecuting(context);
if (!context.Cancel)
{
var tokens = antiforgery.GetAndStoreTokens(context.HttpContext);
context.HttpContext.Response.Cookies.Append(
CookieName,
tokens.RequestToken,
new CookieOptions { HttpOnly = false });
}
}
}
/* HomeController */
[ServiceFilter(typeof(AngularAntiForgeryTokenAttribute), IsReusable = true)]
public IActionResult Index()
{
return View();
}
/* AccountController */
[HttpPost()]
[AllowAnonymous]
[ValidateAntiForgeryToken]
// Send new antiforgery token
[ServiceFilter(typeof(AngularAntiForgeryTokenAttribute), IsReusable = true)]
public async Task<IActionResult> Register([FromBody] RegisterViewModel model)
{
//...
return Json(new { });
}
Register the attribute in Startup, and configure Antiforgery service to read the request token form "X-XSRF-TOKEN" header.
public class Startup
{
// ...
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddScoped<AngularAntiForgeryTokenAttribute>();
services.AddAntiforgery(options =>
{
options.HeaderName = "X-XSRF-TOKEN";
});
}
}
I think you need to make custom AntiForgeryValidationToken attribute that supports sending token via header instead of form values. Then add token to header of every request from your Angular2 app to your api. Example here How do you set global custom headers in Angular2?
To validate the token from a header you can use something like this:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class ValidateHeaderAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException(nameof(filterContext));
}
var httpContext = filterContext.HttpContext;
if (httpContext.Request.Headers["__RequestVerificationToken"] == null)
{
httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
httpContext.Response.StatusDescription = "RequestVerificationToken missing.";
filterContext.Result = new JsonResult
{
Data = new { ErrorMessage = httpContext.Response.StatusDescription },
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
return;
}
var cookie = httpContext.Request.Cookies[System.Web.Helpers.AntiForgeryConfig.CookieName];
System.Web.Helpers.AntiForgery.Validate(cookie != null ? cookie.Value : null, httpContext.Request.Headers["__RequestVerificationToken"]);
}
}
Then you just add [ValidateHeaderAntiForgeryToken] on the methods in your controller. Note though, this is from a MVC 5, ASP.NET 4.5.2 project, so you may have to alter it slightly to adjust to .NET Core. Also I modified this to return a JSON result if the token is missing, you can remove that part if you don't handle the error response and output it to the user.
Credits for the core part of this attribute goes to: https://nozzlegear.com/blog/send-and-validate-an-asp-net-antiforgerytoken-as-a-request-header
The hard part is how to generate the AntiForgeryToken without using #Html.AntiForgeryToken() in pure Angular 2 application (without access to .cshtml files). I'm looking for an answer to that as well.
I'm trying to learn about Unit Testing with the new VS2013 Default MVC w/ Authentication Project. One of the first things I want to test is registering a user. (I know I probably don't need to unit test this since it's already MS tested code but I want to use this to understand the basics). I've also heard that the new Membership code is more 'testable' so I don't need to create my own membership interfaces, etc...
I'm using NSubstitute as a faking framework.
Looking at the Account Controller -> Register() async method
namespace WebApplication1.Controllers
{
[Authorize]
public class AccountController : Controller
{
public AccountController()
: this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
{
}
public AccountController(UserManager<ApplicationUser> userManager)
{
UserManager = userManager;
}
public UserManager<ApplicationUser> UserManager { get; private set; }
...
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.UserName };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
If I wanted to write a simple test (ex. Register_RegisterValidUser), how would I do this? I know I need to substitute for the UserManager somehow but this did not work for me:
var substitute = Substitute.For<UserManager<ApplicationUser>>();
I also understand that I need to bypass the async Task<> functions using Task.FromResult but I'm not sure how to return a valid objects from the CreateAsync() and the SigninAsync() methods.
Can somebody help with some sample test code? Many thanks!
To mock the user manager with NSubstitute you have to use this:
var userStore = Substitute.For<IUserStore<ApplicationUser>>();
var userManager = Substitute.For<UserManager<ApplicationUser>>(userStore);
Now you can fake the methods result too. For example:
userManager.FindByNameAsync(Arg.Any<string>()).Returns(Task.FromResult(new ApplicationUser()));
Hope this help you.
Test Guidance
You can Unit Test couple of behaviours, but I will just show you the direction for a single Unit test that just verify whether receive the correct RedirectToAction value. For example,
return RedirectToAction("Index", "Home");
Improving the Testability
In your question you mentioned
I've also heard that the new Membership code is more 'testable' so I
don't need to create my own membership interfaces, etc..
While this is true, I would make a small adjustment so we can make your implementation bit more testable. This way your Unit Test can purely concentrate on the specific behaviour your going test. In other words we can make you SUT (System Under Test) more testable.
await SignInAsync(user, isPersistent: false);
I believe SignInAsync is a private method. There should be some behavior in this method you can probably extract out to a seperate implementation which you can inject in to the SUT. We can call this ISignInManager
public interface ISignInManager {
Task SignInAsync(ApplicationUser user, bool isPersistent);
}
The benefit of this is that now you can inject the behaiovr of an ISignInManager to perform the SignIn tasks and your SUT become more testable. You should see that there will be less mocking/stubbing in your Unit test and make your test easier to write and understand.
Unit Test
You can take the advantage of new async/await usage of the MSTest method. This simplifies complicated and unreliable tests which we used to write.
A Unit Test that verify the correct redirect route controller/action methods, can be written as below.
[TestMethod]
public async Task Register_RegisterValidUser_EnsureRedirectToIndexActionHomeController()
{
// Arrange
var userManagerStub = Substitute.For<IUserManager<ApplicationUser>>();
var tcs = new TaskCompletionSource<IdentityResult>();
tcs.SetResult(new IdentityResult(true));
userManagerStub.CreateAsync(Arg.Any<ApplicationUser>(), Arg.Any<string>()).Returns(tcs.Task);
var signInManagerStub = Substitute.For<ISignInManager>>();
signInManagerStub.Setup(s => s.SignInAsync(It.IsAny<ApplicationUser>(), It.IsAny<bool>())).Returns(Task.FromResult(true));
var sut = new AccountController(userManagerStub) { SignInManager = signInManagerStub.Object };
// Act
var result = await sut.Register(new RegisterViewModel() { Password = "fakePw" }) as RedirectToRouteResult;
// Assert
Assert.AreEqual<string>("Index", result.RouteValues["action"].ToString());
Assert.AreEqual<string>("Home", result.RouteValues["controller"].ToString());
}
The above uses NSubstitute as the isolation framework, but if anyone interested in the Moq version, please see below.
[TestMethod]
public async Task Register_RegisterValidUser_EnsureRedirectToIndexHome()
{
// Arrange
var userManagerStub = new Mock<IUserManager<ApplicationUser>>();
var tcs = new TaskCompletionSource<IdentityResult>();
tcs.SetResult(new IdentityResult(true));
userManagerStub.Setup(s => s.CreateAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>())).Returns(tcs.Task);
var signInManagerStub = new Mock<ISignInManager>();
signInManagerStub.Setup(s => s.SignInAsync(It.IsAny<ApplicationUser>(), It.IsAny<bool>())).Returns(Task.FromResult(true));
var sut = new AccountController(userManagerStub.Object) {SignInManager = signInManagerStub.Object};
// Act
var result = await sut.Register(new RegisterViewModel() { Password = "fakePw" }) as RedirectToRouteResult;
// Assert
Assert.AreEqual<string>("Index", result.RouteValues["action"].ToString());
Assert.AreEqual<string>("Home", result.RouteValues["controller"].ToString());
}
I don't have much experience with .NET Web Api, but i've been working with it a while now, following John Papa's SPA application tutorial on Pluralsight. The application works fine, but the thing i'm struggling with now, is unit testing POST-controllers.
I have followed this incredible guide on how to unit test web api controllers. The only problem for me is when it comes to test the POST method.
My controller looks like this:
[ActionName("course")]
public HttpResponseMessage Post(Course course)
{
if (course == null)
throw new HttpResponseException(HttpStatusCode.NotAcceptable);
try
{
Uow.Courses.Add(course);
Uow.commit();
}
catch (Exception)
{
throw new HttpResponseException(HttpStatusCode.InternalServerError);
}
var response = Request.CreateResponse(HttpStatusCode.Created, course);
string uri = Url.Link(routeName: "ControllerActionAndId",
routeValues: new { id = course.Id });
response.Headers.Location = new Uri(uri);
return response;
}
And my unit test looks like this:
[Test]
public void PostShouldReturnHttpResponse()
{
var populatedPostController = new CoursesController(new TestUOW());
SetupPostControllerForTest(populatedPostController);
var course = new Course
{
Id = 12,
Author = new UserProfile()
{
Firstname = "John",
Lastname = "Johnson",
},
Description = "Testcourse",
Title = "Test Title"
};
var responses = populatedPostController.Post(course);
ObjectContent content = responses.Content as ObjectContent;
Course result = (Course)content.Value;
Assert.AreSame(result, course);
}
With the help function:
public static void SetupPostControllerForTest(ApiController controller)
{
var config = new HttpConfiguration();
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/api/courses/course");
var route = config.Routes.MapHttpRoute(
name: "ControllerActionAndId",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: null,
constraints: new { id = #"^\d+$" }
);
var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", "courses" }, { "action", "course" } });
controller.ControllerContext = new HttpControllerContext(config, routeData, request);
controller.Request = request;
controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
}
When i debug the unit test, it seems to fail at:
string uri = Url.Link(routeName: "ControllerActionAndId",
routeValues: new { id = course.Id });
response.Headers.Location = new Uri(uri); //Exception because uri = null
It seems like the Url.Link can't find the route.
I tried this guide aswell, but i really want the example i have above to work.
Am i missing something really basic here?
Yes, you are missing the one line in the configuration as Nemesv mentioned.
controller.Request.Properties[HttpPropertyKeys.HttpRouteDataKey] = routeData
As you can see, configuring a controller just for using the UrlHelper is extremely complex. I tend to avoid the use of UrlHelper in the controller classes for that reason. I usually introduce an external dependency to make testing easier like an IUrlHelper, which allows me to mock the behavior in an unit test.
public interface IUrlHelper
{
string Link(string routeName, object routeValues);
string Route(string routeName, object routeValues);
}
public class UrlHelperWrapper : IUrlHelper
{
UrlHelper helper;
public UrlHelperWrapper(UrlHelper helper)
{
this.helper = helper;
}
public string Link(string routeName, object routeValues)
{
return this.helper.Link(routeName, routeValues);
}
public string Route(string routeName, object routeValues)
{
return this.helper.Route(routeName, routeValues);
}
}
I inject this UrlHelperWraper in the real Web API, and a mock of the IUrlHelper interface in the tests. By doing that, you don't need all that complex configuration with the routes.
Regards,
Pablo.
All,
I'm developing and unit testing an interactive voice application using ASP.NET MVC 3 whose controllers return Views containing VoiceXML. I'd like to create unit tests that capture the actual VoiceXML output so I can schema-validate it.
My reading and testing have taken me to Scott H's FakeHttpContext that uses Moq, as well as several responses here. Everything compiles correctly, and I'm trying to do something like the following:
[TestMethod]
public void WelcomeTest1()
{
EmergencyController controller = new EmergencyController();
controller.ControllerContext = new ControllerContext(MvcMockHelpers.FakeHttpContext("~/Emergency/Welcome"), new RouteData(), controller);
ViewResult result = (controller.Welcome()) as ViewResult;
.
.
Assert.IsTrue(controller.ControllerContext.HttpContext.Response.OutputStream.Length > 0);
// assert schema validation on the output here
}
However, stepping through this, I can see that the Welcome view being called, but I'm looking for something in the Response.Output and not finding anything. The mock is set up as follows, in hope that setting CallBase to true would actually write something out. I found some code that I added to the FakeHttpContext constructor that supposedly invokes a StringWriter, but to no avail:
public static HttpContextBase FakeHttpContext()
{
var context = new Mock<HttpContextBase>();
var request = new Mock<HttpRequestBase>() { CallBase = true };
var response = new Mock<HttpResponseBase>();
var session = new Mock<HttpSessionStateBase>();
var server = new Mock<HttpServerUtilityBase>();
context.Setup(ctx => ctx.Request).Returns(request.Object);
context.Setup(ctx => ctx.Response).Returns(response.Object);
context.Setup(ctx => ctx.Session).Returns(session.Object);
context.Setup(ctx => ctx.Server).Returns(server.Object);
response.Setup(r => r.OutputStream).Returns(new MemoryStream());
response.Setup(r => r.Headers).Returns(new NameValueCollection());
var writer = new StringWriter();
var wr = new SimpleWorkerRequest("", "", "", "", writer);
HttpContext.Current = new HttpContext(wr);
return context.Object;
}
I'm sure I'm missing something obvious, but I'm stumped right now.
Thanks
Jim Stanley
Blackboard Connect
The result doesn't get populated in the ViewResult. In other words, the view isn't rendered by you calling return View() in your controller, rather mvc takes the viewresult and renders it at a later time in the request-sycle. What you need to do is to create a render-function for ViewResults, like this one:
using System;
using System.IO;
using System.Web.Mvc;
namespace CoPrice.Helpers
{
public static class ViewRendrer
{
public static string ToHtml(this ViewResult result, Controller controller)
{
controller.ViewData.Model = result.Model;
try
{
using (StringWriter sw = new StringWriter())
{
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, result.ViewName);
ViewContext context = new ViewContext(controller.ControllerContext, viewResult.View, result.ViewData, result.TempData, sw);
viewResult.View.Render(context, sw);
return sw.GetStringBuilder().ToString();
}
}
catch (Exception e)
{
return e.ToString();
}
}
}
}
Then you can do result.ToHtml(controller) to get the actual data (this works for RazorViews only I think, though I'm not sure, that's what I've used it for at least).