Authorize with roles is not working in .NET 5.0 Blazor Client app - authorization

I have a .NET 5.0 Blazor client app and I am unable to get the [Authorize(Roles="Admin")] and AuthorizeView tag to work.
I have scaffolded identity pages as well:
I am using a custom identity implementation that uses Cosmos Db: https://github.com/pierodetomi/efcore-identity-cosmos
I know that Authorization with roles in the Blazor client project template is an issue: https://github.com/dotnet/AspNetCore.Docs/issues/17649#issuecomment-612442543
I tried workarounds as mentioned in the above Github issue thread and the following SO answer: https://stackoverflow.com/a/64798061/6181928
...still, I am unable to get it to work.
Ironically, the IsInRoleAsync method is not even called after logging in to the application. I have applied a breakpoint on its implementation in the custom CosmosUserStore class and it doesn't get hit.
The browser console shows this after logging in to the application with the admin user:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddCosmosIdentity<MyDbContext, IdentityUser, IdentityRole>(
// Auth provider standard configuration (e.g.: account confirmation, password requirements, etc.)
options => options.SignIn.RequireConfirmedAccount = true,
options => options.UseCosmos(
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
databaseName: "xxxxxxxxxxxxxxxxxxxxxxxxx"
),
addDefaultTokenProviders: true
).AddDefaultUI().AddRoles<IdentityRole>();
services.AddScoped<IUsersRepository, UsersRepository>();
services.AddIdentityServer().AddApiAuthorization<IdentityUser, MyDbContext>(options =>
{
options.IdentityResources["openid"].UserClaims.Add("role");
options.ApiResources.Single().UserClaims.Add("role");
});
// Need to do this as it maps "role" to ClaimTypes.Role and causes issues
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");
services.AddAuthentication()
.AddIdentityServerJwt();
services.AddControllersWithViews();
services.AddRazorPages();
}
Program.cs
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddHttpClient("IdentityDocApp.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("IdentityDocApp.ServerAPI"));
builder.Services.AddHttpClient();
builder.Services.AddScoped<IManageUsersService, ManageUsersService>();
builder.Services.AddBlazorTable();
builder.Services.AddApiAuthorization();
builder.Services.AddApiAuthorization(options =>
{
options.UserOptions.RoleClaim = "role";
});
await builder.Build().RunAsync();
}
}
App.razor
NavMenu.razor
<div class="#NavMenuCssClass" #onclick="ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<AuthorizeView Roles="Admin">
<li class="nav-item px-3">
<NavLink class="nav-link" href="users">
<span class="oi oi-person" aria-hidden="true"></span> Users
</NavLink>
</li>
</AuthorizeView>
</ul>
ManageUsers.razor
ManageUsersController
The database has the right data in the UserRoles collection. No issues there.
So, what could be the issue? What am I doing wrong?
Update:
It is embarrassing but my IsInRoleAsync implementation in the custom user store was not correct. As soon as I fixed it the issue was gone.
I am only using the following code in the Startup.cs of the server side:
services.AddIdentityServer()
.AddApiAuthorization<IdentityUser, MyDbContext>(options =>
{
options.IdentityResources["openid"].UserClaims.Add("name");
options.ApiResources.Single().UserClaims.Add("name");
options.IdentityResources["openid"].UserClaims.Add("role");
options.ApiResources.Single().UserClaims.Add("role");
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");
In the Program.cs of client-side I am only using builder.Services.AddApiAuthorization();
Thanks to #MrC aka Shaun Curtis for letting me know that the issue lied on the server-side.

Paste this into your Index page so you can see the information for your user:
#if (user is not null)
{
<h3>#user.Identity.Name</h3>
<div class="m-2 p-2">
Is Authenticated: #user.Identity.IsAuthenticated
</div>
<div class="m-2 p-2">
Authentication Type: #user.Identity.AuthenticationType
</div>
<div class="m-2 p-2">
Admin Role: #user.IsInRole("Admin")
</div>
<div class="m-2 p-2">
<h5>Claims</h5>
#foreach (var claim in user.Claims)
{
<span>
#claim.Type
</span>
<span>:</span>
<span>
#claim.Value
</span>
<br />
}
</div>
}
else
{
<div class="m-2 p-2">
No User Exists
</div>
}
#code {
[CascadingParameter] public Task<AuthenticationState> AuthTask { get; set; }
private System.Security.Claims.ClaimsPrincipal user;
protected async override Task OnInitializedAsync()
{
var authState = await AuthTask;
this.user = authState.User;
}
}
You should get something like this:
This shows which roles have been passed in the authentication data in the header from the authentication provider. This should include role.
Update
Remove:
// Need to do this as it maps "role" to ClaimTypes.Role and causes issues
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

Related

How can I make one to one chat system in Asp.Net.Core Mvc Signalr?

I want to implement private chat system with Mssql Database. This codes works as public when I send the message, message is appears all clients. But I want to one to one chat system. One user enter the receiver id which stored in database and message text, then send the message to Receiver . Then the message appears in receiver message area which has that receiver id.
Here is my js code
"use strict";
var connection = new signalR.HubConnectionBuilder().withUrl("/chathub").build();
//Disable send button until connection is established
document.getElementById("sendButton").disabled = true;
connection.start().then(function () {
document.getElementById("sendButton").disabled = false;
}).catch(function (err) {
return console.error(err.toString());
});
document.getElementById("sendButton").addEventListener("click", function (event) {
var user = document.getElementById("userInput").value;
var message = document.getElementById("messageInput").value;
connection.invoke("SendMessage", user, message).catch(function (err) {
return console.error(err.toString());
});
event.preventDefault();
});
connection.on("ReceiveMessage", function (user, message) {
var msg = message.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
var encodedMsg = user + ":" + msg;
var li = document.createElement("li");
li.textContent = encodedMsg;
document.getElementById("messagesList").appendChild(li);
});
Here is my hub class
using MentorShip.Models;
using Microsoft.AspNetCore.SignalR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace MentorShip.Hubs
{
public class SignalRChat:Hub
{
Context c = new Context();
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage",user,message);
}
}
}
Here is my html code
<div class="container">
<div class="row"> </div>
<div class="row">
<div id="connectionId"></div>
<div class="col-2">Receiver Id</div>
<div class="col-4"><input type="text" id="userInput" /></div>
</div>
<div class="row">
<div class="col-2">Message</div>
<div class="col-4"><input type="text" id="messageInput" /></div>
</div>
<div class="row"> </div>
<div class="row">
<div class="col-6">
<input type="button" id="sendButton" value="Send Message" />
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<hr />
</div>
</div>
<div class="row">
<div class="col-6">
<ul id="messagesList"></ul>
</div>
</div>
</div>
To send the message to specific user, you could use the following methods:
Use Single-user groups.
You can create a group for each user, and then send a message to that group when you want to reach only that user. The name of each group is the name of the user. If a user has more than one connection, each connection id is added to the user's group.
For example, base on the getting start document, I have create a SignalR application, it will send message to all users. Then, in the ChatHub class, add the Authorize attribute and override the OnConnectedAsync() method, in the OnConnectedAsync method, we could create a group based on the Identity User. Then, add a SendMessageToGroup method to send message to group.
[Authorize]
public class ChatHub : Hub
{
public override Task OnConnectedAsync()
{
Groups.AddToGroupAsync(Context.ConnectionId, Context.User.Identity.Name);
return base.OnConnectedAsync();
}
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
public Task SendMessageToGroup(string sender, string receiver, string message)
{
return Clients.Group(receiver).SendAsync("ReceiveMessage", sender, message);
}
}
In the Index.cshtml page, add a Receiver input element to enter the receiver name (the same as the group name).
<div class="container">
<div class="row"> </div>
<div class="row">
<div class="col-2">Sender</div>
<div class="col-4"><input type="text" id="senderInput" /></div>
</div>
<div class="row">
<div class="col-2">Receiver</div>
<div class="col-4"><input type="text" id="receiverInput" /></div>
</div>
<div class="row">
<div class="col-2">Message</div>
<div class="col-4"><input type="text" id="messageInput" /></div>
</div>
<div class="row"> </div>
<div class="row">
<div class="col-6">
<input type="button" id="sendButton" value="Send Message" />
</div>
</div>
</div>
Then, update the sendButton click event in the chat.js file:
document.getElementById("sendButton").addEventListener("click", function (event) {
var sender = document.getElementById("senderInput").value;
var receiver = document.getElementById("receiverInput").value;
var message = document.getElementById("messageInput").value;
if (receiver !="") {
connection.invoke("SendMessageToGroup", sender, receiver, message).catch(function (err) {
return console.error(err.toString());
});
}
else {
connection.invoke("SendMessage", sender, message).catch(function (err) {
return console.error(err.toString());
});
}
event.preventDefault();
});
After that, we could send message to specific user, screenshot as below (if the receiver is null, it will send message to all user, else, will send message to specific user):
Send message via the ConnectionID.
From the above sample code, in the OnConnectedAsyc method, we could get the ConnectId and the User Name, then, you could store them in the database. Then, you can add the SendMessageToUser method in the ChatHub.cs. In this method, you could query the database and find the connectionId based on the receiver name, after that using Clients.Client("connectionId").SendAsync() method to send the message to a specific user.
public Task SendMessageToUser(string sender, string receiver, string message)
{
//based on the receiver name to query the database and get the connection id
return Clients.Client("connectionId").SendAsync("ReceiveMessage", sender, message);
}
Here are some related articles, you can refer to them:
Use hubs in SignalR for ASP.NET Core
Mapping SignalR Users to Connections
Update:
Detail Steps:
Open the Visual Studio 2019 (the latest version), and create an Asp.net Core Web Application (named SignalRApp, using .net core 3.1 version):
Choose the MVC template and change the Authentication to "Individual User Accounts".
Using the following command in the Package Manager Console tools. More detail information, check EF Core Migrations.
add-migration InitialCreate
update-database
After that, we could run the application and register users. The login screenshot as below:
Add the SignalR client library
In Solution Explorer, right-click the project, and select Add > Client-Side Library.
In the Add Client-Side Library dialog, for Provider select unpkg.
For Library, enter #microsoft/signalr#latest.
Select Choose specific files, expand the dist/browser folder, and select signalr.js and signalr.min.js.
Set Target Location to wwwroot/js/signalr/, and select Install.
LibMan creates a wwwroot/js/signalr folder and copies the selected files to it.
Create a SignalR hub.
In the Project folder, create a Hubs folder and add a ChatHub.cs file with the following code:
namespace SignalRApp.Hubs
{
//require using Microsoft.AspNetCore.SignalR;
//require using Microsoft.AspNetCore.Authorization;
[Authorize]
public class ChatHub : Hub
{
public override Task OnConnectedAsync()
{
Groups.AddToGroupAsync(Context.ConnectionId, Context.User.Identity.Name);
return base.OnConnectedAsync();
}
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
public Task SendMessageToGroup(string sender, string receiver, string message)
{
return Clients.Group(receiver).SendAsync("ReceiveMessage", sender, message);
}
}
}
Configure SignalR in the Startup.cs file. You could check this article.
The startup.cs file as below:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews();
services.AddRazorPages();
services.AddSignalR();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
endpoints.MapHub<ChatHub>("/chathub");
});
}
}
Add SignalR client code:
[Note] In this step, please pay attention to the js file path. If the js file doesn't load successfully, the client code will not work.
In the Home controller Index page(Index.cshtml), replace the content as below:
#{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<div class="container">
<div class="row"> </div>
<div class="row">
<div class="col-2">Sender</div>
<div class="col-4"><input type="text" id="senderInput" /></div>
</div>
<div class="row">
<div class="col-2">Receiver</div>
<div class="col-4"><input type="text" id="receiverInput" /></div>
</div>
<div class="row">
<div class="col-2">Message</div>
<div class="col-4"><input type="text" id="messageInput" /></div>
</div>
<div class="row"> </div>
<div class="row">
<div class="col-6">
<input type="button" id="sendButton" value="Send Message" />
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<hr />
</div>
</div>
<div class="row">
<div class="col-6">
<ul id="messagesList"></ul>
</div>
</div>
<script src="~/js/signalr/dist/browser/signalr.js"></script>
<script src="~/js/chat.js"></script>
In the wwwroot/js folder, create a chat.js file with the following code:
"use strict";
var connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();
//Disable send button until connection is established
document.getElementById("sendButton").disabled = true;
connection.on("ReceiveMessage", function (user, message) {
var msg = message.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
var encodedMsg = user + " says " + msg;
var li = document.createElement("li");
li.textContent = encodedMsg;
document.getElementById("messagesList").appendChild(li);
});
connection.start().then(function () {
document.getElementById("sendButton").disabled = false;
}).catch(function (err) {
return console.error(err.toString());
});
document.getElementById("sendButton").addEventListener("click", function (event) {
var sender = document.getElementById("senderInput").value;
var receiver = document.getElementById("receiverInput").value;
var message = document.getElementById("messageInput").value;
if (receiver != "") {
connection.invoke("SendMessageToGroup", sender, receiver, message).catch(function (err) {
return console.error(err.toString());
});
}
else {
connection.invoke("SendMessage", sender, message).catch(function (err) {
return console.error(err.toString());
});
}
event.preventDefault();
});
After that we could send messages to user via groups. Like this:

ASP.NET Core does not log me in

EDIT: So After many failed attempts to figure out why this is not working I decided to create a project and choose to have user login implemented from there so it will configure everything for me.
after that I just scraped the automated login/register code and replaced it with my own. everything works amazingly well. I am pretty sure in my original version I missed some needed parameter
I am trying to make a simple login function. but I cant seem to get it to work and it drives me crazy ..
so I do my shared folder under _layout.cshtml I have added this
#inject SignInManager<IdentityUser> signInManager;
and then just a little later inside my navbar I added this elements
<ul class="navbar-nav ml-auto">
#if (signInManager.IsSignedIn(User)) {
<li class="nav-item">
<form method="post" asp-controller="User" asp-action="Logout">
<button type="submit" class="nav-link btn btn-link py-0" style="width:auto">
Logout #User.Identity.Name
</button>
</form>
</li>
}
else {
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="User" asp-action="Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="User" asp-action="Login">Login</a>
</li>
}
</ul>
and in my controller I added this I called this controller UserController.cs
If I enter wrong login info . like wrong password or username it will tell me I that. so the connection with the database is no issue. but when I am send in my "Home" Index I am not logged it. as if it does not create a session I am not sure
[HttpGet]
public IActionResult Login()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Login(Login model)
{
if (ModelState.IsValid)
{
var result = await signInManager.PasswordSignInAsync(model.UserName,model.Password,
model.RememberMe,false);
if (result.Succeeded)
{
//creates a temponary sign in session cookie. that cookie is going to be lost after the browser is closed
return RedirectToAction("index", "home");
}
//if succeeded = false then ew llop through the error list
ModelState.AddModelError(string.Empty,"Invalid Login");
}
return View(model);
}
Here is the whole working demo like below:
View(Login.cshtml):
#using Microsoft.AspNetCore.Identity
#model LoginViewModel
#inject SignInManager<IdentityUser> SignInManager
#{
ViewData["Title"] = "Log in";
}
<h1>#ViewData["Title"].</h1>
<div class="row">
<div class="col-md-8">
<section>
<form asp-controller="Account" asp-action="Login" asp-route-returnurl="#ViewData["ReturnUrl"]" method="post" class="form-horizontal" role="form">
<h4>Use a local account to log in.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Password" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="checkbox">
<label asp-for="RememberMe">
<input asp-for="RememberMe" />
#Html.DisplayNameFor(m => m.RememberMe)
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Log in</button>
</div>
</div>
<p>
<a asp-action="Register" asp-route-returnurl="#ViewData["ReturnUrl"]">Register as a new user?</a>
</p>
<p>
<a asp-action="ForgotPassword">Forgot your password?</a>
</p>
</form>
</section>
</div>
<div class="col-md-4">
<section>
<h4>Use another service to log in.</h4>
<hr />
#{
var schemes = await SignInManager.GetExternalAuthenticationSchemesAsync();
var loginProviders = schemes.ToList();
if (loginProviders.Count == 0)
{
<div>
<p>
There are no external authentication services configured. See this article
for details on setting up this ASP.NET application to support logging in via external services.
</p>
</div>
}
else
{
<form asp-controller="Account" asp-action="ExternalLogin" asp-route-returnurl="#ViewData["ReturnUrl"]" method="post" class="form-horizontal" role="form">
<div>
<p>
#foreach (var provider in loginProviders)
{
<button type="submit" class="btn btn-default" name="provider" value="#provider.Name" title="Log in using your #provider.Name account">#provider.Name</button>
}
</p>
</div>
</form>
}
}
</section>
</div>
</div>
#section Scripts {
#{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
Controller:
[Authorize]
public class AccountController : Controller
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
public AccountController(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
// GET: /Account/Login
[HttpGet]
[AllowAnonymous]
public IActionResult Login()
{
return View();
}
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model)
{
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
}
return View(model);
}
}
DbContext:
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions options) : base(options) { }
}
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentityCore<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
services.AddAuthentication(o =>
{
o.DefaultScheme = IdentityConstants.ApplicationScheme;
o.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddIdentityCookies(o => { });
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Result:

Attribute routing is not working properly in asp.net core 3.0

I was trying to migrate my application from asp.net core 2.1 to 3.0 which uses attribute routing
My Startup file's ConfigureServices and Configure methods:
public void ConfigureServices(IServiceCollection services)
{
services.ConfigureOptions(typeof(ABCClass));
services.AddTransient<ITagHelperComponent, XYZTagHelperComponent>();
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
I have replaced services.AddMvc(); with services.AddMvc(options => options.EnableEndpointRouting = false); to disable Endpoint routing
My action method:
[Route("")]
[Route("Machines")]
public async Task<ViewResult> GetMachinesAsync()
{
return View("MachineView");
}
First time my application loads with MachineView, but when I try to call same action method on it gives me 404 error (page can’t be found)
action call from .cshtml file:
<li class="nav-item">
<a class="nav-link"
href="#Url.Action("GetMachinesAsync", "Machine")">
Machines
</a>
</li>
Can you please help me out if I am missing something here, or I have done something wrong while configuring middleware for routing.
Thanks in Advance.
Async suffix for controller action names will be trimmed by default in asp.net core 3.0.
Refer to https://stackoverflow.com/a/59024733/10158551
Solution1:
Replace GetMachinesAsync to GetMachines in view.
<li class="nav-item">
<a class="nav-link"
href="#Url.Action("GetMachines", "Machine")">
Machines
</a>
</li>
Solution2:
Keep using GetMachinesAsync
<li class="nav-item">
<a class="nav-link"
href="#Url.Action("GetMachinesAsync", "Machine")">
Machines
</a>
</li>
then disable that behavior in startup
services.AddMvc(options =>
{
options.EnableEndpointRouting = false;
options.SuppressAsyncSuffixInActionNames = false;
});
You don't require async suffixes for action methods. So if you want to refer GetMachinesAsync you need to use GetMachines, like this.
<li class="nav-item">
<a class="nav-link"
href="#Url.Action("GetMachines", "Machine")">
Machines
</a>
</li>

Web API authentication turns request.authentication false

I have a project that I created in ASP.NET MVC and now the second part of the work is pass the logic of the ASP.NET MVC application (basically the database) to an ASP.NET Web API and do the connection with them.
The thing is, I already did the connection and I already save values in the Web API database, but a strange thing is happening.
I have markup in my layout.cs.html file:
<body>
#if (User.Identity.IsAuthenticated)
{
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
#Html.ActionLink("MSDiary", "Index", "Home", new { area = "" }, new { #class = "navbar-brand" })
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>#Html.ActionLink("Despesas", "Index", "Despesas")</li>
<li>#Html.ActionLink("Rendimentos", "Index", "Rendimentos")</li>
<li>#Html.ActionLink("Tipos de Despesa", "Index", "TipoDespesas")</li>
<li>#Html.ActionLink("Tipos de Pagamento", "Index", "TipoPagamentos")</li>
<li>#Html.ActionLink("Tipos de Rendimento", "Index", "TipoRendimentos")</li>
</ul>
#Html.Partial("_LoginPartial")
</div>
</div>
</div>
}
<div class="container body-content">
#RenderBody()
<footer>
#if (Request.IsAuthenticated)
{
<p>#Html.Action("_ObtemSaldo", "Home")</p>
}
</footer>
</div>
#Scripts.Render("~/bundles/bootstrap")
#RenderSection("scripts", required: false)
</body>
</html>
That request is authenticated shows the navbar on the top or not depending if the user is authenticated I cut it off to see if in fact my program is getting the user, and it kinda get the user so the problem is not with the connection, but the request is authenticated don't change in the controller maybe :S
Here is my login controller:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
try
{
var client = WebApiHttpClient.GetClient();
string username = model.Email;
string password = model.Password;
HttpContent content = new StringContent(
"grant_type=password&username=" + username + "&password=" + password,
System.Text.Encoding.UTF8,
"application/x-www-form-urlencoded");
var response = await client.PostAsync("/Token", content);
if (response.IsSuccessStatusCode)
{
TokenResponse tokenResponse =
await response.Content.ReadAsAsync<TokenResponse>();
WebApiHttpClient.storeToken(tokenResponse);
// return Content(tokenResponse.AccessToken);
return RedirectToAction("Index", "Home");
}
else
{
return Content("Ocorreu um erro: " + response.StatusCode);
}
}
catch
{
return Content("Ocorreu um erro.");
}
}
I already tried with user.authenticated still doesn't work if someone can give me a hand I would appreciate it a lot :D
ps: Sorry for my bad English
If I understood, you have an MVC Application and you have passed some logic (database access) to an Web Api project, so when you send a form/request to the server, it will be received by the controller from MVC, and after that the request will be send to the WebApi (at least is what I understood from your code).
Your problem is that the user logs into the application, the MVC Controller goes to the WebApi to authenticate the user and afterwards, even the login and password been correct the MVC (View) still considers that the user is not logged in.
Well, if what I described is correct, seen your code, I would say that the user is indeed being authenticate in the Web Api, however, as the MVC is the direct interface with the user, it is missing set the MVC Application User as authenticated through some authentication method, something like:
FormsAuthentication.SetAuthCookie(username, false);
It´s worth to say that you would have to store the token (from webapi, after user has been authenticated) so that the nexts requests to the WebApi it considers the user authenticated to that specific token.
Hope I´ve helped.
Regards,

Setting specific authentication type with mvc 5

ASP.NET comes default with the external login provider partial page which is set as:
var loginProviders = Context.GetOwinContext().Authentication.GetExternalAuthenticationTypes();
if (loginProviders.Count() == 0)
{
<div>
<p>
There are no external authentication services configured. See this article
for details on setting up this ASP.NET application to support logging in via external services.
</p>
</div>
}
else
{
using (Html.BeginForm("ExternalLogin", "Account", new { ReturnUrl = Model.ReturnUrl }))
{
#Html.AntiForgeryToken()
<div id="socialLoginList">
<p>
#foreach (AuthenticationDescription p in loginProviders)
{
<button type="submit" class="btn btn-default" id="#p.AuthenticationType" name="provider" value="#p.AuthenticationType" title="Log in using your #p.Caption account">#p.AuthenticationType</button>
}
</p>
</div>
}
}
What if i know exactly the authentication type i am going to use, say facebook? How can i set that up?