.NetCore blazor authentication not working - authentication

I have set up the necessities to create an example to build off of. Here's my code.
StartUp File
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
services.AddSingleton<DoggoDataServices>();
services.AddSingleton<AddDoggoServices>();
services.AddSingleton<EventServices>();
services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
App.razor
<Router AppAssembly="#typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)" />
</Found>
<NotFound>
<CascadingAuthenticationState>
<LayoutView Layout="#typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</CascadingAuthenticationState>
</NotFound>
</Router>
customAuth
public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, "john.smith#gmail.com"),
}, "apiauth_type");
var user = new ClaimsPrincipal(identity);
return Task.FromResult(new AuthenticationState(user));
}
}
}
index
<AuthorizeView>
<Authorized>
<p>Welcome, #context.User.Identity.Name</p>
</Authorized>
<NotAuthorized>
<p>Not Logged In</p>
</NotAuthorized>
</AuthorizeView>
Given the code, the index page only shows "not Logged in". Am I missing something so simple that I am overlooking it? I am new to blazor.

You forgot to add CascadingAuthenticationState in app.razor
<CascadingAuthenticationState>
<UserSession>
<Router AppAssembly="#typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="#typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</UserSession>
</CascadingAuthenticationState>

You can use the default template. It has an example that works.

You should call
NotifyAuthenticationStateChanged
When you use a Custom Provider, you should notify it when a user is Authenticated.
Example:
You may add this method to your Custom provider:
public void MarkUserAsAuthenticated(Users u)
{
// add your claims here. This is just an example.
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, u.UserName)
});
var user = new ClaimsPrincipal(identity);
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user)));
}
And please note, that you should create and call similar method when a user is logs out:
public void LogoutUser()
{
// reset identities and other related info (localstorage data if you have, etc).
var identity = new ClaimsIdentity();
var user = new ClaimsPrincipal(identity);
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user)));
}

Related

Change Default Page in Web API project .net core 6 MVC

I have ASP.net core **WebAPI ** project after that I create Scaffolded item witch Identity inside this project. How can I change Default page to Identity/page/login.cshtml instead of ~/swagger/index.html
Thanks
change default page from ~/swagger/index.html to Identity/page/login.cshtml
Thanks
my Code here
AppUser.cs
public class AppUser: IdentityUser <int>
{
public string FullName { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateModified { get; set; }
}
program.cs
builder.Services.ConfigureApplicationCookie(options =>
{
options.LoginPath = $"/Identity/Account/Login";
options.LogoutPath = $"/Identity/Account/Logout";
options.AccessDeniedPath = $"/Identity/Account/AccessDenied";
});
AppDbContext.cs
public class AppDbContext : IdentityDbContext<AppUser, IdentityRole<int>, int>
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{
}
...........................................
}
_LoginPartial.cshtml
#using Claim.Data.Entities
#using Microsoft.AspNetCore.Identity
#inject SignInManager<AppUser> SignInManager
#inject UserManager<AppUser> UserManager
When I Test to click to Login to go to Login Page
error:
InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Identity.SignInManager`1[Microsoft.AspNetCore.Identity.IdentityUser]' while attempting to activate 'XXXXXXX.Areas.Identity.Pages.Account.LoginModel'.
hi have fixed the problem. I have to change the Login.cshtmal.sc file.
However I got problem Authentication, cannot login
SignInManager.IsSignedIn(User) <- return claims = 0
_loginPatial.cshtml
#using Microsoft.AspNetCore.Identity
#inject SignInManager<IdentityUser> SignInManager
#inject UserManager<IdentityUser> UserManager
<ul class="navbar-nav">
#if (SignInManager.IsSignedIn(User))
{
<li class="nav-item">
<a id="manage" class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello #UserManager.GetUserName(User)!</a>
</li>
<li class="nav-item">
<form id="logoutForm" class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="#Url.Action("Index", "Home", new { area = "" })">
<button id="logout" type="submit" class="nav-link btn btn-link text-dark">Logout</button>
</form>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" id="register" asp-area="Identity" asp-page="/Account/Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" id="login" asp-area="Identity" asp-page="/Account/Login">Login</a>
</li>
}
</ul>
Login.cshtml.cs
if (result.Succeeded) <------ true
public async Task<IActionResult> OnPostAsync(string? returnUrl = null)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}
..............................
program.cs
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddRazorPages();
builder.Services.AddDbContext<AppDbContext>(opt =>
{
opt.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
});
builder.Services.AddIdentity<IdentityUser, IdentityRole>().AddDefaultTokenProviders()
.AddEntityFrameworkStores<AppDbContext>();
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Constants.SecretKey)),
ValidateAudience = true,
ValidIssuer = Constants.Issuer,
ValidAudience = Constants.Audience,
RequireExpirationTime = true,
};
});
builder.Services.AddScoped<IUserClaimService, UserClaimServices>();
builder.Services.AddSingleton<IEmailSender, EmailSender>();
builder.Services.ConfigureApplicationCookie(options =>
{
options.LoginPath = $"/Identity/Account/Login";
options.LogoutPath = $"/Identity/Account/Logout";
options.AccessDeniedPath = $"/Identity/Account/AccessDenied";
});
//builder.Services.AddMvc().AddRazorPagesOptions(options =>
//{
// options.Conventions.AddAreaPageRoute("Identity", "/Account/Login", "");
//}
// );
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
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.MapRazorPages();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();

Blazor WASM Authentication Static Web App

I've got a problem which I quite don't understand yet (used doc: https://learn.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-6.0) , if I try to login, it works on the Static Web App (SWA) and locally.
but when I try to logout,
locally it works and on the SWA it doesn't.
Anyone has an idea what the problem is?
Screenshots for reference:
Portal app registration:
appsettings.json
{
"AzureAd": {
"Authority": "https://login.microsoftonline.com/TENANT-ID",
"ClientId": "CLIENT-ID",
"ValidateAuthority": true
}
}
staticwebapp.config.json
{
"networking": {
"allowedIpRanges": [
"IP/MASK"
]
}
}
Program.cs
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddSingleton<IProductService<ProductA,InsuredPerson>, ProductAService>();
builder.Services.AddSingleton<IProductService<ProductB,InsuredPerson>, ProductBService>();
builder.Services.AddSingleton<IDownloadService,DownloadService>();
builder.Services.AddSingleton<IUploadService, UploadService>();
builder.Services.AddAutoMapper(new[] { typeof(ServiceMappingProfile)});
builder.Services.AddHttpClient();
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddLocalization();
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes
.Add("https://graph.microsoft.com/User.Read");
options.ProviderOptions.LoginMode = "redirect";
});
var host = builder.Build();
CultureInfo culture;
var js = host.Services.GetRequiredService<IJSRuntime>();
var result = await js.InvokeAsync<string>("blazorCulture.get");
if (result != null)
{
culture = new CultureInfo(result);
}
else
{
culture = new CultureInfo("en-US");
await js.InvokeVoidAsync("blazorCulture.set", "en-US");
}
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
await host.RunAsync();
}
}
App.razor
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)">
<NotAuthorized>
#if (context.User.Identity?.IsAuthenticated != true)
{
<RedirectToLogin />
}
else
{
<p role="alert">You are not authorized to access this resource.</p>
}
</NotAuthorized>
</AuthorizeRouteView>
<FocusOnNavigate RouteData="#routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="#typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
Please check these points while configuring:
• Install the NuGet package Microsoft.Authentication.WebAssembly.Msal to authenticate your application using NuGet Manager.
• While Enabling the authentication by injecting the [Authorize] attribute to the Razor pages (Counter.razor and FetchData.razor).Also add reference Microsoft.AspNetCore.Authorization
#attribute [Authorize]
#using Microsoft.AspNetCore.Authorization
In LoginDisplay.razor , check the below code for login and logout
#using Microsoft.AspNetCore.Components.Authorization
#using Microsoft.AspNetCore.Components.WebAssembly.Authentication
#inject NavigationManager Navigation
#inject SignOutSessionStateManager SignOutManager
<AuthorizeView>
<Authorized>
Hello, #context.User.Identity.Name!
<button class="nav-link btn btn-link" #onclick="BeginLogout">
Log out
</button>
</Authorized>
<NotAuthorized>
Log in
</NotAuthorized>
</AuthorizeView>
#code {
private async Task BeginLogout(MouseEventArgs args)
{
await SignOutManager.SetSignOutState();
Navigation.NavigateTo("authentication/logout");
}
}
Pages/Authentication.razor
#page "/authentication/{action}"
#using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="#Action" />
#code {
[Parameter]
public string Action { get; set; }
}
Reference : Using Azure Active Directory to Secure a Blazor WebAssembly Standalone App (code-maze.com)
Workarounds that you may try:
Try 1:
Clear all the cache , try login and logout.
Try 2:
If all these are set properly and still facing the issue , Try with setting Redirect type to Web instead of SPA.
For example:
Try 3:
Include "post_logout_redirect_uri": "https://localhost:5001/authentication/logout-callback", In the json file of app where the redirect uri is mentioned
And give that url in logout url in portal in the app's registration screen:
select Authentication in the menu.
In the Logout URL section, set it to https:// localhost:5001/authentication/logout-callback

Controller not executing code (Blazor server-side)

My Goal Create a simple login form in order to authenticate and authorize the user in my Blazor Server-side app with Cookie Authentication.
My Issue Everything works... The EditForm passes the values to my Controller. The Controller validates the usercredentials. Then runs HttpContext.SignInAsync(claims) and returns Ok().
But the Cookie is not passed and the user is not Authenticate either.
What I have done
1. The EditForm, passes the userinputs to the DisplayLoginModel() on a ValidSubmit.
<div class="row">
<div class="col-8">
<EditForm Model="#userLogin" OnValidSubmit="OnValidLogin">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="mb-4 row">
<p class="col-sm-4 font-weight-bold">Email</p>
<div class="col-sm-8">
<InputText #bind-Value="userLogin.Email" class="form-control" />
</div>
</div>
<div class="mb-4 row">
<p class="col-sm-4 font-weight-bold">Password</p>
<div class="col-sm-8">
<InputText #bind-Value="userLogin.Password" class="form-control" />
</div>
</div>
<button class="btn btn-outline-primary col-sm-4" type="submit"><strong>Login EditForm</strong></button>
</EditForm>
</div>
</div>
2. The OnValidLogin Sends a request to the Form Controller
public DisplayLoginModel userLogin = new DisplayLoginModel();
private async Task OnValidLogin()
{
var requestMessage = new HttpRequestMessage()
{
Method = new HttpMethod("POST"),
RequestUri = new Uri("https://localhost:44370/Form"),
Content = JsonContent.Create(userLogin)
};
var client = httpfac.CreateClient();
var response = await client.SendAsync(requestMessage);
}
3. The Controller gets the user credentials from the displayloginModel and validets Ok().
[HttpPost]
public async Task<ActionResult> Post(DisplayLoginModel _userLogin)
{
if (_userLogin.Email == "this#Email.com")
{
ClaimsIdentity claimsIdentity = new ClaimsIdentity(new List<Claim>
{
new Claim(ClaimTypes.Role, "ActiveUser")
}, "auth");
ClaimsPrincipal claims = new ClaimsPrincipal(claimsIdentity);
await HttpContext.SignInAsync(claims);
return Ok();
}
else
{
return BadRequest();
}
}
4. Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
services.AddHttpContextAccessor();
services.AddAuthentication("Cookies").AddCookie(options =>
{
options.SlidingExpiration = true;
});
services.AddRazorPages();
services.AddServerSideBlazor();
}
And
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
Why is the Controller not signing the user in, what am I missing?
An image of the solution structure is seen right below:
Because Blazor Server uses websockets (Blazor circuits) to render the UI while on the controller, await HttpContext.SignInAsync(claims); returns a cookie header so the browser can be authorized on next page load. This means you are actually being authenticated server-side but not from the client-side as you have not reloaded the page context to update the cookies. There is a reason why the default Identity login uses MVC architecture for the authentication process. :)
My suggestion, switch to API-based authentication/authorization or use a traditional login in flow.

Blazor webassembly, check if Policy exists

In my blazor webassembly project i am trying to load poilicies from webapi, so that admin can set policies(permissions) for each role.
The problem is that the Authorization policy validation on the first page is done even before getting the response from httpclient request as it is async call, and i get an error the policy does not exists.
Is there any way that i can execute httpclient request synchronously OR check if the policy exists and wait till policy initialization is complete.
JSON Object:
[
{"policy": "Administration","roles": ["admin"]},
{"policy": "FinanceTransaction","roles": ["admin,accountant"]},
.....
]
Policy Initialization:
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
var baseAddress = builder.Configuration["apiUrl"];
builder.Services.AddHttpClient("Anonymous", client => client.BaseAddress = new Uri(baseAddress));
builder.Services.AddHttpClient("Protected", client => client.BaseAddress = new Uri(baseAddress));
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("Protected"));
builder.Services.AddAuthorizationCore(async options =>
{
var httpClientFactory = builder.Build().Services.GetRequiredService<IHttpClientFactory>();
var http = httpClientFactory.CreateClient("Anonymous");
var tasks = await http.GetFromJsonAsync<IEnumerable<PolicyRoles>>($"AppPolicy");
foreach (var task in tasks)
{
AppLoadStatus.PolicyLoaded = true;
OnPolicyLoaded.Invoke();
if (task.Roles.Any())
options.AddPolicy(task.Policy, policy => { policy.RequireRole(task.Roles); });
}
});
await builder.Build().RunAsync();
}
}
Razor Page:
#page "/Items/"
#attribute [Authorize(Policy = "FinanceTransaction")]
<div class="col col-lg-9">
#if (items == null)
{
<LoadingSpinner LoadFailed="#loadFailed" />
}
else
{
.....
.....
}
<div>
If i try to load the above page directly i get the error:
crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: The AuthorizationPolicy named: 'FinanceTransaction' was not found
But if i wait for policy initialization and navigate to other pages everything would work fine.
Can any one help me to resolve the issue. Or is there any other better approach for this
app.razor:
#if (!AppLoadStatus.PolicyLoaded)
{
<LoadingSpinner />
}
else
{
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)">
<NotAuthorized>
#if (!context.User.Identity.IsAuthenticated)
{
<RedirectToLogin />
}
else
{
<AccessDenied />
}
</NotAuthorized>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="#typeof(MainLayout)">
<RedirectToHome />
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
}
#code{
protected override void OnInitialized()
{
ServiceExtensions.OnPolicyLoaded += () => { StateHasChanged(); };
}
}

How to call method from another Razor page?

This is structure of my App and i want to call a method in Login.cshtml from ExternalLogins.cshtml.cs class. How can I do that? Some asp helpers?
Login just like ExternalLogins is RazorPage - no controller class.
Edit:
#mj1313 I added this code:
<div class="col">
<h3>Zaloguj się przez:</h3>
#{
if (Model.ExternalLogins.Count == 0)
{
<div>Brak możliwości zalogowania przez serwisy zewnętrzne.</div>
}
else
{
<form method="post"
asp-page="./Manage/ExternalLogins"
asp-page-handler="LinkLogin"
asp-route-returnUrl="#Model.ReturnUrl">
<div>
#foreach (var provider in #Model.ExternalLogins)
{
<button type="submit"
class="btn btn-primary"
name="provider"
value="#provider.Name"
title="Zaloguj się za pomocą konta
#provider.DisplayName">
#provider.DisplayName
</button>
}
</div>
</form>
}
}
</div>
But my Action in ExternalLogins not called
public async Task<IActionResult> OnPostLinkLoginAsync(string provider)
{
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
// Request a redirect to the external login provider to link a login for the current user
var redirectUrl = Url.Page("./ExternalLogins", pageHandler: "LinkLoginCallback");
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User));
return new ChallengeResult(provider, properties);
}