RegisterRoutes in ASP.NET MVC 4 - asp.net-mvc-routing

I have two areas Admin and SecurityGuard. Routing shown below. Displays error:
System.ArgumentException: The route with the name "Admin_default" is already in the family route. Names must be unique routes.
Parameter name: name
RouteConfig:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Main", action = "Index", id = UrlParameter.Optional }
);
AreaRegistration.RegisterAllAreas();
}
Global.asax:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
}
AdminAreaRegistration:
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new { controller = "Slider|Product" },
namespaces: new[] { "CaterWebsite.Areas.Admin.Controllers" }
);
}
SecurityGuardAreaRegistration:
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute("SearchMembership", "SecurityGuard/Membership/index/{searchterm}/{filterby}",
new { controller = "Membership", action = "Index", searchterm = UrlParameter.Optional, filterby = "all" }
);
context.MapRoute("Membership", "SecurityGuard/Membership/{action}/{userName}",
new { controller = "Membership", userName = UrlParameter.Optional }
);
context.MapRoute(
"SecurityGuard_default",
"SecurityGuard/{controller}/{action}/{id}",
new { controller = "Dashboard", action = "Index", id = UrlParameter.Optional }
);
}

Remove AreaRegistration.RegisterAllAreas(); from your RegisterRoutes method. You are calling it twice.
You have it specified in the right place in your Application_Start method.

Related

Authorization in .Net Framework 4.8 returns Unauthorized using OpenIdentity4

I'm trying to use Authorization in .Net Framework 4.8, but making a get request returns Unauthorized using OpenIdentity4
I have to use Framework and not Core!
This is my Startup.cs:
public void Configuration(IAppBuilder app)
{
var authority = "https://localhost:5001";
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
authority + "/.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever(),
new HttpDocumentRetriever());
var discoveryDocument = Task.Run(() => configurationManager.GetConfigurationAsync()).GetAwaiter().GetResult();
System.Console.WriteLine(discoveryDocument.AuthorizationEndpoint);
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
TokenValidationParameters = new TokenValidationParameters()
{
ValidateAudience = false,
}
}) ;
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "RestAPI",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Formatters.Remove(config.Formatters.XmlFormatter);
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.JsonFormatter.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;
app.UseWebApi(config);
}
}
}
OpenIdentity4 is running on https://localhost:5001
It's Startup:
public class Startup
{
public IWebHostEnvironment Environment { get; }
public Startup(IWebHostEnvironment environment)
{
Environment = environment;
}
public void ConfigureServices(IServiceCollection services)
{
var builder = services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients);
services.AddControllers();
services.AddAuthorization(options =>
{
options.AddPolicy("ApiScope", policy => {
policy.RequireAuthenticatedUser();
policy.RequireClaim("RestAPI", "APIRest");
});
});
}
public void Configure(IApplicationBuilder app)
{
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// uncomment if you want to add MVC
//app.UseStaticFiles();
//app.UseRouting();
app.UseIdentityServer();
app.UseRouting();
// uncomment, if you want to add MVC
app.UseAuthorization();
//app.UseEndpoints(endpoints =>
//{
// endpoints.MapDefaultControllerRoute();
//});
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers()
.RequireAuthorization("ApiScope");
});
}
}
}
It's probably a stupid mistake, but I can't figure it out.

Identity Server 4 JWT "access_token" is not valid for Identity Server Authorized endpoints

I have an Identity Server & API apps with ResourceOwnerPasswordAndClientCredentials flow.
After getting "access_token" from RequestResourceOwnerPasswordAsync method I can use my [Authorized] endpoints in my API project, but can't access [Authorized] endpoints in my Identity Server app.
May be I'm missing something in AllowedScopes?
Identity Server's Starup.cs:
public class Startup
{
public Startup(IHostingEnvironment env, ILoggerFactory loggerFactory)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
builder.AddEnvironmentVariables();
Configuration = builder.Build();
var section = Configuration.GetSection("Logging");
loggerFactory.AddConsole(section);
loggerFactory.AddDebug();
loggerFactory.AddProvider(new FileLoggerProvider());
}
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<IdentityDataContext>(optionsAction =>
optionsAction.UseSqlServer(Configuration.GetConnectionString("DevelopersConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<IdentityDataContext>()
.AddDefaultTokenProviders();
services.AddMvc();
services.AddIdentityServer()
.AddDeveloperSigningCredential(filename: "tempkey.rsa")
.AddInMemoryClients(IdentityConfig.GetClients("http://localhost:7017"))
.AddInMemoryIdentityResources(IdentityConfig.GetIdentityResources())
.AddInMemoryApiResources(IdentityConfig.GetApiResources())
.AddAspNetIdentity<ApplicationUser>();
services.AddCors();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseBrowserLink();
}
RolesData.SeedRoles(app).Wait();
app.UseCors(x =>
x.WithOrigins("http://localhost:7017")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
);
app.UseStaticFiles();
app.UseIdentityServer();
app.UseMvc();
}
}
Web Api Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient(typeof(IDataRepository<>), typeof(DataRepository<>));
services.AddDbContext<DataContext>(optionsAction =>
optionsAction.UseSqlServer(Configuration.GetConnectionString("DevelopersConnection")));
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddAuthentication(
JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:7777"; // Auth Server
options.RequireHttpsMetadata = false; // only for development
options.ApiName = "api"; // API Resource Id
options.SupportedTokens = SupportedTokens.Jwt;
});
services.AddCors();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(x =>
x.WithOrigins("http://localhost:7017")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
);
app.UseAuthentication();
app.UseMvc();
}
}
IdentityConfig.cs:
public class IdentityConfig
{
public static IEnumerable<Client> GetClients(string hostname) => new List<Client>
{
new Client
{
ClientId = "client",
ClientName = "application",
AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
AccessTokenType = AccessTokenType.Jwt,
AllowAccessTokensViaBrowser = true,
ClientSecrets = {new Secret("secret".Sha256())},
RequireConsent = false,
RedirectUris = { $"{hostname}/callback.html" },
PostLogoutRedirectUris = { $"{hostname}/index.html" },
AllowedCorsOrigins = { hostname },
AlwaysIncludeUserClaimsInIdToken = true,
AllowOfflineAccess = true,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.OfflineAccess,
JwtClaimTypes.Role,
"api"
}
},
};
public static IEnumerable<IdentityResource> GetIdentityResources() => new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Email(),
new IdentityResources.Profile(),
new IdentityResource("role", new []{ JwtClaimTypes.Role })
};
public static IEnumerable<ApiResource> GetApiResources() => new List<ApiResource>
{
new ApiResource("api")
{
UserClaims =
{
JwtClaimTypes.Email,
JwtClaimTypes.Role,
JwtClaimTypes.IdentityProvider,
IdentityServerConstants.StandardScopes.OpenId
}
}
};
}
Getting Jwt access_token:
var disco = await DiscoveryClient.GetAsync("http://localhost:7777");
var tokenClient = new TokenClient(disco.TokenEndpoint, "client", "secret");
var tokenResponse =
await tokenClient.RequestResourceOwnerPasswordAsync("username", "password", "openid api");
var access_token = tokenResponse.AccessToken;

Seed data to UserRole table .net core

I want to seed the default DB with an admin user before I start the project on .NET Core Default MVC application. The code is as below:
public void SeedDb(ApplicationDbContext Context, IServiceProvider ServiceProvider, IConfiguration Configuration)
{
if (Context.Users.Count() > 0) return;
var UserManager = ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var ApplicationUser = new ApplicationUser()
{
Email = Configuration["Email"],
NormalizedEmail = Configuration["Email"],
LockoutEnabled = false,
NormalizedUserName = Configuration["Email"],
SecurityStamp = "579355dd - a64c - 498d - a0b5 - 9e55754c9109",
EmailConfirmed = true,
ConcurrencyStamp = null,
Id = "977ec1a5-1ae7-4658-952a-6b5dccd75a85",
PasswordHash ="",
PhoneNumber = "333333333333",
LockoutEnd = null,
AccessFailedCount = 1,
PhoneNumberConfirmed = true,
TwoFactorEnabled = false,
UserName = Configuration["Email"]
};
var Password = HashPassword(ApplicationUser, Configuration["Password"]);
if (VerifyHashedPassword(ApplicationUser, Password, Configuration["Password"]) == PasswordVerificationResult.Success)
{
ApplicationUser.PasswordHash = Password;
}
Context.Users.Add(ApplicationUser);
Context.SaveChanges();
var RoleManager = ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
string[] Roles = { "Admin", "Manager", "User" };
foreach (string RoleName in Roles) {
RoleManager.CreateAsync(new IdentityRole(RoleName));
}
var Admin = Context.Users.SingleOrDefault(m => m.Email == Configuration["Email"]);
var Role = Context.Roles.SingleOrDefault(m => m.Name == Configuration["Role"]);
IdentityUserRole<string> UserRole = new IdentityUserRole<string>() { UserId = Admin.Id, RoleId = Role.Id };
Context.UserRoles.Add(UserRole);
Context.SaveChanges();
}
Everything runs perfect except I can't seed the UserRole DB with Data. From DBContext I add IdentityUserRole entity and save the changes to DB. Although nothing passed under the DB. Any suggestion?
Create a class named StartupDbInitializer:
using System;
using System.Collections.Generic;
using System.Linq;
using Core.Entities;
using Microsoft.AspNetCore.Identity;
namespace Core.Startups
{
public class StartupDbInitializer
{
private const string AdminEmail = "admin#admin.com";
private const string AdminPassword = "StrongPasswordAdmin123!";
private static readonly List<IdentityRole> Roles = new List<IdentityRole>()
{
new IdentityRole {Name = "Admin", NormalizedName = "ADMIN", ConcurrencyStamp = Guid.NewGuid().ToString()}
};
public static void SeedData(ApplicationDbContext dbContext, UserManager<User> userManager)
{
dbContext.Database.EnsureCreated();
AddRoles(dbContext);
AddUser(dbContext, userManager);
AddUserRoles(dbContext, userManager);
}
private static void AddRoles(ApplicationDbContext dbContext)
{
if (!dbContext.Roles.Any())
{
foreach (var role in Roles)
{
dbContext.Roles.Add(role);
dbContext.SaveChanges();
}
}
}
private static async void AddUser(ApplicationDbContext dbContext, UserManager<User> userManager)
{
if (!dbContext.Users.Any())
{
var user = new User {
UserName = AdminEmail,
Email = AdminEmail,
IsEnabled = true,
EmailConfirmed = true,
};
await userManager.CreateAsync(user, AdminPassword);
}
}
private static void AddUserRoles(ApplicationDbContext dbContext, UserManager<User> userManager)
{
if (!dbContext.UserRoles.Any())
{
var userRole = new IdentityUserRole<string>
{
UserId = dbContext.Users.Single(r => r.Email == AdminEmail).Id,
RoleId = dbContext.Roles.Single(r => r.Name == "Admin").Id
};
dbContext.UserRoles.Add(userRole);
dbContext.SaveChanges();
}
}
}
}
Then call it in your Startup's Configure method:
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
ApplicationDbContext dbContext,
UserManager<User> userManager,
)
{
// rest of code...
StartupDbInitializer.SeedData(dbContext, userManager);
}
Above, I inject my DbContext and UserManager<T>.
Try this line... it must work.
ApplicationUser user = await _usermanager.FindByEmailAsync("your.email#mymail.com");
if (!await _usermanager.IsInRoleAsync(user, "Admin"))
{
await _usermanager.AddToRoleAsync(user, "Admin");
}
When you tried it and it works, change it to your config parameters if you prefer them. It's not that hard to get it to work, you have everything you need in UserManager and RoleManager classes.
I still say you have to check if the role exist in table before you insert it, I got all my roles populated every time I run the application before I added the check.
if ((await _roleManager.FindByNameAsync("Admin")) == null)
{
await _roleManager.CreateAsync(new IdentityRole { Name = "Admin" });
}

MEF and WEB API 2.2

I am trying to inject dependencies into a Web Api Controller.
I created an own IHttpControllerActivator class and replaced the default one in lobalConfiguration.
public class SimpleASPWebAPIContainer : IHttpControllerActivator
{
private readonly CompositionContainer container;
public SimpleASPWebAPIContainer(CompositionContainer compositionContainer)
{
container = compositionContainer;
}
public IHttpController Create(System.Net.Http.HttpRequestMessage request, System.Web.Http.Controllers.HttpControllerDescriptor controllerDescriptor, Type controllerType)
{
if (controllerType != null)
{
var export = container.GetExports(controllerType, null, null).FirstOrDefault();
IHttpController result = null;
if (null != export)
{
result = export.Value as IHttpController;
}
else
{
//result = base.GetControllerInstance(requestContext, controllerType);
//container.ComposeParts(result);
}
return result;
}
else
{
return null;
}
}
public void Dispose()
{
if (container != null)
container.Dispose();
}
}
var apiSimpleContainer = new SimpleASPWebAPIContainer(container);
System.Web.Http.GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), apiSimpleContainer);
But when the client app is calling a controller method the IHttpControllerActivation Create method is not invoked.
Anybody can help me?
It was a very silly mistake.
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
MefConfig.RegisterMef(config);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
AutoMapperConfig.InitAutoMapper();
}
I should have to used the new HttoConfiguration instance to replace default IHttpControllerActivator instead of System.Web.Http.GlobalConfiguration.Configuration.

No breadcrumbs with MVCSiteMapProvider custom MvcRouteHandler

I have 2 routes in global.asax
routes.MapRoute(
"DefaultFriendlyUrl",
"Page/{FriendlyUrl}",
null,
new string[] { "MvcApplication2.Controllers" }
).RouteHandler = new FriendlyUrlRouteHandler();
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "index", id = UrlParameter.Optional },
new string[] { "MvcApplication2.Controllers" }
);
so, FriendlyUrlRouteHandler work all my /Page/blablabla routes and send to PageController with 1 action Index
public class FriendlyUrlRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var friendlyUrl = (string)requestContext.RouteData.Values["FriendlyUrl"];
PageItem page = null;
if (!string.IsNullOrEmpty(friendlyUrl))
page = PageManager.GetPageByFriendlyUrl(friendlyUrl);
if (page == null)
{
requestContext.RouteData.Values["controller"] = "home";
requestContext.RouteData.Values["action"] = "index";
requestContext.RouteData.Values["id"] = null;
}
else
{
requestContext.RouteData.Values["controller"] = "page";
requestContext.RouteData.Values["action"] = "index";
requestContext.RouteData.Values["id"] = page.PageID;
}
return base.GetHttpHandler(requestContext);
}
}
Then PageController get content for my page and show it. But MvcSiteMapProvider don't show breadcrumbs for these pages
SiteMap.cs
public class SiteMap : DynamicNodeProviderBase
{
public override IEnumerable<DynamicNode> GetDynamicNodeCollection()
{
var returnValue = new List<DynamicNode>();
returnValue.Add(new DynamicNode() { Key = "id1", Title="CustomPage", Controller="Page", Action="Index" });
return returnValue;
}
}
And my CustomPage doesn,t exists in #Html.MvcSiteMap().SiteMapPath(), but page is showed correctly. What,s wrong in my code?
So I can,t build tree of my custom pages in breadcrumbs string...
Please provide your Mvc.sitemap.
Your instance of DynamicNode appears to be missing the ParentKey.