Allow "semi anonymous" authentication - c#

I have a case where I need some controller methods to be accessible either by an authenticated user, or if the request contains a sort of "acccess token" in the url.
For example:
Either an authenticated user could make a call to:
https://example.com/some/resource
Or a non authenticated user could make the same call, but add some kind of token to the url (or as a header):
https://example.com/some/resource?token=123abc
The token does not have to be super secret, only something hard to guess.
[AllowSpecialToken]
[HttpGet]
[Route("some/resource")]
public async Task<string> GetSomeResource()
{
return "some resource";
}
What I'm struggling with is how to write the AllowSpecialTokenAttribute. And how to get that to run before the authentication (using OpenIddict) we have in place now.
Is this a stupid use case? Should I find another solution?
To give some context: We have a SPA that calls our API. Some pages of the SPA can be shared with others (non user) just by sending a link. That link will contain the token. The content of those pages are not critical security wise, but they shouldn't be completely open.

You need to make your own authentication attribute. I've done something like that in the past, here is my stub at it:
public class TokenAuthenticationAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
// this will read `token` parameter from your URL
ValueProviderResult valueProvided = filterContext.Controller.ValueProvider.GetValue("token");
if (valueProvided == null)
{
filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
return;
}
var providedToken = valueProvided.AttemptedValue;
var storedToken = "12345"; // <-- get your token value from DB or something
if (storedToken != providedToken)
{
filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
return;
}
}
}
Then decorate your action with the attribute:
[TokenAuthentication]
[HttpGet]
[Route("some/resource")]
public async Task<string> GetSomeResource()
{
return "some resource";
}
And get your URI looking like https:\\www.example.com\api\some\resource?token=12345

You could try the below and see if it works for you.
Caveat: I have absolutely no idea if this is the "correct" way to do this. I just know it is a way that appears to work. Please test and downvote if you find problems. I still have an open question on another authentication handler I have written, but with no replies, so use with caution. It may be worth contacting blowdart (search users) at MS if you are going to pursue this use case.
Middleware Class
public class TokenCodeAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public const string DefaultSchemeName = "TokenAuthScheme";
public TokenCodeAuthHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
AuthenticateResult result = await this.Context.AuthenticateAsync();
if (result.Succeeded)
{
//User has supplied details
return AuthenticateResult.Success(result.Ticket);
}
else if (Context.Request.Query["token"] == "123abc") //TODO: Change hard-coded token
{
//User has supplied token
string username = "Test"; //Get/set username here
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, username, ClaimValueTypes.String, Options.ClaimsIssuer),
new Claim(ClaimTypes.Name, username, ClaimValueTypes.String, Options.ClaimsIssuer)
};
ClaimsPrincipal principal = new ClaimsPrincipal(new ClaimsIdentity(claims, Scheme.Name));
AuthenticationTicket ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
return AuthenticateResult.Fail("Unauthorized");
}
}
Configure Services in Startup
services.AddAuthentication()
.AddScheme<AuthenticationSchemeOptions, TokenCodeAuthHandler>(
TokenCodeAuthHandler.DefaultSchemeName,
(o) => { });
Attribute Usage
Use on controller actions as follows:
Note - I couldn't seem to override controller level authorize attributes).
[Authorize(AuthenticationSchemes = TokenCodeAuthHandler.DefaultSchemeName)]
[HttpGet]
[Route("some/resource")]
public async Task<string> GetSomeResource()
{
return "some resource";
}

Related

.net core 3.1 identity: cookies create a "request too long" error

I created a class using IUserClaimsStore to set the claims after login:
public class TheUserStore : IUserStore<User>, IUserRoleStore<User>, IUserPasswordStore<User>, IUserClaimStore<User>
The problem is, when I have a user with multiple roles and permissions, storing my claims no longer works and there are cookies stored that produce a "request too long" error. They look like this:
Is there a way to tell c# not to store those informations in cookies or any other way to prevent this problem?
I haven't been working with c# for a long time so I'm at a loss here.
Thanks!
Ok, not sure if this is the correct answer, but I found a way to stop .net core from storing all that information in cookies. In the startup.cs I add the following line:
services.AddScoped<IUserClaimsPrincipalFactory<User>, AppClaimsPrincipalFactory>();
And then I created the AppClaimsPrinzipalFactory.cs which contains the following:
public class AppClaimsPrincipalFactory : UserClaimsPrincipalFactory<User, Role> {
public AppClaimsPrincipalFactory(UserManager<User> userManager, RoleManager<Role> roleManager, IOptions<IdentityOptions> optionsAccessor)
: base(userManager, roleManager, optionsAccessor) {
}
public override async Task<ClaimsPrincipal> CreateAsync(User user) {
if (user == null) {
throw new ArgumentNullException(nameof(user));
}
var userId = await UserManager.GetUserIdAsync(user);
var userName = await UserManager.GetUserNameAsync(user);
var id = new ClaimsIdentity("Identity.Application",
Options.ClaimsIdentity.UserNameClaimType,
Options.ClaimsIdentity.RoleClaimType);
id.AddClaim(new Claim(Options.ClaimsIdentity.UserIdClaimType, userId));
id.AddClaim(new Claim(Options.ClaimsIdentity.UserNameClaimType, userName));
if (UserManager.SupportsUserSecurityStamp) {
id.AddClaim(new Claim(Options.ClaimsIdentity.SecurityStampClaimType,
await UserManager.GetSecurityStampAsync(user)));
}
// code removed that adds the role claims
if (UserManager.SupportsUserClaim) {
var claims = await UserManager.GetClaimsAsync(user);
id.AddClaims(claims);
}
return new ClaimsPrincipal(id);
}
}
Not sure what exactly happens here but it works. Found this answer here: Recommended best practice for role claims as permissions

How to manually set value of HttpContext.User.Identity.IsAuthenticated

I am creating an Asp.NET MVC 5 application. For this project, I am trying to implement a custom authentication mechanism (i don't want to use Forms Authentication / OWIN etc. external providers)
I created a custom authorize attribute as follows:
[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class myAuthorize : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (!HttpContext.Current.Request.IsAuthenticated)
{
httpContext.Response.Redirect("~/Account/Login");
}
return base.AuthorizeCore(httpContext);
}
}
And in my Login action, I am trying to change the value of
HttpContext.User.Identity.IsAuthenticated
But it is read only and I am unable to change the value. Can I change its value manually or am I making a logical mistake.
You can achieve this by manually settings HttpContext.User:
var identity = new ClaimsIdentity("Custom");
HttpContext.User = new ClaimsPrincipal(identity);
It's important to set a custom authenticationType. In the example above, I just used the string "Custom", but it can be anything you want.
With that, HttpContext.User.Identity.IsAuthenticated will be true.
For something more complicated, you can add claims like this:
var identity = new ClaimsIdentity(new List<Claim>
{
new Claim("UserId", "123", ClaimValueTypes.Integer32)
}, "Custom");
HttpContext.User = new ClaimsPrincipal(identity);
This results in:
HttpContext.User.Identity.IsAuthenticated == true;
int.Parse(((ClaimsIdentity)HttpContext.User.Identity).ValueFromType("UserId")) == 123;
My answer may no suit you perfectly but it may help.
In my ASP.NET Core MVC application, administrators need to impersonate other users.
It is an intranet application and obviously users are authenticated by Windows authentication.
It is done thanks to an ajax request to this controller action :
public async Task<JsonResult> UserImpersonation(IdentityExtension userIdentity)
IdentityExtension is a custom class of which you can observe the signature below :
public class IdentityExtension : IIdentity
{
public IdentityExtension()
{ }
public IdentityExtension(string name)
{
this.Name = name;
}
public string AuthenticationType => "Kerberos";
public bool IsAuthenticated => true;
public string Name { get; set; }
}
UserImpersonation method returns the success state of ReplaceUser method, wich updates HttpContext.User this way :
this.HttpContext.User = identity as ClaimsPrincipal;
return true;
identity being an instance of IdentityExtension.
I hope my solution can be adapted to your use case !
We can see, from the source code:
/// <summary>
/// Gets a value that indicates if the user has been authenticated.
/// </summary>
public virtual bool IsAuthenticated
{
get { return !string.IsNullOrEmpty(_authenticationType); }
}
so, it means when we have a null _authenticationType, IsAuthenticated will always be false;
otherwise, it will be true.
If you want the HttpContext to persist and be able to fetch claims afterward, do the following. I'll be using Cookie Authentication as the authenticationType.
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme, ClaimTypes.Name, ClaimTypes.Role);
identity.AddClaim(new Claim(ClaimTypes.Name, TempData["Username"].ToString()));
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties { IsPersistent = true });
you can't use FormsAuthentication.SetAuthCookie?
https://learn.microsoft.com/en-us/dotnet/api/system.web.security.formsauthentication.setauthcookie?view=netframework-4.7.2

ASP.NET Core MVC custom identity properties

I am trying to build a website in ASP.NET Core MVC and am using the Microsoft.Identity library. I have a custom property in my User (ApplicationUser) class which is called Token. I want to create a cookie on login with that token. So I need to call some function that allows me to fetch the Token attribute from the logged in user (via UserManager or whatever. It has to be the user that logged in.)
I have searched on the internet and have found several solutions by creating a custom Factory and then adding it to the startup.cs Like this. But I cannot find or see a way to access the property. User.Identity.GetToken() does not work.
Here is my custom factory:
public class CustomUserIdentityFactory : UserClaimsPrincipalFactory<User, IdentityRole>
{
public CustomUserIdentityFactory(UserManager<User> userManager, RoleManager<IdentityRole> roleManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, roleManager, optionsAccessor)
{}
public override async Task<ClaimsPrincipal> CreateAsync(User user) {
var principal = await base.CreateAsync(user);
if(!string.IsNullOrWhiteSpace(user.Token)) {
((ClaimsIdentity)principal.Identity).AddClaims(new[] {
new Claim(ClaimTypes.Hash, user.Token)
});
}
return principal;
}
}
Here is the configure in my Startup.cs
services.AddScoped<IUserClaimsPrincipalFactory<User>, CustomUserIdentityFactory>();
So, long story short: I am trying to access a custom identity property and have found a way to add it to the UserManager, but can not find a way to access it.
Your "CustomUserIdentityFactory" adding claims to the logged in user, so that claims will be added in to the cookie, which can be accessed using "User.Claims" by specifying your claim type.
Assume your claim type is "http://www.example.com/ws/identity/claims/v1/token"
Change your code as below by overriding "CreateAsync" method using your own claim type.
public override async Task<ClaimsPrincipal> CreateAsync(User user) {
var principal = await base.CreateAsync(user);
var tokenClaimType = "http://www.example.com/ws/identity/claims/v1/token"
if(!string.IsNullOrWhiteSpace(user.Token)) {
((ClaimsIdentity)principal.Identity).AddClaims(new[] {
new Claim(tokenClaimType, user.Token)
});
}
return principal;
}
How to access token as part of "User.Claims"
var tokenClaimType = "http://www.example.com/ws/identity/claims/v1/token"
var token = User.Claims.Where(claim => claim.Type == tokenClaimType);
Hope this helps.

DB-First authentication confusion with ASP.NET Web API 2 + EF6

I need to create a Web API C# application for an existing MySQL database. I've managed to use Entity Framework 6 to bind every database table to a RESTful API (that allows CRUD operations).
I want to implement a login/registration system (so that I can implement roles and permissions in the future, and restrict certain API requests).
The MySQL database I have to use has a table for users (called user) that has the following self-explanatory columns:
id
email
username
password_hash
It seems that the de-facto standard for authentication is ASP.Net Identity. I have spent the last hour trying to figure out how to make Identity work with an existing DB-First Entity Framework setup.
If I try to construct ApplicationUser instances storing user instances (entities from the MySQL database) to retrieve user data, I get the following error:
The entity type ApplicationUser is not part of the model for the current context.
I assume I need to store Identity data in my MySQL database, but couldn't find any resource on how to do that. I've tried completely removing the ApplicationUser class and making my user entity class derive from IdentityUser, but calling UserManager.CreateAsync resulted in LINQ to Entities conversion errors.
How do I setup authentication in a Web API 2 application, having an existing user entity?
You say:
I want to implement a login/registration system (so that I can
implement roles and permissions in the future, and restrict certain
API requests).
How do I setup authentication in a Web API 2 application, having an
existing user entity?
It definitely means that you DO NOT need ASP.NET Identity. ASP.NET Identity is a technology to handle all users stuffs. It actually does not "make" the authentication mechanism. ASP.NET Identity uses OWIN Authentication mechanism, which is another thing.
What you are looking for is not "how to use ASP.NET Identity with my existing Users table", but "How to configure OWIN Authentication using my existing Users table"
To use OWIN Auth follow these steps:
Install the packages:
Owin
Microsoft.AspNet.Cors
Microsoft.AspNet.WebApi.Client
Microsoft.AspNet.WebApi.Core
Microsoft.AspNet.WebApi.Owin
Microsoft.AspNet.WebApi.WebHost
Microsoft.Owin
Microsoft.Owin.Cors
Microsoft.Owin.Host.SystemWeb
Microsoft.Owin.Security
Microsoft.Owin.Security.OAuth
Create Startup.cs file inside the root folder (example):
make sure that [assembly: OwinStartup] is correctly configured
[assembly: OwinStartup(typeof(YourProject.Startup))]
namespace YourProject
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
//other configurations
ConfigureOAuth(app);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
var oAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/api/security/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(2),
Provider = new AuthorizationServerProvider()
};
app.UseOAuthAuthorizationServer(oAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
try
{
//retrieve your user from database. ex:
var user = await userService.Authenticate(context.UserName, context.Password);
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, user.Name));
identity.AddClaim(new Claim(ClaimTypes.Email, user.Email));
//roles example
var rolesTechnicalNamesUser = new List<string>();
if (user.Roles != null)
{
rolesTechnicalNamesUser = user.Roles.Select(x => x.TechnicalName).ToList();
foreach (var role in user.Roles)
identity.AddClaim(new Claim(ClaimTypes.Role, role.TechnicalName));
}
var principal = new GenericPrincipal(identity, rolesTechnicalNamesUser.ToArray());
Thread.CurrentPrincipal = principal;
context.Validated(identity);
}
catch (Exception ex)
{
context.SetError("invalid_grant", "message");
}
}
}
}
Use the [Authorize] attribute to authorize the actions.
Call api/security/token with GrantType, UserName, and Password to get the bearer token. Like this:
"grant_type=password&username=" + username + "&password=" password;
Send the token within the HttpHeader Authorization as Bearer "YOURTOKENHERE". Like this:
headers: { 'Authorization': 'Bearer ' + token }
Hope it helps!
Since your DB schema are not compatible with default UserStore You must implement your own UserStore and UserPasswordStore classes then inject them to UserManager. Consider this simple example:
First write your custom user class and implement IUser interface:
class User:IUser<int>
{
public int ID {get;set;}
public string Username{get;set;}
public string Password_hash {get;set;}
// some other properties
}
Now author your custom UserStore and IUserPasswordStore class like this:
public class MyUserStore : IUserStore<User>, IUserPasswordStore<User>
{
private readonly MyDbContext _context;
public MyUserStore(MyDbContext context)
{
_context=context;
}
public Task CreateAsync(AppUser user)
{
// implement your desired logic such as
// _context.Users.Add(user);
}
public Task DeleteAsync(AppUser user)
{
// implement your desired logic
}
public Task<AppUser> FindByIdAsync(string userId)
{
// implement your desired logic
}
public Task<AppUser> FindByNameAsync(string userName)
{
// implement your desired logic
}
public Task UpdateAsync(AppUser user)
{
// implement your desired logic
}
public void Dispose()
{
// implement your desired logic
}
// Following 3 methods are needed for IUserPasswordStore
public Task<string> GetPasswordHashAsync(AppUser user)
{
// something like this:
return Task.FromResult(user.Password_hash);
}
public Task<bool> HasPasswordAsync(AppUser user)
{
return Task.FromResult(user.Password_hash != null);
}
public Task SetPasswordHashAsync(AppUser user, string passwordHash)
{
user.Password_hash = passwordHash;
return Task.FromResult(0);
}
}
Now you have very own user store simply inject it to the user manager:
public class ApplicationUserManager: UserManager<User, int>
{
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(new MyUserStore(context.Get<MyDbContext>()));
// rest of code
}
}
Also please note you must directly inherit your DB Context class from DbContext not IdentityDbContext since you have implemented own user store.

Unit Testing ASP.NET MVC5 App

I'm extending the ApplicationUser class by adding a new property (as shown in the tutorial
Create an ASP.NET MVC 5 App with Facebook and Google OAuth2 and OpenID Sign-on (C#))
public class ApplicationUser : IdentityUser
{
public DateTime BirthDate { get; set; }
}
Now I want to create a Unit Test to verify that my AccountController is correctly saving the BirthDate.
I've created an in-memory user store named TestUserStore
[TestMethod]
public void Register()
{
// Arrange
var userManager = new UserManager<ApplicationUser>(new TestUserStore<ApplicationUser>());
var controller = new AccountController(userManager);
// This will setup a fake HttpContext using Moq
controller.SetFakeControllerContext();
// Act
var result =
controller.Register(new RegisterViewModel
{
BirthDate = TestBirthDate,
UserName = TestUser,
Password = TestUserPassword,
ConfirmPassword = TestUserPassword
}).Result;
// Assert
Assert.IsNotNull(result);
var addedUser = userManager.FindByName(TestUser);
Assert.IsNotNull(addedUser);
Assert.AreEqual(TestBirthDate, addedUser.BirthDate);
}
The controller.Register method is boilerplate code generated by MVC5 but for reference purposes I'm including it here.
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.UserName, BirthDate = model.BirthDate };
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);
}
When I call Register, it calls SignInAsync which is where the trouble will occur.
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
At the lowest layer, the boilerplate code includes this tidbit
private IAuthenticationManager AuthenticationManager
{
get
{
return HttpContext.GetOwinContext().Authentication;
}
}
This is where the root of the problm occurs. This call to GetOwinContext is an extension method which I cannot mock and I cannot replace with a stub (unless of course I change the boilerplate code).
When I run this test I get an exception
Test method MVCLabMigration.Tests.Controllers.AccountControllerTest.Register threw exception:
System.AggregateException: One or more errors occurred. ---> System.NullReferenceException: Object reference not set to an instance of an object.
at System.Web.HttpContextBaseExtensions.GetOwinEnvironment(HttpContextBase context)
at System.Web.HttpContextBaseExtensions.GetOwinContext(HttpContextBase context)
at MVCLabMigration.Controllers.AccountController.get_AuthenticationManager() in AccountController.cs: line 330
at MVCLabMigration.Controllers.AccountController.<SignInAsync>d__40.MoveNext() in AccountController.cs: line 336
In prior releases the ASP.NET MVC team worked very hard to make the code testable. It seems on the surface that now testing an AccountController is not going to be easy. I have some choices.
I can
Modify the boiler plate code so that it doesn't call an extension method and deal with this problem at that level
Setup the OWin pipeline for testing purposes
Avoid writing testing code that requires the AuthN / AuthZ infrastructure (not a reasonable option)
I'm not sure which road is better. Either one could solve this. My question boils down to which is the best strategy.
Note: Yes, I know that I don't need to test code that I didn't write. The UserManager infrastructure provided MVC5 is such a piece of infrastructure BUT if I want to write tests that verify my modifications to ApplicationUser or code that verifies behavior that depends upon user roles then I must test using UserManager.
I'm answering my own question so I can get a sense from the community if you think this is a good answer.
Step 1: Modify the generated AccountController to provide a property setter for the AuthenticationManager using a backing field.
// Add this private variable
private IAuthenticationManager _authnManager;
// Modified this from private to public and add the setter
public IAuthenticationManager AuthenticationManager
{
get
{
if (_authnManager == null)
_authnManager = HttpContext.GetOwinContext().Authentication;
return _authnManager;
}
set { _authnManager = value; }
}
Step 2:
Modify the unit test to add a mock for the Microsoft.OWin.IAuthenticationManager interface
[TestMethod]
public void Register()
{
// Arrange
var userManager = new UserManager<ApplicationUser>(new TestUserStore<ApplicationUser>());
var controller = new AccountController(userManager);
controller.SetFakeControllerContext();
// Modify the test to setup a mock IAuthenticationManager
var mockAuthenticationManager = new Mock<IAuthenticationManager>();
mockAuthenticationManager.Setup(am => am.SignOut());
mockAuthenticationManager.Setup(am => am.SignIn());
// Add it to the controller - this is why you have to make a public setter
controller.AuthenticationManager = mockAuthenticationManager.Object;
// Act
var result =
controller.Register(new RegisterViewModel
{
BirthDate = TestBirthDate,
UserName = TestUser,
Password = TestUserPassword,
ConfirmPassword = TestUserPassword
}).Result;
// Assert
Assert.IsNotNull(result);
var addedUser = userManager.FindByName(TestUser);
Assert.IsNotNull(addedUser);
Assert.AreEqual(TestBirthDate, addedUser.BirthDate);
}
Now the test passes.
Good idea? Bad idea?
My needs are similar, but I realized that I don't want a pure unit test of my AccountController. Rather I want to test it in an environment that is as close as possible to its natural habitat (integration test, if you want). So I don't want to mock the surrounding objects, but use the real ones, with as little of my own code as I can get away with.
The HttpContextBaseExtensions.GetOwinContext method also got in my way, so I was very happy with Blisco's hint.
Now the most important part of my solution looks like this:
/// <summary> Set up an account controller with just enough context to work through the tests. </summary>
/// <param name="userManager"> The user manager to be used </param>
/// <returns>A new account controller</returns>
private static AccountController SetupAccountController(ApplicationUserManager userManager)
{
AccountController controller = new AccountController(userManager);
Uri url = new Uri("https://localhost/Account/ForgotPassword"); // the real string appears to be irrelevant
RouteData routeData = new RouteData();
HttpRequest httpRequest = new HttpRequest("", url.AbsoluteUri, "");
HttpResponse httpResponse = new HttpResponse(null);
HttpContext httpContext = new HttpContext(httpRequest, httpResponse);
Dictionary<string, object> owinEnvironment = new Dictionary<string, object>()
{
{"owin.RequestBody", null}
};
httpContext.Items.Add("owin.Environment", owinEnvironment);
HttpContextWrapper contextWrapper = new HttpContextWrapper(httpContext);
ControllerContext controllerContext = new ControllerContext(contextWrapper, routeData, controller);
controller.ControllerContext = controllerContext;
controller.Url = new UrlHelper(new RequestContext(contextWrapper, routeData));
// We have not found out how to set up this UrlHelper so that we get a real callbackUrl in AccountController.ForgotPassword.
return controller;
}
I have not yet succeeded to get everything working (in particular, I could not get UrlHelper to produce a proper URL in the ForgotPassword method), but most of my needs are covered now.
I've used a solution similar to yours - mocking an IAuthenticationManager - but my login code is in a LoginManager class that takes the IAuthenticationManager via constructor injection.
public LoginHandler(HttpContextBase httpContext, IAuthenticationManager authManager)
{
_httpContext = httpContext;
_authManager = authManager;
}
I'm using Unity to register my dependencies:
public static void RegisterTypes(IUnityContainer container)
{
container.RegisterType<HttpContextBase>(
new InjectionFactory(_ => new HttpContextWrapper(HttpContext.Current)));
container.RegisterType<IOwinContext>(new InjectionFactory(c => c.Resolve<HttpContextBase>().GetOwinContext()));
container.RegisterType<IAuthenticationManager>(
new InjectionFactory(c => c.Resolve<IOwinContext>().Authentication));
container.RegisterType<ILoginHandler, LoginHandler>();
// Further registrations here...
}
However, I'd like to test my Unity registrations, and this has proved tricky without faking (a) HttpContext.Current (hard enough) and (b) GetOwinContext() - which, as you've found, is impossible to do directly.
I've found a solution in the form of Phil Haack's HttpSimulator and some manipulation of the HttpContext to create a basic Owin environment. So far I've found that setting a single dummy Owin variable is enough to make GetOwinContext() work, but YMMV.
public static class HttpSimulatorExtensions
{
public static void SimulateRequestAndOwinContext(this HttpSimulator simulator)
{
simulator.SimulateRequest();
Dictionary<string, object> owinEnvironment = new Dictionary<string, object>()
{
{"owin.RequestBody", null}
};
HttpContext.Current.Items.Add("owin.Environment", owinEnvironment);
}
}
[TestClass]
public class UnityConfigTests
{
[TestMethod]
public void RegisterTypes_RegistersAllDependenciesOfHomeController()
{
IUnityContainer container = UnityConfig.GetConfiguredContainer();
HomeController controller;
using (HttpSimulator simulator = new HttpSimulator())
{
simulator.SimulateRequestAndOwinContext();
controller = container.Resolve<HomeController>();
}
Assert.IsNotNull(controller);
}
}
HttpSimulator may be overkill if your SetFakeControllerContext() method does the job, but it looks like a useful tool for integration testing.

Categories