Updating user name and email - asp.net

In a Asp.net Core project, I´m using the code below to change user name and email. But the property "User" in the controller does not update, just after logoff.
obs.: This "User" property is already defined by the framework in controller bases class, and it is a "ClaimsPrincipal" type.
Controller
public async Task<IActionResult> Create(EditViewModel viewModel)
{
if (ModelState.IsValid)
{
var model = await _userManager.FindByIdAsync(CurrentUser.Id);
model.UserName = viewModel.UserName;
model.Email = viewModel.Email;
await _userManager.UpdateAsync(model);
//THIS ONE STILL HAS OLD VALUES ==============================
Console.WriteLine(_userManager.GetUserName(User));
//THIS ONE HAS NEWER VALUES ==================================
model = await _userManager.FindByIdAsync(CurrentUser.Id);
Console.WriteLine(model.UserName);
...
I´m using this "User" property in my views, to display user informations, like this:
View
#inject UserManager<ApplicationUser> UserManager
...
<p>Hello: #UserManager.GetUserName(User)</p>

This is likely because your current User is going to be stored in a fashion similar to a Claim (i.e. in memory) or Cookie to avoid frequent hits to the database and thus in order to pull the new values, you would need to log out and log back in to refresh these.
However, you could try following your code by calling the RefreshSignInAsync() method, which should handle this same behavior :
// Update your User
await _userManager.UpdateAsync(model);
// signInManager is an instance of SignInManager<ApplicationUser> and can be passed in via
// dependency injection
await signInManager.RefreshSignInAsync(existingUser);

Related

How do I create a ClaimsPrincipal in my Blazor/.NetCore "Session"?

Background: I have an old MVC app that I'm experimenting with migrating to a shiny new Blazor app. Blazor seems to tick a lot of boxes for me here. Wunderbar. For clarity this is the solution template in VS2022 where there's a WASM, a .Net Core host, and a shared project. I will have plenty of api calls that need to be secured as well as UI that will be affected by various authorization policies (eg show/hide admin features).
I have a table of users with an ID and hashed password.
I can't get Blazor to use its native authentication/authorization processes with my existing store.
My latest attempt was to create an AccountController on the server app (inherits ControllerBase) and put in a Login method that gets the username and password from a json body for the moment. I have successfully ported the old authentication mechanism and I have my user that I have verified the password for. I now want to use Claims and a ClaimsPrincipal to store some of the things about the user, nothing too complex.
How do I put my ClaimsPrincipal into the app such that the WASM UI can see it AND future calls to api controllers (or ControllerBase controllers) will see it?
I have found hundreds of examples that use built-in scaffolding that lets it use EF to create tables and things but I need to use my existing stores and I can't find anything that joins the dots on how to connect the WASM and the server side.
I have read about and implemented and around the place, and tried some #authorize around the place but my WASM just doesn't know about the authenticated user.
In my login controller I have attempted a bunch of different approaches:
I implemented a custom AuthenticationStateProvider, got it into the controller via DI, called the AuthenticationStateChanged() and for the lifecycle of that one controller call I can see my HttpContext.User gets the new identity. But the WASM doesn't, and if I hit the same method again the User is null again
I tried to implement a SignInManager. This never worked well and my reading suggests that it's not compatible
I discovered ControllerBase.SignIn() which hasn't helped either
HttpContext.SignInAsync() with Cookie authentication (because that was the example I found)
I tried setting HttpContext.User directly (and tried combining that one call with the AuthenticationStateProvider implementation simultaneously)
I tried creating a fresh solution from template to pick through it, but it would appear to be reliant on hacking up my EF DataContext. I just want to find how I tell the whole contraption "Here's a ClaimsPrincipal" and have that work in both the WASM and api controllers.
I'm also not excited to have a dependency on the Duende stuff - I don't see what it brings to the table. I don't really need a whole identity provider, I already have my own code for authorizing against the database I just need to get my very boring ClaimsPrincipal into my app.
Am I going at this all wrong? Has my many years of "old school" experience stopped me from seeing a modern way of doing this? Am I trying to force cool new stuff to behave like clunky old stuff? Yes I'd love to switch to Google/Facebook/Twitter/MS authorization but that's not an option, I have passwords in a database.
You need to build a custom AuthenticationHandler.
Here's the relevant bits of one of mine (see credits at bottom for where I lifted some of the code). You'll need to pick out the pieces from the code to make your work. Ask if you have any specific problems.
The custom AuthenticationHandler looks up your user in your database and if authenticated, builds a standard ClaimsPrincipal object and adds it to the security header. You can then use the standard Authorization and AuthenticationStateProvider.
public class AppAuthenticationHandler : AuthenticationHandler<AppAuthOptions>
{
private const string AuthorizationHeaderName = "Authorization";
private const string BasicSchemeName = "BlazrAuth";
//this is my custom identity database
private IIdentityService _identityService;
public AppAuthenticationHandler(IOptionsMonitor<AppAuthOptions> options, IIdentityService identityService, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
_identityService = identityService;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
await Task.Yield();
// Check the Headers and make sure we have a valid set
if (!Request.Headers.ContainsKey(AuthorizationHeaderName))
return AuthenticateResult.Fail("No Authorization Header detected");
if (!AuthenticationHeaderValue.TryParse(Request.Headers[AuthorizationHeaderName], out AuthenticationHeaderValue? headerValue))
return AuthenticateResult.Fail("No Authorization Header detected");
if (!BasicSchemeName.Equals(headerValue.Scheme, StringComparison.OrdinalIgnoreCase))
return AuthenticateResult.Fail("No Authorization Header detected");
if (headerValue is null || headerValue.Parameter is null)
return AuthenticateResult.Fail("No Token detected");
// Get the User Guid from the security token
var headerValueBytes = Convert.FromBase64String(headerValue.Parameter);
var userpasswordstring = Encoding.UTF8.GetString(headerValueBytes);
// This will give you a string like this "me#you.com:password"
if (youcantdecodethestring ))
return AuthenticateResult.Fail("Invalid Token submitted");
// Get the user data from your database
var principal = await this.GetUserAsync(userId);
if (principal is null)
return AuthenticateResult.Fail("User does not Exist");
// Create and return an AuthenticationTicket
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
// method to get the user from the database and retuen a ClaimsPrincipal
public async Task<ClaimsPrincipal?> GetUserAsync(Guid Id)
{
// Get the user object from the database
var result = await _identityService.GetIdentityAsync(Id);
// Construct a ClaimsPrincipal object if the have a valid user
if (result.Success && result.Identity is not null)
return new ClaimsPrincipal(result.Identity);
// No user so return null
return null;
}
}
You can construct a ClaimsIdentity like this:
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Sid, record.Id.ToString()),
new Claim(ClaimTypes.Name, record.Name),
new Claim(ClaimTypes.Role, record.Role)
}, "MyIdentityProvider");
public class AppAuthOptions : AuthenticationSchemeOptions
{
public string Realm = "BlazrAuth";
}
The service registration:
public static class AuthServicesCollection
{
public static void AddAppAuthServerServices(this IServiceCollection services)
{
services.AddAuthentication("BlazrAuth").AddScheme<AppAuthOptions, AppAuthenticationHandler>("BlazrAuth", null);
services.AddScoped<IIdentityService, IdentityService>();
}
}
Credits: Some of this code was derived from: https://harrison-technology.net/

When using Global Query Filters, tenant Id is null when I try to get roles to update the claims

I'm trying to set up multitenancy in the application that I'm working on. I added the Global Query Filters and implemented the TenantProvider as suggested here. Note this block of code in the example that I listed:
public interface ITenantProvider
{
Guid GetTenantId();
}
public class DummyTenantProvider : ITenantProvider
{
public Guid GetTenantId()
{
return Guid.Parse("069b57ab-6ec7-479c-b6d4-a61ba3001c86");
}
}
In my case, instead of the DummyTenantProvider, I have implemented the tenant provider that gets tenantId from the HttpContextAccessor.HttpContext.User.GetTenantId(). I understand that the type of HttpContextAccessor.HttpContext.User is of ClaimsPrincipal, so I added the additional method that accepts this ClaimsPrincipal as parameter and returns tenantId:
public static string GetTenantId(this ClaimsPrincipal principal)
{
return principal.FindFirstValue("tenant");
}
Also, I've implemented the Api Authentication with JWT token. In Startup.cs, I added the registration of the authentication like this:
services.AddAuthentication(options =>
{
// some code that is not relevant
}).AddJwtBearer(options =>
{
// some code that is not relevant
options.Events = new JwtBearerEvents
{
OnTokenValidated = async context =>
{
// here I get the needed service and from that service, I get the data that I need for Claims (Roles are needed)
var claims = new List<Claim>
{
new Claim(ClaimTypes.Role, JsonConvert.SerializeObject(roles)),
};
var appIdentity = new ClaimsIdentity(claims);
context.Principal.AddIdentity(appIdentity);
},
};
});
Now, the issue that I'm having is when I'm making an HTTP request that's targeting the method in the controller that gets roles for the user (the method is using the same service I'm using in the code above) when debugging the code, first the OnTokenValidated is called and the roles for the user should be populated and added to claims, and then the method in the controller is called. When the OnTokenValidated calls the service and when the request to the database is executed (simple "dbContext.Roles.ToListAsync()" in the repository), the global query filter is applied and tenantId should be added to that database request. But when the global filter is applied, tenantId is null in the GetTenantId method and the code throws ArgumentNullException error. What I can't figure out is why is the tenantId null. My assumption is that when the service is called from OnTokenValidated, that is not the part of the HTTP request, and then the HttpContextAccessor.HttpContext doesn't have the needed value, but I'm not sure if I'm right about that.
I would appreciate some additional insight into what I'm doing wrong here.
If anything else is needed to make things clearer, I'm happy to edit the question.
Thank you all.
For anyone in need of a solution, I was able to resolve this issue by adding the Tenant Id as a new claim in Startup.cs using the TokenValidatedContext (because I have that information in TokenValidatedContext at that moment). Looks something like this:
ctx.HttpContext.User.Identity?.AddClaim(New Claim('tenant'));
Later in the Tenant Provider I have access to this claim.

Dynamically adding steps to SSO with IdentityServer4

I`m facing some problems when trying to customize one of the quickstarts from identityServer4 QuickStart 9, basically, I need to create a single sign-on application that will be used by several services, multiple web applications, one electron, and PhoneGap app.
Currently, my flow is a bit more complicated than simply authenticating the user, see below:
User inputs login and password -> system validates this piece of data and presents the user with a selection of possible sub-applications to select -> the user selects one of the sub-applications -> the system now requests the user to select a possible environment for this application (staging/production can be customized)
I want to do this flow on the authentication layer because otherwise, I would have to replicate all these steps on all the apps, and off-course I want the authentication to have separate development lifecycle.
Currently, I'm trying to make 3 modifications to achieve this:
PersistentGrantStore -> save this steps to a custom table using the
grant key as a reference. (something like
Key/application/environment)
IProfileService -> add custom claims that represent this steps
(stuck here), and are temporary, they only have meaning for this token and subsequent refreshes.
authenticationHandler -> validate if the user went through all the
steps
I will also need to make a modification to the token endpoint to accept these 2 parameters via custom header due to my spa`s apps
my question boils down to: is there a better way to this? am I overcomplicating this?
sorry if this question is too basic, but I`m not used to doing this type of auth.
If i understand you correctly, following way might be helpful.
Create a temp cookie and display select page after user loggedin:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model, string button)
{
if (ModelState.IsValid)
{
var loginResult = .service.Check(model.Username, model.Password);
if (loginResult.IsSucceed)
{
await HttpContext.SignInAsync("TempCookies", loginResult.Principal);
var selectViewModel = new SelectViewModel();
model.ReturnUrl = model.ReturnUrl;
return View("SelectUserAndEnvironment", selectViewModel);
}
else
{
ModelState.AddModelError("", "****.");
return View(model);
}
}
return View(model);
}
Add claims you want and sign in for IdentityServerConstants.DefaultCookieAuthenticationScheme
[HttpPost]
[Authorize(AuthenticationSchemes = "TempCookies")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SelectUserAndEnvironment(SelectModel model)
{
// add claims from select input
var claims = new List<Claim>();
claims.Add(new Claim(<Type>, <Value>));
var p = new ClaimsPrincipal(new ClaimsIdentity(auth.Principal?.Identity, claims));
await HttpContext.SignOutAsync("TempCookies");
await HttpContext.SignInAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme, p);
return Redirect(model.ReturnUrl);
}
And use claims in ProfileService
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
// you can get claims added in login action by using context.Subject.Claims
// other stuff
context.IssuedClaims = claims;
await Task.CompletedTask;
}
Finally add authentication scheme in Startup.cs
services.AddAuthentication()
.AddCookie("TempCookies", options =>
{
options.ExpireTimeSpan = new TimeSpan(0, 0, 300);
})
If you want to use external login, change above code appropriately.

ASP.NET MVC Custom user fields on every page

Background:
I'm building more and more web applications where the designers / template makers decide that adding a "profile picture" and some other user-related data, of course only when someone is logged in.
As most ASP.NET MVC developers I use viewmodels to provide razor layouts with the information that I need shown, sourced from repositories et al.
It is easy to show a user name through using
HttpContext.Current.User.Identity.Name
What if I want to show information that's saved in my backing datastore on these pages? Custom fields in the ApplicationUser class like a business unit name or a profile picture CDN url.
(for sake of simplicity let's assume I use the Identity Framework with a Entity Framework (SQL database) containing my ApplicationUsers)
Question
How do you solve this:
Without poluting the viewmodel/controller tree (e.g. building a BaseViewModel or BaseController populating / providing this information?
Without having to roundtrip the database every page request for these details?
Without querying the database if a user is not logged in?
When you cannot use SESSION data (as my applications are often scaled on multiple Azure instances - read why this isn't possible here- I'm not interested in SQL caching or Redis caching.
I've thought about using partials that new their own viewmodel - but that would still roundtrip the SQL database every pageload. Session data would be safe for now, but when scaled up in azure this isn't a way either. Any idea what would be my best bet?
TLDR;
I want to show user profile information (ApplicationUser) on every page of my application if users are logged in (anon access = allowed). How do I show this info without querying the database every page request? How do I do this without the Session class? How do I do this without building base classes?
The best way with Identity is to use claims to store custom data about the user. Sam's answer pretty close to what I'm saying here. I'll elaborate a bit more.
On ApplicationUser class you have GenerateUserIdentityAsync method which used to create ClaimsIdentity of the user:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaims(new[]
{
new Claim("MyApp:FirstName",this.FirstName), //presuming FirstName is part of ApplicationUser class
new Claim("MyApp:LastName",this.LastName),
});
return userIdentity;
}
This adds key-value pairs on the user identity that is eventually serialised and encrypted in the authentication cookie - this is important to remember.
After user is logged in, this Identity are available to you through HttpContext.Current.User.Identity - that object is actually ClaimsIdentity with claims taken from the cookie. So whatever you have put into claims on login time are there for you, without having to dip into your database.
To get the data out of claims I usually do extension methods on IPrincipal
public static String GetFirstName(this IPrincipal principal)
{
var claimsPrincipal = principal as ClaimsPrincipal;
if (claimsPrincipal == null)
{
throw new DomainException("User is not authenticated");
}
var personNameClaim = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "MyApp:FirstName");
if (personNameClaim != null)
{
return personNameClaim.Value;
}
return String.Empty;
}
This way you can access your claims data from your Razor views: User.GetFirstName()
And this operation is really fast because it does not require any object resolutions from your DI container and does not query your database.
The only snag is when the values in the storage actually updated, values in claims in the auth cookie are not refreshed until user signs-out and signs-in. But you can force that yourself via IAuehtenticationManager.Signout() and immediately sign them back in with the updated claims values.
You could store your extra information as claims. In your log in method fill your data to generated identity. For example if you are using Identity's default configuration you could add your claims in ApplicationUser.GenerateUserIdentityAsync() method:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaims(new[]
{
new Claim("MyValueName1","value1"),
new Claim("MyValueName2","value2"),
new Claim("MyValueName2","value3"),
// and so on
});
return userIdentity;
}
And in your entire application you have access those information by reading current user claims. Actually HttpContext.Current.User.Identity.Name uses same approach.
public ActionResult MyAction()
{
// you have access the authenticated user's claims
// simply by casting User.Identity to ClaimsIdentity
var claims = ((ClaimsIdentity)User.Identity).Claims;
// or
var claims2 = ((ClaimsIdentity)HttpContext.Current.User.Identity).Claims;
}
I think the "how to" is a little subjective as there are probably many possible ways to go about this but I solved this exact problem by using the same pattern as HttpContext. I created a class called ApplicationContext with a static instance property that returns an instance using DI. (You could alter the property to generate a singleton itself as well if you aren't, for some reason, using DI.)
public interface IApplicationContext
{
//Interface
string GetUsername();
}
public class ApplicationContext : IApplicationContext
{
public static IApplicationContext Current
{
get
{
return DependencyResolver.Current.GetService<IApplicationContext>();
}
}
//appropriate functions to get required data
public string GetUsername() {
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
return HttpContext.Current.User.Identity.Name;
}
return null;
}
}
Then you just reference the "Current" property in your view directly.
#ApplicationContext.Current.GetUsername()
This would solve all of you requirements except #2. The database call may not add a significant enough overhead to warrant avoiding altogether but if you require it then your only option would be to implement some form of caching of the user data once it is queried the first time.
Simply implement ChildAction with caching and vary by loggedin user

Complex authentication with existing user database in MVC5

I'm migrating a SaaS app from Classic ASP to .NET MVC5 and will use EF6 Database First. The login form for end users is customisable by each tenant (on their own subdomain but pointing to the same web application). We wish to use the existing database schema and the new authentication & authorization filters.
For example, a user on one tenant may login by entering their first name, surname and a code generated by our system. A user on another tenant may login by entering their email address and a password. Additionally, each tenant has a separate administrator login which uses a username and password. Another tenant may use LDAP authentication against a remote AD server.
Is there a definitive best practice way of doing custom authentication?
Almost every article appears to suggest different ways of accomplishing this: simply setting FormsAuthentication.SetAuthCookie, using a custom OWIN provider, override AuthorizeAttribute, etc.
In Classic ASP, we queried the database to find out the type of login for that tenant, displayed the appropriate fields on the login screen and then on post back, checked the fields match what's in the database and then set the session variables appropriately which were checked on each page request.
Thanks
I find that Identity framework is very flexible in terms of authentication options. Have a look on this bit of authentication code:
var identity = await this.CreateIdentityAsync(applicationUser, DefaultAuthenticationTypes.ApplicationCookie);
authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
This is pretty standard run of the mill authentication part in Identity, you'll find this in every Identity sample on the web. If you look closely it is very flexible - all you need for authentication is ApplicationUser object that framework does not care how you get.
So in theory you can do things like this (pseudocode, I did not try to compile this):
// get user object from the database with whatever conditions you like
// this can be AuthCode which was pre-set on the user object in the db-table
// or some other property
var user = dbContext.Users.Where(u => u.Username == "BillyJoe" && u.Tenant == "ExpensiveClient" && u.AuthCode == "654")
// check user for null
// check if the password is correct - don't have to do that if you are doing
// super-custom auth.
var isCorrectPassword = await userManager.CheckPasswordAsync(user, "enteredPassword");
if (isCorrectPassword)
{
// password is correct, time to login
// this creates ClaimsIdentity object from the ApplicationUser object
var identity = await this.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
// now we can set claims on the identity. Claims are stored in cookie and available without
// querying database
identity.AddClaim(new Claim("MyApp:TenantName", "ExpensiveClient"));
identity.AddClaim(new Claim("MyApp:LoginType", "AuthCode"));
identity.AddClaim(new Claim("MyApp:CanViewProducts", "true"));
// this tells OWIN that it can set auth cookie when it is time to send
// a reply back to the client
authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
Using this authentication, you have set a few claims on the user - they are stored in the cookie and available everywhere via ClaimsPrincipal.Current.Claims. Claims are essentially a collection of key-value pairs of strings and you can store there anything you like.
I usually access claims from the user via extension method:
public static String GetTenantName(this ClaimsPrincipal principal)
{
var tenantClaim = principal.Claims.FirstOrDefault(c => c.Type == "MyApp:TenantName");
if (tenantClaim != null)
{
return tenantClaim.Value;
}
throw new ApplicationException("Tenant name is not set. Can not proceed");
}
public static String CanViewProducts(this ClaimsPrincipal principal)
{
var productClaim = principal.Claims.FirstOrDefault(c => c.Type == "MyApp:CanViewProducts");
if (productClaim == null)
{
return false;
}
return productClaim.Value == "true";
}
So in your controller/view/business layer you can always call to ClaimsPrincipal.Current.GetTenantName() and in this case you'd get "ExpensiveClient" back.
Or if you need to check if a specific feature is enabled for the user, you do
if(ClaimsPrincipal.Current.CanViewProducts())
{
// display products
}
It is up to you how you store your user properties, but as long as you set them as claims on the cookie, they will be available.
Alternatively you can add claims into the database for every user:
await userManager.AddClaimAsync(user.Id, new Claim("MyApp:TenantName", "ExpensiveClient"));
And this will persist the claim into the database. And by default, Identity framework adds this claim to the user when they login without you needing to add it manually.
But beware, you can't set too many claims on a cookie. Cookies have 4K limit set by browsers. And the way Identity cookie encryption works it increases encoded text by about 1.1, so you can have roughly 3.6K of text representing claims. I've run into this issue here
Update
To control access to controllers via claims you can use the following filter on the controller:
public class ClaimsAuthorizeAttribute : AuthorizeAttribute
{
public string Name { get; private set; }
public ClaimsAuthorizeAttribute(string name)
{
Name = name;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
var user = HttpContext.Current.User as ClaimsPrincipal;
if (user.HasClaim(Name, Name))
{
base.OnAuthorization(filterContext);
}
else
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary()
{
{"controller", "errors"},
{"action", "Unauthorised"}
});
}
}
}
and then use this attribute on controllers or separate actions like this:
[ClaimsAuthorize("Creating Something")]
public ActionResult CreateSomething()
{
return View();
}
User will require "Create Something" claim on them to access this action, otherwise they will be redirected to "Unauthenticated" page.
Recently I've played with claims authentication and made a prototype application similar to your requirement. Please have a look on the simple version: https://github.com/trailmax/ClaimsAuthorisation/tree/SimpleClaims where claims are stored individually for each user. Or there is more complex solution where claims belong to a role and when users login, role claims assigned to the user: https://github.com/trailmax/ClaimsAuthorisation/tree/master
There's two components you need. The authentication itself and the strategy each user gets for authentication.
The first is easy and is accomplished with these two lines...
var identity = await UserManager.CreateIdentityAsync(user,
DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties()
{ IsPersistent = isPersistent }, identity);
When a user is Signed In, they get an identity which contains the user's claims on roles and who they are. These are given to the user as a cookie. After this point you just decorate controllers with [Authorize] to make sure only authenticated users can log in. Pretty standard here.
The only complicated part in the problem is the second part; The strategy for how each user gets authenticated set by the admin.
Some pseudocode for how this could work in actions is this...
// GET: /Account/Login
[AllowAnonymous]
public ActionResult Login(int tenantId)
{
var tenant = DB.GetTenant(tenantId);
return View(tenant);
}
In your view you would output the authentication strategy for the tenant. That may be email and password, a code and email, or whatever your requirements.
When the user enters their info and clicks to login, you then have to determine what strategy they were using, and check to see if their information matches.
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model)
{
var tenant = DB.GetTenant(model.tenantId);
//If user info matches what is expected for the tenants strategy
if(AuthenticateUserInfo(tenant, model.UserInputs))
{
//Sign the user in
var identity = await UserManager.CreateIdentityAsync(user,
DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties()
{ IsPersistent = isPersistent }, identity);
}
}
I did a lot of hand-waving in the second part because of the complicated nature of how dynamic it is. Overall you should use the same strategies you used in your legacy application to generate the right inputs and such. Nothing has changed there, only the way you sign in is going to be different.
Using Visual Studio 2013 Update 3 you can create a new Web Application that comes with MVC5, EF6 and Identity already installed. Here is how to select Identity when you create a new Application:
With MVC Template selected, click Change Authentication and the highlighted window will pop up. Individual User Accounts = Identity. Click ok and continue.
Having done that, you have created an application with Identity. You can now customize your login and registration as follows.
You want to look at your AccountController.cs in the Controllers folder. Here you will find the script for Registration and Login.
If you look at the
public async Task<ActionResult> Register(RegisterViewModel model)
function, you'll notice it contains:
IdentityResult result = await UserManager.CreateAsync(new ApplicationUser() { UserName = newUser.UserName }, newUser.Password);
This is where the user gets created. If you want to use Identity, you should save the users username and password. You can use an e-mail as the username if you want. etc.
After doing that, I add the user a specified role (I find the user and then add it to the role):
ApplicationUser userIDN = UserManager.FindByName(newUser.UserName);
result = await UserManager.AddToRoleAsync(userIDN.Id, "Admin");
In my scenario, I have created an additional extended table where I hold their address, phone number, etc. In that table, you can hold any additional login information. You can add these new entries before or after creating the users account in Identity. I would create the extended information and then create the Identity account just to be sure.
IMPORTANT: For any scenarios where a user is logging in with something that is not a username or e-mail address that isn't saved into via Identity, you will have to do a custom solution.
Example: User types in their first name, surname and the code. You could do two things: Save the first name and surname into the username field of identity and the code into the password and verify the login that way
OR
you would check your custom table for those properties and make sure they match, if and when they do you could call this little beauty:
await SignInAsync(new ApplicationUser() { UserName = model.UserName }, isPersistent: false);
Once you call that SignInAsync function, you can go ahead and direct them to your protected page.
NOTE: I'm creating the ApplicationUser on the function call but if you use it more than once it would be ideal for you to declare the ApplicationUser as follows:
ApplicationUser user = new ApplicationUser() { UserName = model.UserName };
NOTE #2: If you don't want to user Async methods, those functions all have non-async versions of them.
Note #3: At the very top of any page using UserManagement, it is being declared. Make sure if you are creating your own controller that wasn't generated by Visual Studio to use Identity, you include the UserManagement declaration script at the top inside of the class:
namespace NameOfProject.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; }
Please let me know if you have any questions and I hope this helps.

Resources