Seeding data and creating/managing roles in MVC4 - how hard can it be? - asp.net-mvc-4

I'm about to go insane, so I'll try getting some help one more time...
I'm using Visual Studio Express 2012 for Web to create an internet project using C# + MVC4 + Razor + Entity Framework + ASP.NET 4.5.
I need to do the following: automatically create an "admin" user (who will be authorized all over the site) and then create some user roles.
I've read about SimpleMembership, roles and everything all over the web, but nothing seems to give me a straightforward method to make the whole thing work.
This is what I've done so far:
1- Created a DataContext class:
public class DataContext : DbContext
{
public DataContext()
: base("DefaultConnection")
{
}
public DbSet<UserProfile> UserProfiles { get; set; }
}
2- Created an initializer class with what I assume would get me the admin user and Admins role created:
public class DataContextDbInitializer : DropCreateDatabaseAlways<DataContext>
{
protected override void Seed(DataContext context)
{
var roles = (Webmatrix.WebData.SimpleRoleProvider)System.Web.Security.Roles.Provider;
var membership = (Webmatrix.WebData.SimpleMembershipProvider)System.Web.Security.Membership.Provider;
if (!roles.RoleExists("Admins")) {
roles.CreateRole("Admins");
}
if (membership.GetUser("admin", false) == null) {
membership.CreateUserAndAccount("admin", "123456");
}
if (!roles.GetRolesForUser("admin").Contains("Admins")) {
roles.AddUsersToRoles(new[] { "admin" }, new[] { "Admins" });
}
}
}
(I've tried inheriting from DropCreateDatabaseIfModelChanges but that doesn't help).
3- Added two lines to the App_Start method in Global.asax:
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);
Database.SetInitializer<DataContext>(new DataContextDbInitializer());
I also tried using WebSecurity.InitializeDatabaseConnection in the Seed method in my DataContextDbInitializer class, but didn't work.
4- Removed the [InitializeSimpleMembership] annotation from the AccountController, so I can initialize it from the very beginning of the application life cycle (using WebSecurity.InitializeDatabaseConnection in App_Start, as I've explained in number 3). I tried adding [InitializeSimpleMembership] on top of the Index method in the HomeController and removing WebSecurity.InitializeDatabaseConnection from App_Start, but that doesn't help either.
5- In Web.config I have authentication method as Forms (default) and also left the default connection string:
<connectionStrings>
<add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=aspnet-TPFinal-20130121210447;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\aspnet-TPFinal-20130121210447.mdf" providerName="System.Data.SqlClient" />
</connectionStrings>
And added this inside the system.web tag:
<roleManager enabled="true" cacheRolesInCookie="true" />
6- Then, to test if everything works, I use the [Authorize(Roles = "Admins")] annotation on top of the About() method in the HomeController. If things were working as expected, this should force me to log in as admin/123456 in order to be able to see the "About" page, controlled by HomeController/About.
7- I haven't added EF migrations or anything else. All the other stuff is by default as VS2012 automatically created it.
So I run my application and click on the "About" link, and I get presented with the login form (that means that the [Authorize(Roles = "Admins")] annotation is doing what it's expected to do). I attempt to log in as admin/123456 but the log in page is reloaded over and over, every time I click on the log in button.
A few things I have noticed:
-if I add a breakpoint in the Seed method, it seems it's not getting called.
-when I use DropCreateDatabaseAlways and run the application again, I'm able to log in as admin/123456, which makes me think again that my whole DataContextDbInitializer class is not even being used, since I assume the DB should be created from scratch, which would delete the admin user.
I don't know what else to read, what else to try... I'm new to asp.net (needed I say that?) and just going nuts.
Thanks!!

Finally, I managed to make the whole thing work!
My seed method now looks like this:
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);
if (!Roles.RoleExists("Admins"))
{
Roles.CreateRole("Admins");
}
if (!WebSecurity.UserExists("admin"))
{
WebSecurity.CreateUserAndAccount("admin", "123456");
}
if (!Roles.GetRolesForUser("admin").Contains("Admins"))
{
Roles.AddUsersToRoles(new[] { "admin" }, new[] { "Admins" });
}
base.Seed(context);
My App_Start in Global.asax looks like this:
Database.SetInitializer(new DataContextDbInitializer());
DataContext c = new DataContext();
c.Database.Initialize(true);
I'm not sure if this is actually doing something, but I have this inside the the system.web tag in Web.config:
<roleManager enabled="true" cacheRolesInCookie="true" />
I also removed [InitializeSimpleMembership] from AccountController, and the UsersContext class in AccountModels. I moved this bit to my own context class:
public DbSet<UserProfile> UserProfiles { get; set; }
Then, to test if everything works, I use the [Authorize(Roles = "Admins")] annotation on top of the About() method in the HomeController. If things are working as expected, this should force me to log in as admin/123456 in order to be able to see the "About" page, controlled by HomeController/About. Which does ;)
Thanks for the help! It contributed to me understanding a bit more what was going on.

In MVC4 a default Internet Application has Authentication built-in. A class called InitializeSimpleMembershipAttribute is located in a Filters directory. As the name suggests this class Initializes the Simple Membership Database. If you look at the constructor you'll see the following line:
WebSecurity.InitializeDatabaseConnection("UserContext", "UserProfile", "UserId", "UserName", autoCreateTables: true);
Below this line you can insert the following code to create a default user:
// Create admin user.
if (!WebSecurity.UserExists("admin"))
{
WebSecurity.CreateUserAndAccount("admin", "12345678!");
}
Just another way to do things.

Try like this:
public class DataContextDbInitializer : DropCreateDatabaseAlways<DataContext>
{
protected override void Seed(DataContext context)
{
if (!Roles.RoleExists("Admins"))
{
Roles.CreateRole("Admins");
}
if (!WebSecurity.UserExists("admin"))
{
WebSecurity.CreateUserAndAccount("admin", "123456");
}
if (!Roles.GetRolesForUser("admin").Contains("Admins"))
{
Roles.AddUsersToRoles(new[] { "admin" }, new[] { "Admins" });
}
}
}
and in your Application_Start:
Database.SetInitializer<DataContext>(new DataContextDbInitializer());
using (var ctx = new DataContext())
{
ctx.Database.Initialize(true);
}
WebSecurity.InitializeDatabaseConnection(
"DefaultConnection",
"UserProfile",
"UserId",
"UserName",
autoCreateTables: true
);

Related

Startup.vb DonĀ“t recognize StarttupAuth.vb Class [duplicate]

I'm getting an error when I'm attempting to run my page says that,
The name 'ConfigureAuth' does not exist in the current context
in my Stratup Class. I'm sure all AspNet Identity libraries are installed. What do I need to do next, to try to fix this?
using Microsoft.Owin;
using Owin;
[assembly: OwinStartupAttribute(typeof(project_name.Startup))]
namespace project_name
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
}
}
If you are using default Visual Studio project template, the ConfigureAuth method could be found in partial class Startup.Auth.cs. So make sure you didn't break anything when modifying project structure.
This is an example of ConfigureAuth method:
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/api/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
I had similar issue, To fix the issue I removed .App_Start from namespace in Startup.Auth.cs file. After that I was able to see the reference.
It is either:
[assembly: **OwinStartup**(typeof(Project-Name.Startup))]
namespace project-name
{
public partial class Startup
{
public void **Configuration**(IAppBuilder app)
{
OR
[assembly: **OwinStartupAttribute**(typeof(Project-Name.Startup))]
namespace project-name
{
public partial class Startup
{
public void **ConfigureAuth**(IAppBuilder app)
{
Either rename OwinStartupAttribute to OwinStartup
OR Configuration to ConfigureAuth
Kindly I note that the two partial classes (Startup.Auth.cs and Startup.cs) should be in the same namespace which is the root of the project, just change the namespace of Startup.Auth.cs to the same namespace of the Startup.cs
Make sure when you originally create the project that there are no spaces in the name.
e.g. my app was called "DevOps Test" which was giving me errors when I ran it.
I recreated it as "DevopsTest" and no longer had these issues
namespace PAYOnline.App_Start
delete App_Start only namespace PAYOnline => It's welldone

Restrict account registration to only Admin users with asp.net identity authentication

I am creating a Blazor server app that requires authenticated users in order to prevent external access, and I would like to limit the ability to register new accounts to be only available to Administrator users to prevent unwanted accounts from being created.
I'm using Identity user accounts, scaffolded out for Blazor. Solutions like this at least disable the registration, but from there I need to be able to enable it again for administrative users. I attempted to recreate the register page as a Blazor component, however, using the generated RegisterModel did not seem to work for me.
Upon a large amount of searching - the answer ended up being relatively simple. Muhammad Hammad Maroof's solution although technically correct, confused me and was mostly unhelpful for working with the register page specifically.
As I am using Role-Based Authentication scaffolded out from Blazor - in a seperate razor page I use this code to set up roles:
#code {
protected override async Task OnParametersSetAsync()
{
await SetUpAuth();
}
private async Task SetUpAuth()
{
const string Manager = "Manager";
string[] roles = { Manager };
foreach (var role in roles)
{
var roleExist = await roleManager.RoleExistsAsync(role);
if (!roleExist)
{
await roleManager.CreateAsync(new IdentityRole(role));
}
}
var user = await userManager.FindByEmailAsync(config.GetValue<string>("AdminUser"));
if (user != null)
{
await userManager.AddToRoleAsync(user, Manager);
}
}
}
Allowing the appropriate user to be marked as an administrator. This page has the [AllowAnonymous] tag on it in order to allow the administrative user as dictated by "AdminUser": "SomeEmail#test.com", in the appsettings.json page to be able to access the site on initial setup.
Preventing access to the Blazor site itself from anonymous users was as simple as adding this line to ConfigureServices in the startup class (Code taken from Microsoft Docs)
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
From this, allowing access to the register page was significantly easier than I had initially thought (likely due to my lack of .net experience). To do so, all you have to do is locate the Register.cshtml.cs page (I couldn't initially find the controller method Muhammad had mentioned) which I did by using visual studio to right click on the Register Model and then go to definition. This should take you to the Register.cshtml.cs page with the RegisterModel class. In order to restrict access to this page for only a specific role of users, all you have to do is change the [AllowAnonymous] tag above the class to look similar to this:
[Authorize(Roles ="Manager")]
public class RegisterModel : PageModel
It's important to note that the same technique used to secure the register page could be used to secure any of the of the other scaffolded Identity pages. For applications where you may have more than a few roles, the method provided by Muhammad of using policy based authorization may be the way to go, and this link he provided is a great tutorial for setting up and using that form of authentication.
//FORCE autentication for all RAZOR PAGES except [AllowAnonymous]
services.AddControllers(config => {
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
Only adding this code to my startup.cs solved my problem.
Here's how I am doing it in asp.net core mvc app
C# Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy(ADMIN_ACCESS, policy => policy.RequireRole($"{UserType.Admin}"));
});
}
[Authorize("AdminAccess")]
public class AdminController : Controller
{
//Some action methods here
}

Back button issue in MVC 6 after login and logout

I am trying to restrict user to click back button after Login/Logout into the application.
If the user is logged in into the application, then after clicking on back button Login view should not be displayed and if user is logged out from the application then clicking on back button LogOff view should not be displayed.
I have already used caching technique which is given in number of links, but seems its not working.
This CacheAfterLogout link and done exactly as it is, still problem is not solved. I am using asp.net identity framework.
Could someone help on this ?
To disable caching you can apply the following to each controller you wish to disable caching on
[ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]
You can also setup named cache profiles and configure the settings at runtime.
Cache can be disabled by applying the [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] on each controller for which you wish to disable caching.
This can be applied to all controllers globally by adding your custom filter in startup.cs while configuring MVC services with ASP.Net Core 1.0.-* and MVC6.
First create a custom cache filter implementing the ResponseCacheAttribute as follows
public class ResponseCacheFilter : ResponseCacheAttribute
{
public ResponseCacheFilter() : base()
{
Location = ResponseCacheLocation.None;
NoStore = true;
}
}
This should be added in startup.cs file as follows.
public void ConfigureServices(IServiceCollection services)
{
// Add framework and custom services.
services.AddMvc(config =>
{
config.Filters.Add(new ResponseCacheFilter());
});
}
See the configuration of ResponseCache at https://docs.asp.net/en/latest/performance/caching/response.html#responsecache-attribute
Just Add This Code in Global.asax.cs
protected void Application_BeginRequest()
{
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetExpires(DateTime.UtcNow.AddHours(-1));
Response.Cache.SetNoStore();
}
Just add these lines to your Global.asax.cs file.
protected void Application_BeginRequest()
{
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetExpires(DateTime.UtcNow.AddHours(-1));
Response.Cache.SetNoStore();
}

user isn't authenticated in custom authorize attribute

I've made my own authorize attribute, and this is what it looks like
public class RedirectAuthorize : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result = new RedirectToRouteResult(new
RouteValueDictionary(new { controller = "NotExist" }));
}
}
}
So if the user isn't authenticated, I want them to get redirected to the NotExist controller. I've debugged and it seems that unauthorized users get in the if clause, which is correct. But I've also tried doing this with logged in users, and they get in the if clause as well which is wrong.
I dont understand why this is happening. It makes me hesitate about whether my log-in didnt work. Is this the right way of logging a user in?
FormsAuthentication.SetAuthCookie(acc.username, false);
I've never made a log-in system in asp.net mvc before, so please tell me what I'm doing wrong.
Edit:
It seems that the default [Authorized] attribute isn't working either... I really think the problem lays in the log in:
[HttpPost]
public ActionResult Login(User acc)
{
if(ModelState.IsValid)
{
if (Validate(acc.username, acc.password))
{
FormsAuthentication.SetAuthCookie(acc.username, false);
return RedirectToAction("Index", "System");
}
}
ModelState.AddModelError("IncorrectDetails", "Wrong details. Please try again.");
return View(acc);
}
The custom authorize attribute looks correct.
Since you are setting the cookie yourself I would guess you are not using the built-in membership provider.
If you set the cookie yourself, you also need to read the auth cookie and set the Identity and Principal objects on each request. Otherwise, HttpContext.User.Identity.IsAuthenticated will always be false, which seems to be what you are experiencing.

Adding forms authentication (SimpleMembership) to webapi app

I'm trying to add to a MVC4 webapi project the simple membership provider authentication mechanism found in a MVC 4 web application project, for a hybrid application serving its pages with a rich JS content, which uses AJAX calls to webapi actions to perform its tasks. I need the app users to authenticate before they can work with the apps provided in these pages, so I think I'll be fine with the forms authentication. I thus need to add it to the existing WebApi project and let my authorized-only actions return a 302 (redirect user to login page) rather than a 401.
Anyway, I'm missing something because as soon as I try to use a WebSecurity method I get the following exception:
System.InvalidOperationException was caught
Message=To call this method, the "Membership.Provider" property must be an instance of "ExtendedMembershipProvider".
Source=WebMatrix.WebData
Could anyone suggest a fix? Here are the steps I took for adding authorization:
1) Web.config: add to system.web:
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>
Add to appsettings (the 2nd entry is for replacing 401 with 302):
<add key="enableSimpleMembership" value="true"/>
<add key="webapi:EnableSuppressRedirect" value="false" />
Also remove profile, membership and rolemanager sections from the original template (they are not intended to be used with simple membership).
2) add NuGet packages for OpenAuth (DotNetOpenAuth Core, DotNetOpenAuth ext for ASP.NET, DotNetOpenAuth 1.0(a) consumer, DotNetOpenAuth 1.0(a), DotNetOpenAuth OpenID Core, DotNetOpenAuth OpenID Relying Party).
3) add InitializeSimpleMembership.cs to Filters (the code is pretty standard, see below).
4) copy from an MVC web app project the models in AccountModels.cs, all the views in Views/Account, and the AccountController.cs.
The InitializeSimpleMembership code is here:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute
{
private static SimpleMembershipInitializer _initializer;
private static object _initializerLock = new object();
private static bool _isInitialized;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
}
private class SimpleMembershipInitializer
{
private static void SeedData()
{
// seed data: users and roles
if (!WebSecurity.UserExists("TheAdminGuyName"))
WebSecurity.CreateUserAndAccount("TheAdminGuyName", "password");
if (!Roles.RoleExists("administrator")) Roles.CreateRole("administrator");
if (!Roles.IsUserInRole("TheAdminGuyName", "administrator"))
Roles.AddUserToRole("TheAdminGuyName", "administrator");
}
public SimpleMembershipInitializer()
{
Database.SetInitializer<UsersContext>(null);
try
{
using (var context = new UsersContext())
{
if (!context.Database.Exists())
{
((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
}
}
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "User", "UserId", "UserName", autoCreateTables: true);
SeedData();
}
catch (Exception ex)
{
throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);
}
}
}
}
This might be relevant, as it mentions your error specifically:
http://weblogs.asp.net/jgalloway/archive/2012/08/29/simplemembership-membership-providers-universal-providers-and-the-new-asp-net-4-5-web-forms-and-asp-net-mvc-4-templates.aspx