Blazor - when open the main page without being authenticated never redirect to login page - authorization

I'm using Blazor with .net 6. In the app.razor page I have the defaults for AutorizedRouteView and NotAuthorized. It's working fine, except for main page. If does not have any authenticated, should redirect to login page, but it's always detecting the NotAuthorized page.
App.razor
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)">
<Authorizing>
<text> Please wait, we are authorizing the user. </text>
</Authorizing>
<NotAuthorized>
<div class="text-center">
<img src="/assets/img/restrito.png" class="rounded" alt="não encontrado">
<br /><br>
<h1 class="fw-bolder">Não possui acesso a esta página.</h1>
</div>
</NotAuthorized>
</AuthorizeRouteView>
</Found>
<NotFound>
#{
<div class="text-center">
<img src="/assets/img/404.png" class="rounded" alt="não encontrado">
<br /><br>
<h1 class="fw-bolder">Página não encontrada.</h1>
Retornar à página principal
</div>
}
</NotFound>
</Router>
</CascadingAuthenticationState>
AppRouteView.cs
public class AppRouteView : RouteView
{
[Inject]
private NavigationManager _navigationManager { get; set; }
[Inject]
private IAccountService _accountService { get; set; }
protected override void Render(RenderTreeBuilder builder)
{
// var authorize = Attribute.GetCustomAttribute(RouteData.PageType, typeof(AuthorizeAttribute)) != null;
if (_accountService.User == null)
{
_navigationManager.NavigateTo("account/login");
}
else
{
base.Render(builder);
}
}
}
Index.razor
#page "/"
#using VidaConfortoApplication.Client.Services.Interfaces
#attribute [Authorize]
#inject IAccountService _accountService
<div class="p-4">
<div class="container">
<h1>Olá #_accountService.User?.Name!</h1>
<p>Está autenticado na aplicação GesSad!</p>
<p><NavLink href="users">Gerir utilizadores</NavLink></p>
</div>
</div>
Login.razor page does not have any [Authorize] attriute

You need to handle the redirect to the login page yourself, within the <NotAuthorized> tag. Check whether the user is Authenticated, and if not, redirect them to the login page. Here is an example:
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)">
<NotAuthorized>
#if (!context.User.Identity.IsAuthenticated)
{
<RedirectToLogin />
}
else
{
<p>You are not authorized to access this resource.</p>
}
</NotAuthorized>
</AuthorizeRouteView>
Note that this also handles the difference between being Authenticated and being Authorized. A user may be logged in to a site (authenticated), without having permissions to access certain resources (authorized).
In your <RedirectToLogin /> component, just redirect the user in OnInitialized() or OnInitializedAsync(). Here is a simple example:
#inject NavigationManager _nav
#code {
protected override void OnInitialized()
{
_nav.NavigateTo("/account/login");
}
}
If you want the user to be returned to the page they initially tried to enter, get the target uri before redirecting to login, and pass it as a query parameter to the login page (to handle the redirection after logging in). Then, the <RedirectToLogin /> component could look like this:
#inject NavigationManager _nav
#code {
protected override void OnInitialized()
{
var path = _nav.ToBaseRelativePath(_nav.Uri);
_nav.NavigateTo($"/account/login?returnUrl={path}");
}
}

Related

Blazor Secure Api

I am developing an Application in Microsoft Blazor. I have secured all the UI Pages using a custom AuthenticationStateProvider class which searches for a cookie on the browser.
The by restricting the #Body on the MainLayout.razor every page is secured and not readable when the user is not autorized.
<div class="page">
<Sidebar />
<div class="main">
<Header />
<article class="content px-4">
<AuthorizeView>
<NotAuthorized>
<div class="row">
<div class="col-md-4">
<p>Please sign in to use the Platform...</p>
</div>
</div>
</NotAuthorized>
<Authorized>
#Body
</Authorized>
</AuthorizeView>
</article>
</div>
</div>
The issue is that the ./api endpoint is still accessible for not authorized users as the controllers are still active.
[Route("api/User")]
[ApiController]
public class Controller_User : ControllerBase
{
private readonly Interface_User _IUser;
public Controller_User(Interface_User iUser)
{
_IUser = iUser;
}
[HttpGet, Route("/api/user/view")]
public async Task<List<User>> GetUsers()
{
try { return await Task.FromResult(_IUser.GetUsers()); }
catch { throw; }
}
}
Any ideas how we can secure all ./api urls at once like the razor pages?
Example using inheritance to apply Authorization to controllers.
Two abstract controllers
[Authorize]
public abstract class AuthorizedController: Controller {}
[Authorize(Policy = "AdminOnly")]
public abstract class AdminOnlyAuthorizedController: Controller {}
And then some implementations
public sealed class WeatherForecastController: AuthorizedController {
//....
}
public sealed class WeatherLocationController: AuthorizedController {
//....
public class MyAdminController: AdminOnlyAuthorizedController {
//....
}

MAUI AAD Authentication ClaimsPrincipal with IsAuthenticated always false

I have a MAUI app running on localhost and when you start the app, it triggers microsoft login, after the user sucessfull login I catch the ClaimsPrincipal and IsAuthenticated is always false. According to my Main.razor component if the user is not Authenticated it calls the RedirectToLogin Component again and I don't know how to Authorize my logged in user in the app so it might have somethin to do with the IsAuthenticated being false.
I have seen some solutions and they say you have to build ClaimsPrincipal passin the authentication type as parameter to the ClaimsIdentity like this:
new ClaimsPrincipal(new Identity("something"))
The problem is that I get already the ClaimsPrincipal from AAD só I dont know what I should do because I have no way to see the AAD because it is taken care by other team.
This is my Main.razor component:
<Router AppAssembly="#typeof(Main).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)">
<Authorizing>
Authorizing...
</Authorizing>
<NotAuthorized>
<RedirectToLogin />
</NotAuthorized>
</AuthorizeRouteView>
</Found>
<NotFound>
<CascadingAuthenticationState>
<LayoutView Layout="#typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</CascadingAuthenticationState>
</NotFound>
</Router>
This is RedirectToLogin component:
#using Microsoft.AspNetCore.Components.Authorization
#using OfficeManagerApp.Areas.Services.Implementations
#inject AuthenticationStateProvider AuthenticationStateProvider
#inject NavigationManager NavigationManager
<div class="loader loader-bouncing"><span>Redirecting...</span></div>
#code {
protected override async Task OnInitializedAsync()
{
await ((ExternalAuthStateProvider)AuthenticationStateProvider)
.LogInAsync();
NavigationManager.NavigateTo("/", forceLoad: true);
}
}
and here is where I have the break point to see the CLaimsPrincipal inside the ExternalAuthStateProvider class:
Here are the tutorials I followed for this.
PlatformService tutorial
ExternalAuthStateProvider tutorial
I also have a solution using just the PlatformService tutorial with the same problem.
After a while I found the solution!
Basically you receive a ClaimsPrincipal from AAD but you have to create your own ClaimsPrincipal inside the app using the claims from the AAD ClaimsPrincipal.
In ExternalAuthStateProvider.cs, LoginWithExternalProviderAsync() method I did the following:
private async Task<ClaimsPrincipal> LoginWithExternalProviderAsync()
{
var authenticationResult = await _platformService.GetAuthenticationResult();
var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(authenticationResult.ClaimsPrincipal.Claims, "Basic"));
return await Task.FromResult(authenticatedUser);
}
You just need to do this and then it works!!
Extra -----------
To improve the flow login logout, I created a LoginPage.razor:
#page "/login"
#using Microsoft.AspNetCore.Authorization
#using Microsoft.AspNetCore.Components.Authorization
#using OfficeManagerApp.Areas.Services.Implementations
#attribute [AllowAnonymous]
#inject AuthenticationStateProvider AuthenticationStateProvider
#inject NavigationManager NavigationManager
<button #onclick="Login">Log in</button>
#code
{
public async Task Login()
{
await ((ExternalAuthStateProvider)AuthenticationStateProvider)
.LogInAsync();
NavigationManager.NavigateTo("/");
}
}
Changed the RedirectToLogin,razor:
#inject NavigationManager NavigationManager
<div class="loader loader-bouncing"><span>Redirecting...</span></div>
#code {
protected override void OnInitialized()
{
NavigationManager.NavigateTo("/login");
}
}
And added a logout method:
private void Logout(){
((ExternalAuthStateProvider)AuthenticationStateProvider)
.Logout();
}
Also did some changes to my Main.razor:
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(Main).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)">
<Authorizing>
Authorizing...
</Authorizing>
<NotAuthorized>
#if (!context.User.Identity.IsAuthenticated)
{
<RedirectToLogin />
}
else
{
<p>You are not authorized to access this resource.</p>
}
</NotAuthorized>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="#typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
Note don't forget to add to your _Imports.razor:
#using Microsoft.AspNetCore.Components.Authorization

get back to login page again after click on forgot password in Blazor web assembly app

I have a "Blazer Web assembly" app with a login page(Component) and a Forgot Password page(Component).
When I click on the "Forgot Password" link on the Login page, instead of sending me to the "forgot password" page, it sends me back again to the login page.
Here is my code:
App.Razor
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)">
<NotAuthorized>
</NotAuthorized>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="#typeof(MainLayout)">
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
MainLayout.razor:
#inherits LayoutComponentBase
<AuthorizeView>
<Authorized>
<NavBar>
<NavBarLeft>....
#code {
[CascadingParameter]
Task<AuthenticationState> AuthenticationState { get; set; }
protected override async Task OnParametersSetAsync()
{
navBarLeftInjectableMenu.SetContent(null, false);
if (!(await AuthenticationState).User.Identity.IsAuthenticated)
{
NavigationManager.NavigateTo("/login");
}
}
Forgot Password Page:
#page "/ForgotPass"
#layout AuthLayout
<div class=....
Login Page:
#layout AuthLayout
#page "/LoginX"
#inject NavigationManager NavigationManager
<div class="hold-transition login-page">
<div class="login-box">
<button #onclick="ForgotPassword" class="btn btn-primary btn-block">Forgot Password</button>
</div>
</div>
#code {
void ForgotPassword()
{
NavigationManager.NavigateTo("/ForgotPassX", true);
}
}
AuthLayout.razor:
#inherits LayoutComponentBase
<div class="main">
<div class="content px-4">
#Body
</div>
</div>
HttpInterceptor:
private async Task InterceptBeforeSendAsync(object sender, HttpClientInterceptorEventArgs e)
{
var absolutePath = e.Request.RequestUri != null? e.Request.RequestUri.AbsolutePath : string.Empty;
if (!absolutePath.Contains("token") && !absolutePath.Contains("acc"))
{
var token = await _refreshTokenService.TryRefreshToken();
if (!string.IsNullOrEmpty(token))
{
e.Request.Headers.Authorization =
new AuthenticationHeaderValue("bearer", token);
}
}
}
You have 2 options:
1/ Put #attribute [AllowAnonymous] in your forgot password page.
2/ Create a different Layout with no Authorize required then use that for your forgot password page like #layout AnonymousLayout
This is because you call the OnParametersSetAsync method every time you request, and your judgment condition is forcibly directed to the Login page.
You can add an extra judgment to bypass the IsAuthenticated check.
For example: Use the request url to determine whether to execute this check:
protected override async Task OnParametersSetAsync()
{
string currentUrl = NavigationManager.Uri;
if (!currentUrl.Contains("ForgotPass")){
navBarLeftInjectableMenu.SetContent(null, false);
if ((!(await AuthenticationState).User.Identity.IsAuthenticated))
{
NavigationManager.NavigateTo("/Login");
}
}
}

Redirect to login in Blazor Server Azure B2C Authentication

Trying to redirect the unauthenticated users to login page instead of showing a blank Index page.
I tried to modify app.razor to redirect as below:
<NotAuthorized>
#if (!context.User.Identity.IsAuthenticated)
{
<RedirectToLogin />
}
else
{
<p>
You are not authorized to access
this resource.
</p>
}
</NotAuthorized>
That didn't work. Breakpoint on " #if (!context.User.Identity.IsAuthenticated)" never gets hit.
I also tried to add #code section to MainLayout.razor as below:
[CascadingParameter] protected Task<AuthenticationState> AuthStat { get; set; }
protected async override Task OnInitializedAsync()
{
base.OnInitialized();
var user = (await AuthStat).User;
if (!user.Identity.IsAuthenticated)
{
navMan.NavigateTo($"authentication/login?returnUrl={Uri.EscapeDataString(navMan.Uri)}");
}
}
THat goes in some kind of redirect loop i assume because I get a error saying
"Request filtering is configured on the Web server to deny the request because the query string is too long."
and Requested URL:
https://localhost:44385/authentication/login?returnUrl=https%3A%2F%2Flocalhost%3A44385%2Fauthentication%2Flogin%3FreturnUrl%3Dhttps%253A%252F%252Flocalhost%253A44385%252Fauthentication%252Flogin%253FreturnUrl%253Dhttps%25253A%25252F%25252Flocalhost%25253A44385%25252Fauthentication%25252Flogin%25253FreturnUrl%25253Dhttps%2525253A%2525252F%2525252Flocalhost%2525253A44385%2525252Fauthentication%2525252Flogin%2525253FreturnUrl%2525253Dhttps%252525253A%252525252F%252525252Flocalhost%252525253A44385%252525252Fauthentication%252525252Flogin%252525253FreturnUrl%252525253Dhttps%25252525253A%25252525252F%25252525252Flocalhost%25252525253A44385%25252525252Fauthentication%25252525252Flogin%25252525253FreturnUrl%25252525253Dhttps%2525252525253A%2525252525252F%2525252525252Flocalhost%2525252525253A44385%2525252525252Fauthentication%2525252525252Flogin%2525252525253FreturnUrl%2525252525253Dhttps%252525252525253A%252525252525252F%252525252525252Flocalhost%252525252525253A44385%252525252525252Fauthentication%252525252525252Flogin%252525252525253FreturnUrl%252525252525253Dhttps%25252525252525253A%25252525252525252F%25252525252525252Flocalhost%25252525252525253A44385%25252525252525252Fauthentication%25252525252525252Flogin%25252525252525253FreturnUrl%25252525252525253Dhttps%2525252525252525253A%2525252525252525252F%2525252525252525252Flocalhost%2525252525252525253A44385%2525252525252525252Fauthentication%2525252525252525252Flogin%2525252525252525253FreturnUrl%2525252525252525253Dhttps%252525252525252525253A%252525252525252525252F%252525252525252525252Flocalhost%252525252525252525253A44385%252525252525252525252Fauthentication%252525252525252525252Flogin%252525252525252525253FreturnUrl%252525252525252525253Dhttps%25252525252525252525253A%25252525252525252525252F%25252525252525252525252Flocalhost%25252525252525252525253A44385%25252525252525252525252Fauthentication%25252525252525252525252Flogin%25252525252525252525253FreturnUrl%25252525252525252525253Dhttps%2525252525252525252525253A%2525252525252525252525252F%2525252525252525252525252Flocalhost%2525252525252525252525253A44385%2525252525252525252525252F
Please can someone suggest how a user can be redirected to the login page if not authenticated in Blazor Server side project with Azure B2C Authentication?
You can force the user to login with the below changes
Approach 1:
_Host.Cshtml Add the below code
#using Microsoft.AspNetCore.Authorization
#attribute [Authorize]
App.razor
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)">
<NotAuthorized>
<h4>Not authorized.</h4>
</NotAuthorized>
<Authorizing>
<h4>Authentication in progress...</h4>
</Authorizing>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="#typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
MainLayout.razor
#inherits LayoutComponentBase
<div class="sidebar">
<NavMenu />
</div>
<div class="main">
<AuthorizeView>
<NotAuthorized></NotAuthorized>
<Authorized>
<div class="top-row px-4 auth">
<LoginDisplay />
About
</div>
<div class="content px-4">
#Body
</div>
</Authorized>
</AuthorizeView>
</div>
Approach 2:
Another way For redirecting the user to login page Please make the below changes
_Host.Cshtml
<environment include="Staging,Production">
<component render-mode="ServerPrerendered" type="typeof(App)" />
</environment>
<environment include="Development">
<component render-mode="Server" type="typeof(App)" />
</environment>
Create RedirectToLogin.razor
#inject NavigationManager Navigation
#code {
protected override Task OnAfterRenderAsync(bool firstRender)
{
Navigation.NavigateTo("/AzureADB2C/Account/SignIn");
return base.OnAfterRenderAsync(firstRender);
}
}
MainLayout.razor
<div class="main">
<div class="top-row px-4 auth">
<LoginDisplay />
About
</div>
<div class="content px-4">
<AuthorizeView>
<Authorized>
#Body
</Authorized>
<NotAuthorized>
<Blazor_B2C.Pages.RedirectToLogin></Blazor_B2C.Pages.RedirectToLogin>
</NotAuthorized>
</AuthorizeView>
</div>
</div>

how to change Blazor WASM identity net core 3.1 messages "You are logged out", "checking login state" and "authorizing"?

I need to know how to personalize and/or change language for this messages, I suppose it has to do with IdentityServer4.
Any ideas?
What you are looking for is the RemoteAuthenticatorView .
You can find more details for your answer in the official documentation.
#page "/security/{action}"
#using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="#Action">
#*authentication/login*
<LoggingIn></LoggingIn>
#*authentication/login-callback*
<CompletingLoggingIn></CompletingLoggingIn>
#*authentication/login-failed*
<LogInFailed></LogInFailed>
#*authentication/logout*
<LogOut></LogOut>
#*authentication/logout-callback*
<CompletingLogOut></CompletingLogOut>
#*authentication/logout-failed*
<LogOutFailed></LogOutFailed>
#*authentication/logged-out*
<LogOutSucceeded></LogOutSucceeded>
#*authentication/profile*
<UserProfile></UserProfile>
#*authentication/register*
<Registering></Registering>
</RemoteAuthenticatorView>
#code{
[Parameter]
public string Action { get; set; }
}
It is necessary to add some tags in Authentication.razor component:
#page "/authentication/{action}"
#using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="#Action">
<LogInFailed>
<div class="alert alert-danger" role="alert">#strLogInFailed</div>
</LogInFailed>
<LogOut>
<div class="alert alert-info" role="alert">#strLogOut</div>
</LogOut>
<LogOutSucceeded>
<div class="alert alert-success" role="alert">#strLogOutSucceeded</div>
</LogOutSucceeded>
<LoggingIn>
<div class="alert alert-info" role="alert">#strLoggingIn</div>
</LoggingIn>
<CompletingLoggingIn>
<div class="alert alert-success" role="alert">#strCompletingLoggingIn</div>
</CompletingLoggingIn>
</RemoteAuthenticatorView>
#code {
[Parameter] public string Action { get; set; }
string strLogInFailed = "Your login was not successful.";
string strLogOut = "Trying to close your session.";
string strLogOutSucceeded = "Your session has been closed successfully.";
string strLoggingIn = "Redirecting to the login screen.";
string strCompletingLoggingIn = "Your login was successful.";
}
I found how to do it in: Custom Authentication User Interface which, by the way, explains a lot on
How to Secure Blazor WebAssembly with IdentityServer4
After read it, I also check the RemoteAuthenticatorView Class
Even accomplished this, still persist the "Authorizing..." message.
To change the message "Authorizing..."
Need to add
<Authorizing>
<h1>Authorization in progress</h1>
<p>Only visible while authorization is in progress.</p>
</Authorizing>
to file App.razor
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData"
DefaultLayout="#typeof(MainLayout)">
<NotAuthorized>
<h1>Sorry</h1>
<p>You're not authorized to reach this page.</p>
<p>You may need to log in as a different user.</p>
</NotAuthorized>
<Authorizing>
<h1>Authorization in progress</h1>
<p>Only visible while authorization is in progress.</p>
</Authorizing>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="#typeof(MainLayout)">
<h1>Sorry</h1>
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
You can find more details official documentation.
You can use the <Authorizing> tag to override the default text.
<AuthorizeView>
<Authorizing>
<p class="authorizing">Authorizing...</p>
</Authorizing>
<Authorized>
<p class="authorized">Welcome, #context.User.Identity.Name!</p>
</Authorized>
<NotAuthorized>
<p class="not-authorized">You're not authorized, #(context.User.Identity.Name ?? "anonymous")</p>
</NotAuthorized>
</AuthorizeView>
Source:
https://github.com/dotnet/aspnetcore/blob/1ed72f8f5f14dfc5a4ebc9d5d116d63792caa7fc/src/Components/test/testassets/BasicTestApp/AuthTest/AuthorizeViewCases.razor