How to get fresh information whether the user is logged in? - authentication

In this particular case I am only interested in getting info whether the user is logged in or not.
Following the answers How to enable/disable elements depending whether the user is logged in or not? I can fetch this information but there is one odd problem.
I use Blazor demo app which displays provided "LoginDisplay.razor" component at top, my own page uses following code:
#code {
protected override async Task OnInitializedAsync()
{
Console.WriteLine("Init");
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var authenticated = authState.User.Identity?.IsAuthenticated ?? false;
Console.WriteLine($"We are {authenticated}");
}
}
I have three steps when using this app:
I start the app, I am not logged in, "LoginDisplay" shows "log in" prompt and my code prints I am not authenticated -- good
I click on "log in", I log in, I am redirected back to my page -- "LoginDisplay" now shows my name, yet my code still prints I am not authenticated (this incorrect printout happens when using "NavMenu.razor" but it is fine when when using "Index.razor" -- see update below)
I hit F5 (reload the page) -- "LoginDisplay" shows my name, and the code prints I am authenticated -- as expected
So step (2) is problematic -- I should get the info I am logged in (because I am), and obviously "LoginDisplay" is capable of realizing this fact. So what logic should I add to my code that it too would show I am logged in?
Update After #enet reply I realized the outcome of the step (2) depends where you put this code -- if it placed in "NavMenu.razor" right after logging in the printout states you are not authorized. When I put the same code in "Index.razor" (home page) the printout correctly states I am logged in.
I can see that rendering "NavMenu" is almost immediate, while "Index" is rendered after some pause. So the problem is more "how to get fresh authentication info regardless the page is used?".

The following code describes how you can display your message in the MainLayout component:
<LoginDisplay />
<div>IsAuthenticated: #authenticated.ToString()</div>
#code {
bool authenticated;
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask {get; set;}
protected override async Task OnInitializedAsync()
{
Console.WriteLine("Init");
var authState = await authenticationStateTask;
var user = authState.User;
authenticated = user.Identity?.IsAuthenticated ?? false;
Console.WriteLine($"We are {authenticated}");
}
}
Note that the above code is executed only when the MainLayout component is initialized. In order to refresh the display of the authenticated message,
we need to executed the code once more. This may be achieved by doing the following:
In the Authentication component add to the RemoteAuthenticatorView component instance the event handler attribute OnLogInSucceeded that is called after the user has logged in, like this:
<RemoteAuthenticatorView Action="#Action" OnLogInSucceeded="RefreshMain" />
Add the event handler RefreshMain
private void RefreshMain()
{
NavigationManager.NavigateTo("mainlayout");
}
The event handler code simply redirect to the MainLayout
Add #inject directive for the NavigationManager, like this:
#inject NavigationManager NavigationManager;
Note that instead of the code above you can simply use the AuthorizeView component like this:
<LoginDisplay />
<div>
<AuthorizeView>
<Authorized>
<div>#context.User.Identity.IsAuthenticated</div>
</Authorized>
<NotAuthorized>
<div>#context.User.Identity.IsAuthenticated</div>
</NotAuthorized>
</AuthorizeView>
</div>
Update
You don't typically use AuthenticationStateProvider directly. Use the AuthorizeView component or Task approaches described later in this article. The main drawback to using AuthenticationStateProvider directly is that the component isn't notified automatically if the underlying authentication state data changes.
Source...
Don't bother yourself too much about rendering. Ordinarily, there is no issue unless your code re-render endlessly.
Anyhow, the following code sample describes how you should really do that:
Reverse the code in the Authentication component to its default state
Change the code you added in the MainLayout to the following
#inherits LayoutComponentBase
#inject AuthenticationStateProvider AuthenticationStateProvider
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<div class="main">
<div class="top-row px-4 auth">
<LoginDisplay />
<div>IsAuthenticated: #authenticated.ToString()</div>
<a href="https://learn.microsoft.com/aspnet/"
target="_blank">About</a>
</div>
<div class="content px-4">
#Body
</div>
</div>
</div>
#code {
private bool authenticated;
protected override void OnInitialized()
{
Task<AuthenticationState> _currentAuthenticationStateTask;
AuthenticationStateProvider.AuthenticationStateChanged +=
OnAuthenticationStateChanged;
_currentAuthenticationStateTask =
AuthenticationStateProvider.GetAuthenticationStateAsync();
OnAuthenticationStateChanged(_currentAuthenticationStateTask);
}
private void OnAuthenticationStateChanged
(Task<AuthenticationState> authenticationStateTask)
{
_ = InvokeAsync(async () =>
{
var authState = await authenticationStateTask;
var user = authState.User;
authenticated = user.Identity?.IsAuthenticated ?? false;
StateHasChanged();
});
}
}

Related

Oppeniddict - How to skip logout prompt?

I am using velusia sample, I want the client app to skip the log out prompt page, is there any specific way to achieve this, or should I implement it my self ?
How you handle logout requests is up to you. To trigger a redirection to the client application (when a post_logout_redirect_uri is set) without displaying a consent form, trigger an ASP.NET Core Logout operation pointing to OpenIddict:
// Returning a SignOutResult will ask OpenIddict to redirect the user agent
// to the post_logout_redirect_uri specified by the client application or to
// the RedirectUri specified in the authentication properties if none was set.
return SignOut(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties
{
RedirectUri = "/"
});
That said, I wouldn't recommend doing that: not requiring user consent or a form of anti-forgery protection - the id_token_hint can help, use AuthenticateAsync() to retrieve the principal from it - may make targeted DOS attacks possible.
According to your description, I suggest you could try to set a js code to automatically click the logout button in the server side.
More details, you could refer to below codes:
Modify the server's logout view as below:
#using Microsoft.Extensions.Primitives
<div class="jumbotron">
<h1>Log out</h1>
<p class="lead text-left">Are you sure you want to sign out?</p>
<form asp-controller="Authorization" asp-action="Logout" method="post">
#* Flow the request parameters so they can be received by the LogoutPost action: *#
#foreach (var parameter in Context.Request.HasFormContentType ?
(IEnumerable<KeyValuePair<string, StringValues>>) Context.Request.Form : Context.Request.Query)
{
<input type="hidden" name="#parameter.Key" value="#parameter.Value" />
}
<input class="btn btn-lg btn-success" id="Confirm" name="Confirm" type="submit" value="Yes" />
</form>
</div>
#section scripts{
<script>
$(document).ready(function() {
console.log("Fired");
document.getElementById("Confirm").click();
});
</script>
}
You can also change the HTTP method to GET instead of POST based on Velusia sample:
[HttpGet("logout")]
public async Task<IActionResult> LogoutPost()
{
await HttpContext.SignOutAsync(Clients.CmsApp);
await HttpContext.SignOutAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
return SignOut(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties
{
RedirectUri = "/"
});
}

.NET Core Identity as UI show login page if not signed in

I used Identity as UI to generate my login page,
My index.cshtml looks like this:
#page
#model IndexModel
#{
ViewData["Title"] = "Home page";
}
#inject SignInManager<IdentityUser> SignInManager
#if (SignInManager.IsSignedIn(User)) {
... my html ...
}
When the user is not logged in this shows a Login button on the top right that redirects to the login page, I see the button has
<a class="nav-link text-dark" href="/VoyDashIdentity/Account/Login">Login</a>
I want the index page to show the login page (or embedd it) if the user is not logged in without the need to click the login button, I tried:
#if (SignInManager.IsSignedIn(User)) {
... my html ...
} else {
Response.Redirect("VoyDash/Identity/Account/Login");
}
But this does not properly redirect to the area, all relative links (ie to bootstrap.css stop working correctly.
What is the best way to achieve this ?
By default , it will redirect user to the new login page after scaffolding .But you can always change the path using :
services.PostConfigure<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme,
opt => {
//configure your other properties
opt.LoginPath = "/Login";
});
Make sure above config is under AddDefaultIdentity , otherwise AddDefaultIdentity will override the configuration .

In Razor Pages, what is the recommended way to run an action with no UI

I want to implement a sign-out method which just signs the user out and redirects to the home page.
How would I set up the routing and would the code go into a controller, or do I need to create a new Page for this? Would seem odd as it doesn't require a UI.
asp-page specify the page name which the sign-out method is in , The asp-page-handler attribute is a companion to asp-page. asp-page-handler generates URLs that submit to each of the handler methods defined by a page. Change your _LoginPartial.cshtml as follows :
#if (SignInManager.IsSignedIn(User))
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello #User.Identity.Name!</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-page="/Index" asp-page-handler="SignOut">Logout</a>
</li>
}
IndexModel of Home page , the RedirectToPage parameter is combined with the path of the current page to compute the name of the destination page.
public class IndexModel : PageModel
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly ILogger<LogoutModel> _logger;
public IndexModel(SignInManager<ApplicationUser> signInManager, ILogger<LogoutModel> logger)
{
_signInManager = signInManager;
_logger = logger;
}
public void OnGet()
{
}
public async Task<IActionResult> OnGetSignOut()
{
await _signInManager.SignOutAsync();
_logger.LogInformation("User logged out.");
return RedirectToPage("/Index");
}
}
Result:
Reference for RedirectToPage :https://learn.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-3.0&tabs=visual-studio#url-generation-for-pages
Create a method named GetSignOut() then add a button or an <a></a> tag and add this to its attributes asp-route-handler="signout"
Finally in your GetSignOut() do your signout related stuff and finally return redirectToPage("pageName")
The original Razor Pages project template included an AccountController that had one method - Logout. The justification was that the Logout process did not require a UI, so it seemed odd to create a Razor Page for it when the purpose behind Razor Pages is to generate HTML.
If you scaffold Identity into an existing project, you now get a Logout.cshtml file, which redirects the use to a return URL if one is provided, or displays "you have been logged out" otherwise.
So both approaches tend to suggest that the recommendation is to use Razor Pages for HTML generation, and controllers for non-HTML related services.

How to sign out over HttpContext in server-side Blazor

I access the HttpContext in a Blazor server-side view to manually log out. I added this line to Startup.cs: services.AddHttpContextAccessor(); and inject it in the view with #inject IHttpContextAccessor HttpContextAccessor.
I've got a log out button which tries to execute this code:
await HttpContextAccessor.HttpContext.SignOutAsync("Cookies");
but I get the following error message:
System.InvalidOperationException: 'Headers are read-only, response has already started.'
How can I prevent this error?
If you scaffolded Identity and overridden the old "LogOut.cshtml" from when you created the project via template, the Logout button won't logout. Assume you've used the default IdentityUser model. I ran into this issue and just wanted to add this if anyone else had this problem as well. I'm using Blazor Server with .Net 5.0.3's template. You can remove the Logout.cshtml.cs after as well.
Replace this \Areas\Identity\Pages\Account\LogOut.cshtml
#page
#model LogoutModel
#{
ViewData["Title"] = "Log out";
}
<header>
<h1>#ViewData["Title"]</h1>
#{
if (User.Identity.IsAuthenticated)
{
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="#Url.Page("/", new { area = "" })" method="post">
<button type="submit" class="nav-link btn btn-link text-dark">Click here to Logout</button>
</form>
}
else
{
<p>You have successfully logged out of the application.</p>
}
}
</header>
Replace with
#page
#using Microsoft.AspNetCore.Identity
#attribute [IgnoreAntiforgeryToken]
#inject SignInManager<IdentityUser> SignInManager
#functions {
public async Task<IActionResult> OnPost()
{
if (SignInManager.IsSignedIn(User))
{
await SignInManager.SignOutAsync();
}
return Redirect("~/");
}
}
This tripped me up too, but you need the logout functionality to be on a Razor Page (not a Blazor component). Create a Logout page and put your logout code in the OnGetAsync() method.
Your logout button can then link to the logout page.
http://lightswitchhelpwebsite.com/Blog/tabid/61/EntryId/4316/A-Demonstration-of-Simple-Server-side-Blazor-Cookie-Authentication.aspx - this is a helpful example
Don't use IHttpContextAccessor.
I guess that you're using ASP.NET Core Blazor authentication and authorization new system. If not, then start with it right now. Live is too short to be wasted over other things. This is the best product created so far for Blazor's authentication and authorization, and it is based on the Identity UI (This is not Blazor, of course). Additionally, there are a couple of Components which enable controlling the flow of authentication and authorization in your application, such as displaying a "Log in" button and a "Log out" button in your layout, interchangeably altering depending on your authentication state, etc.
Please, go to this page and start learning this excellent system, and then come here for specific issues you face:
https://learn.microsoft.com/en-us/aspnet/core/security/blazor/?view=aspnetcore-3.0&tabs=visual-studio
Hope this helps...

Multi friend selector and authentication dialog in PageTab app

i have created a page tab app for which i have set all the parameters under Auth Dialog in the app settings.
Now when i send a friend request through my app, and my friend clicks on the app request, he is able to see the authentication box describing the app and the permissions requested etc.
But if anyone visits my page on which i have added the app, and clicks on the app from there, it directly takes the user to the page tab without displaying the auth box.
Is this how it is supposed to work from a page? is it possible to display the auth box for a user coming to the app from a page?
Secondly, i have added a multi friend selector which opens by default in a popup as it is supposed to.
Is it possible to display it in the page itself instead of a popup?
I tried adding the display: 'page' option but it din work.
I have used the same code from : https://developers.facebook.com/docs/reference/dialogs/requests/
...
<body>
<div id="fb-root"></div>
<p>
<input type="button"
onclick="sendRequestToRecipients(); return false;"
value="Send Request to Users Directly" />
<input type="text" value="User ID" name="user_ids" />
</p>
<p>
<input type="button"
onclick="sendRequestViaMultiFriendSelector(); return false;"
value="Send Request to Many Users with MFS" />
</p>
<script>
window.fbAsyncInit = function() {
FB.init({
appId : XXXXXXXXXXX,
status : true,
cookie : true,
xfbml : true,
oauth : true,
});
};
(function(d){
var js, id = 'facebook-jssdk';
if (d.getElementById(id)) {
return;
}
js = d.createElement('script');
js.id = id;
js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
d.getElementsByTagName('head')[0].appendChild(js);
}(document));
</script>
<script>
function sendRequestToRecipients() {
var user_ids = document.getElementsByName("user_ids")[0].value;
FB.ui({method: 'apprequests',
message: 'My Great Request',
to: user_ids,
}, requestCallback);
}
function sendRequestViaMultiFriendSelector() {
FB.ui({method: 'apprequests',
message: 'My Great Request'
}, requestCallback);
}
function requestCallback(response) {
// Handle callback here
}
</script>
...
Any help in this regard would be very much appreciated.
Is this how it is supposed to work from a page?
Yes. Authenticated referrals don’t work when the app is accessed from a page directly.
is it possible to display the auth box for a user coming to the app from a page?
Of course – analyze the signed_request parameter, and react accordingly (meaning, display the auth dialog yourself, server- or client-side).