Controller not working after publish to azure website - asp.net-mvc-4

I have created a simple web site using ASP.Net MVC 4, Internet Application. I added a controller, model and associated views for a new page. Everything works fine running locally, I can register, login and use my new page functionality. After publishing to Azure website I can successfully use the home, about, register, login all of the built in functionality. However I cannot navigate to my new content I added, it gives an error 500. Again all of this functionality does work locally. I am very new to ASP.Net MVC and could use any help I can get.
RouteConfig
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Index action of my added controller
private UsersContext uc = new UsersContext();
private UserProfile user = new UserProfile();
private Character character = new Character();
//
// GET: /Character/
public ActionResult Index()
{
user = uc.UserProfiles.Single(u => u.UserName == User.Identity.Name);
return View(uc.Characters.ToList().Where(c => c.AccountOwnerId == user.UserId));
}
Let me know if any other items would help. Oh I put this into my UsersContext instead of in a newly created DbContext.
public UsersContext()
: base("DefaultConnection")
{
}
public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<Character> Characters { get; set; }
EDIT: one thing i forgot to mention is that when i published, it did not add the email address field to userprofile or my entire Character table. I had to run SQL on the Azure manage portal to manually add those items.

After all the frustration of trying to understand why this wasn't working I decided to start over. I deleted the website from Azure as well as the SQL DB. I then added a new website and SQL DB then grabbed the new publish settings file. I imported that into VS and Published. It all worked with a fresh install, not sure what I botched the first time around but it is working as intended now. Thanks,

Related

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
}

ASP.Net Core Authentication Providers

I created an ASP.Net Core 2.0 MVC using authentication providers as described here: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/
On localhost (i.e. when run via Visual Studio 2017) all works well. However, after deploying to Azure I found that the login providers stopped working (despite my setting up appropriate callback URIs; e.g. for Google I have https://localhost:44357/signin-google but also https://mysite.azurewebsites.net/signin-google, https://example.com/signin-google, and https://www.example.com/signin-google (as well as having setup the example.com domain and its www subdomain in Azure and configured SSL covering these domains). For Twitter I've changed the setup to the www subdomain only (as only 1 callback URL's allowed), and for LinkedIn I only have the domain and subdomain (i.e. I had to remove localhost; as LinkedIn only allows callback URI's under a single domain). I've also configured those keys/values which had been in my secrets.json under the Azure App Service's Application Settings.
Symptoms
On first login (aka registration), the user clicks the relevant provider's button after which new user entry appears in the AspNetUsers and AspNetUserLogins tables, and the user is directed to the page where they can associate their email. However, they're not logged in at that point; just registered. Subsequent attempts take them back to the email registration form; only clicking the Register button then returns an error message stating that the email's already registered (which is correct); but the user's still not signed in to the site.
I have the same issue with all providers; though after proving this focussed most of my ongoing on Google, just to limit the number of changing variables.
The only significant change I've made from the example was to refactor code in Startup.cs so that each provider's encapsulated in it's own method; so ConfigureServices contains:
ConfigureServicesAuthFacebook(services);
ConfigureServicesAuthGoogle(services);
ConfigureServicesAuthTwitter(services);
ConfigureServicesAuthMicrosoft(services);
ConfigureServicesAuthLinkedIn(services);
... and those methods look like this:
#region Authentication Providers
public void ConfigureServicesAuthFacebook(IServiceCollection services)
{
services.AddAuthentication().AddFacebook(x =>
{
x.AppId = Configuration["Authentication:Facebook:Id"];
x.AppSecret = Configuration["Authentication:Facebook:Secret"];
});
}
public void ConfigureServicesAuthGoogle(IServiceCollection services)
{
services.AddAuthentication().AddGoogle(x =>
{
x.ClientId = Configuration["Authentication:Google:Id"];
x.ClientSecret = Configuration["Authentication:Google:Secret"];
});
}
public void ConfigureServicesAuthTwitter(IServiceCollection services)
{
services.AddAuthentication().AddTwitter(x =>
{
x.ConsumerKey = Configuration["Authentication:Twitter:Id"];
x.ConsumerSecret = Configuration["Authentication:Twitter:Secret"];
});
}
public void ConfigureServicesAuthMicrosoft(IServiceCollection services)
{
services.AddAuthentication().AddMicrosoftAccount(x =>
{
x.ClientId = Configuration["Authentication:Microsoft:Id"];
x.ClientSecret = Configuration["Authentication:Microsoft:Secret"];
});
}
public void ConfigureServicesAuthLinkedIn(IServiceCollection services)
{
services.AddAuthentication().AddOAuth("LinkedIn", x =>
{
x.ClientId = Configuration["Authentication:LinkedIn:Id"];
x.ClientSecret = Configuration["Authentication:LinkedIn:Secret"];
x.CallbackPath = new PathString("/signin-linkedin");
x.AuthorizationEndpoint = "https://www.linkedin.com/oauth/v2/authorization";
x.TokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken";
x.UserInformationEndpoint = "https://api.linkedin.com/v1/people/~:(id,formatted-name,email-address,picture-url)";
//x.Scope = { "r_basicprofile", "r_emailaddress" };
});
}
#endregion Authentication Providers
Question
How can I debug this issue given I cannot recreate the problem on localhost. Any hints on what the issue may be?
The way it works is your user first must assoicate their Google account with the user on your system. It sounds like this is working for you.
After that is done your code should preform some kind of ExternalLoginSignInAsync however this kind of depends on how you have your system set up.
Out of the box, where IsNotAllowed is true this means the email or phone number associated with the account which needs to be confirmed has not yet been confirmed. See ASN.NET Core 2.0 Facebook authentication ExternalLoginSignInAsync Fails (IsNotAllowed)
Take a look at the AccountController method ExternalLoginConfirmation and you'll see:
var user = new ApplicationUser(model.Email) { Email = model.Email };
Assuming you're happy for those signing up with existing logon providers, amend this to:
var user = new ApplicationUser(model.Email) { Email = model.Email, EmailConfirmed = true };

Piranha CMS Management Custom Controller Not Found

I am having an issue with adding a custom controller to my Piranha CMS.
I have set up a new site and installed from the template and all the base functionality is working well.
I have added the menu to the manager section using the following code from the documentation:
Manager.Menu.Add(new Manager.MenuGroup()
{
InternalId = "MEProducts",
Name = "Products"
});
Manager.Menu.Where(m => m.InternalId == "MEProducts").Single().Items =
new List<Manager.MenuItem>() {
new Manager.MenuItem() {
Name = "Products",
Action = "productlist",
Controller = "products",
Permission = "ADMIN",
SelectedActions = "productlist,productedit"
},
new Manager.MenuItem() {
Name = "Product groups",
Action = "productgrouplist",
Controller = "products",
Permission = "ADMIN",
SelectedActions = "productgrouplist,productgroupedit"
}
};
This menu displays in the manager interface fine, the problem is when I click on the menu item the controller path can not be found.
The controller is class is in Areas/Manager/Controllers/ProductsController.cs and the code is below
namespace MyApp.Areas.Manager.Controllers
{
public class ProductsController : ManagerController
{
//
// GET: /Manager/Products/
public ActionResult Index()
{
return View();
}
public ActionResult ProductList()
{
return View();
}
public ActionResult ProductEdit(string id = "")
{
return View();
}
}
}
There are view files for ProductList and ProductEdit in Areas/Manager/Views/Products/
My web config contains the following line that I believe I need
<add key="manager_namespaces" value="MyApp.Areas.Manager.Controllers" />
When I click on the Products link in the manager I get
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /MyApp/manager/products/productlist
The page /MyApp/manager/page displays fine for the default configuration.
I am sure that I have missed something, or done something incorrect somewhere I'm just not sure where it is.
I've tried reproduce your issues but it works perfectly with your productscontroller in my project. I've zipped my test-project and uploaded it to my dropbox so you can download and compare it to your project:
EDIT
Removed download link as author downloaded the file
Please let me know when you've downloaded the zip-file so I can delete it.
Regards
HÃ¥kan

URL rewrite in ASP.NET 4.5 and Web API

We've got a long-running ASP.NET web-forms application that was born in the .NET 1.1/IIS6 days. We're now on .NET4.5/IIS7 but we've done nothing with MVC.
We provide a catalog to customers and give them a URL they can use:
www.ourhost.com/customername
Using a custom IHttpModule we developed we pull 'customername' out of the URL to find the customer in the database. That customer's ID is then stored in the page's context* and used by virtually all the pages on the site to customize content for that customer. After this process, the above URL would be rewritten and processed as
www.ourhost.com/index.aspx
with index.aspx having access to the customer's ID via its context and it can do its thing.
This works great and we support several thousand customers with it. the rewriting logic is fairly complex because it validates customer accounts, redirects to a 'uh oh' page if the customer is invalid and to a different 'find a dealer' page if the customer has not paid, etc. etc.
Now I'd like to build some Web API controllers and MVC-style rewriting has me worried. I see many examples where rewriting happens to make URL's like this work:
www.ourhost.com/api/{controller}
but I still need these web api 'calls' to happen in the context of a customer. Our pages are getting more sophisticated with JSON/AJAX async calls but in answering those calls I still need customer context. I would like the URL's to be
www.ourhost.com/customername/api/{controller}
But I am stumped as to how to configure routing to do this and have it play nicely with our IHttpModule.
Is this even possible?
*UPDATE: When I say 'stored in the page context' I mean the HttpContext associated with each web request that includes a dictionary where I can store some page/request-specific data.
There are two parts of the answer to your issue that I can see.
Maintaining the User Info across multiple requests
Generally an MVC API application will be stateless, that is you do not retain the current users session state between requests. Well that is what I have learned or been preached many times when writing RESTFul APIs.
That been said, you can enable session state in MVC Web API by adding the following to your global.asax.cs
protected void Application_PostAuthorizeRequest()
{
// To enable session state in the WebAPI.
System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}
Authorising A Customer in the Request
As you have shown in the Request URL you could add the customer name, then capture that and pass it to the same routine that your current http module calls to authorise on request. You could do this with an MVC Filter.
First do a similar URL Pattern to capture your customers name in the WebApiConfig.cs, something like so;
config.Routes.MapHttpRoute(
name: "WithCustomerApi",
routeTemplate: "api/{customername}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Then add an ActionFilter to your API Controller which processes each request, checks current session info and if needed calls your authorise/customer lookup code and then saves to session state for later use. Or if no good info from customer can send to a new MVC route
So you will add an attribute something like so;
[WebApiAuthentication]
public class BaseApiController : ApiController
{
}
Then create an action filter that might look like this (note I have not tested this, just done for a pattern of how to).
public class WebApiAuthenticationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var routeData = actionContext.ControllerContext.Request.GetRouteData();
var currentContext = HttpContext.Current;
if (routeData.Route.RouteTemplate.Contains("customername"))
{
try
{
var authenticated = currentContext.Request.IsAuthenticated;
if (!authenticated)
{
var customer = routeData.Values["customername"];
// do something with customer here and then put into session or cache
currentContext.Session.Add("CustomerName", customer);
}
}
catch (Exception exception)
{
var error = exception.Message;
// We dont like the request
actionContext.Response = new HttpResponseMessage(HttpStatusCode.BadRequest);
}
}
else
{
// No customer name specified, send bad request, not found, what have you ... you *could* potentially redirect but we are in API so it probably a service request rather than a user
actionContext.Response = new HttpResponseMessage(HttpStatusCode.NotFound);
}
}
}
If you create a new MVC 5 Web API Application and add in these extras and put the filter on the default values controller like so you should be able to see this running as demo of a possible solution.
This will echo the customer name back if all works ok.
[WebApiAuthentication]
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
var session = HttpContext.Current.Session;
if (session != null)
{
return new string[] {"session is present", "customer is", session["CustomerName"].ToString()};
}
return new string[] { "value1", "value2" };
}
}
I offer this up as a possible solution as I say, there are religious arguments about storing session and authorising in an API but those are not the question.
Hope that helps,
Steve

Seeding data and creating/managing roles in MVC4 - how hard can it be?

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
);