HttpContext is always null [duplicate] - c#

Is it possible to use SignInManager without having some HTTPContext ? I'm making a Blazor server-side app and I need to make end-users signed in using PasswordSignInAsync() method of SignInManager.
If there is other ways to do it with cookies or something else, I'll take it too, as long they are "clean" methods.
If I could get also an explanation on how to configure for Startup.cs the solution, it will be perfect.

I'd strongly suggest that you use the Identity authentication system in your app.
You should not try to create any authentication system instead. This is something free, and is set up within a couple of minutes. Don't waste your time to do something so complicated. Instead learn what are the Blazor Authorization components and how to use them in your application. They are great.
Note: The communication between Blazor Server and its client-side is done through SignaleR. HttpContext is not available most of the time. Do not try to use the HttpContext. Actually, you can't because whenever you try to access it, it is null.
The following describe how you can create a Blazor Server App with the Identity UI:
Start creating a Blazor App
In the window titled Create a new Blazor app do this:
Select Blazor Server App
On the right side of the window is a link with the text Change, under the Authentication title. Tap the link and select Individual User Account. Press OK...
Click the "Create" button
Visual Studio has created for you a Blazor Server App with Identity UI to authenticate your users. Note that the Identity UI is actually the Razor Pages Identity UI used with Razor Pages and MVC. You may scaffold one or more items from this system if you need to make some changes. Go to solution explorer and verify that Visual Studio has added two folders named Areas and Data. She also has configured your Startup class with the necessary services to mange the Identity UI. What you want now is to create the database where user names, roles, claims, etc. are stored. To create the database you should use migrations. Here is a link telling you how to run the commands that do the job for you. Now you can run your app, register to the web site, login, logout, etc.
Good luck.

To use SignInManager in server-side Blazor,
First, addAddIdentity service into Startup.cs
services.AddIdentity<IdentityUser, IdentityRole>(options => {
options.SignIn.RequireConfirmedAccount = false;
})
.AddEntityFrameworkStores<AppDBContext>()
.AddDefaultTokenProviders();
Then, Inject SignInManager into your razor page
#using Microsoft.AspNetCore.Identity
#inject SignInManager<IdentityUser> SignInManager
#inject NavigationManager navManager;
<!-- Put Login Form Here -->
#code{
protected async Task Submit()
{
var SignInResult= await SignInManager.PasswordSignInAsync("username", "password", true, lockoutOnFailure: false);
if (SignInResult.Succeeded)
{
navManager.NavigateTo("Home", false);
}
}
}

There's another way that nobody has mentioned, so thought I might as well: you could use middleware to login, since middleware carries the full html request and can modify it (for example, by logging in), before it follows the pipeline to Blazor rendering. I use this method for creating temporary guest users for my gaming pages-- which works because the guest doesn't need to type in and submit their own password
as plaintext.
The logistics of making the login secure would probably not be easier than following #enet's advice of using the built-in scaffolded Authentication system, or of logging in using UserManager and re-navigating to your page. You could, however, use a modal box to sign in, rather than having to navigate to the MVC-style login page.

Related

User.Claims is empty for every page outside of Areas/Identity

Authorization doesn't work on Razor pages outside of Areas/Identity, cannot figure out why:
Steps to reproduce:
Fresh app. ASP.NET Core Blazor WASM Hosted
Create page: Areas/Identity/Index2.cshtml
Create page: Areas/NewArea/NewPage.cshtml
Register and Log in.
Access Index2 and NewPage and break in OnGet method while debugging.
Index2: property User.Claims contains multiple claims.
NewPage: property User.Claims is empty
Why the authorization is not "propagated" into NewPage?
When I set #page "/Identity/NewPage" it works so it is somehow related to Identity Area (??).
EDITS:
(Placing page into Pages folder of the server project results the same (no auth))
Reason why do I care:
For quick loading speed I want my index page (Index.cshtml) to be razor page (not Blazor wasm). I need to display login status on index page.
More research:
I have found exactly the same (unresolved, but closed) issue in aspnetcore repo. https://github.com/dotnet/aspnetcore/issues/34080
This issue describe the same scenario I have: Authorise normal Razor Pages in a Blazor WebAssemby App? and there is also a solution:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
})
Another same issue on SO: Blazor WebAssembly with server side Razor page authorize problem
But I still don't understand why is this happening.
EDIT - Solution
The solution with changing DefaultAuthenticateScheme didn't work well after all - it break authorization for api controllers and wasm app.
This may be caused by my setup - I am not actually using server app for "hosting" the Blazor. Blazor app is just SPA using server app for auth and api.
Anyway: Instead of changing default schema for "everything" I change it only for particular .cshtml pages:
[AllowAnonymous]
[Authorize(AuthenticationSchemes = "Identity.Application")]
With that ↑ everything works. But you have to paste this into every .cshtml page... Or - you can configure it in Program.cs:
//add policy which uses ApplicationScheme
services.AddAuthorization(config =>
{
config.AddPolicy("RazorPagesAuthPolicy",
policyBuilder =>
{
policyBuilder.AddAuthenticationSchemes(IdentityConstants.ApplicationScheme);
policyBuilder.RequireAssertion(context => true);//has to be here
});
}
//...
services.AddAuthentication()//keep this
.AddIdentityServerJwt();
//...
services.AddRazorPages(cfg =>
{
cfg.Conventions.AddFolderApplicationModelConvention("/", mo =>
{//add authorize and allowAnonymous filters
mo.Filters.Add(new AuthorizeFilter("RazorPagesAuthPolicy"));
mo.Filters.Add(new AllowAnonymousFilter());
}
);
}
);
which I am not sure if it is way to go
As I've previously mentioned, you can either use cookies authentication or Jwt authentication in order to access the ClaimsPrincipal from the Server project. The solution proposed is fine, and it is used to configure the cookies middleware used by the identity system. Incidentally, it should be:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
}).AddIdentityServerJwt();
But I still don't understand why is this happening
Do you mean why the code above works ?
The code above works because it configures the cookies middleware to authenticate the user, tells the Identity System what scheme to use, and how to challenge the user. Understand this, the Server side of your app is not authenticated, unless you configure it to perform authentication, which differs from the authentication performed when you are re-directed from the WebAssembly Blazor App. I'll try to clarify this by giving you this example:
If you created a Blazor Server App with individual accounts, and then added client SignalR to enable a chat system in your app, and you want to secure your hub object with the Authorize attribute, an authenticated user to your Blazor Server App won't be able to access secured end points on the hub, unless he is authenticated on the hub as well. How to authenticate the user ? Simply by passing the application cookie to the hub. This is more or less simialr to the issue under discussion; that is, though the Identity folder and its content are authenticated (and authorized), the hosting Server App is not.
Note: Its hard to internalized this on first shot, but rest assured that the proposed code not only works but is also 100% kosher.

Custom blazor server-side authentication

I am new to blazor and stumbled across a problem when trying to implement authentication in my app. For now, I need to store all my users' data in JSON file and do not have a SQL DB to refer to, which, as I understand is needed to implement authentication.
As for now I have a list of users and check if the user is in my list of signed in accounts, then call markAsAuthenticated
public void markUserAsAuthenticated(string emailAddress)
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, emailAddress),
}, "someType");
var user = new ClaimsPrincipal(identity);
NotifyAuthenticationStateChanged (Task.FromResult(new AuthenticationState(user)));
}
I would like to add roles or claims now to be able to include them in the component, but find it difficult to understand, what the following steps should be.
Most of the tutorials use Authorization services provided by VisualStudio, but we work in VSCode and I would like to implement this myself, so I can use the list of users I have.
I would also like to find out if I can use cookies or JWT in my app and if there are any good tutorials for that or suggestions considering work with VSCode and server-side work, thanks in advance!
If you want to use cookies you need to use a razor page (.cshtml). You can have a razor page inside a Blazor application. It's the same code you'd use in a traditional razor application (pre-Blazor).
There's nothing Visual Studio specific to this that I know of.
See https://blazorhelpwebsite.com/ViewBlogPost/36

Is there a way to utilize the [Authorize] attribute within a condition in the CSHTML of a razor page?

I'm working on a dotnet core/js application in which all of the frontend code including authentication page is produced with a JS framework where the bundled frontend is injected into a single Razor page. The frontend accesses all dotnet core logic from controller api endpoints. The authentication utilize bearer tokens and the [Authorize(role='whatever')] is used in several controllers.
For some reason, a single javascript plugin is injected into the app from the razor page. I basically need this plugin not to be visible before the user is authenticated. It's not a matter of "secret" content but the plugin just isn't useful before the user is authenticated. Is there a simple way for me to find out if the user is authorized within the CSHTML of the code as with the controller attributes? I've been searching around and found nothing very useful. Not an expert on c# or dotnet core (mostly been working on the frontend) and simplest possible solution would be appreciated. Don't know if I've missed any relevant information. If so, tell me!
Depending on the complexity of your condition, you have a few ways to authorize the user.
If you just want to verify that the user is authenticated, i.e. that they are successfully logged in, regardless of who the user is, then you can just check against User.Identity.IsAuthenticated:
#if (User.Identity.IsAuthenticated)
{
<p>User is signed in.</p>
}
If you want to verify that the user has a specific role, then you can also operate on the User principal object directly and just check for that specific role:
#if (User.IsInRole("role-name"))
{
<p>User is in role <em>role-name</em>.</p>
}
Otherwise, if you want to utilize the full capability of the authorization system in ASP.NET Core, you can also inject the IAuthorizationService and authorize the user against a configured authorization policy:
#inject IAuthorizationService _authorizationService
#if ((await _authorizationService.AuthorizeAsync(User, "policy-name")).Succeeded)
{
<p>User was authorized against policy <em>policy-name</em>.</p>
}

how to implement google login in .net core without an entityframework provider

I am implementing Google login for my .net core site.
In this code
var properties = signInManager.ConfigureExternalAuthenticationProperties("Google", redirectUrl);
return new ChallengeResult("Google", properties);
I need a signInManager which is (by the code example) this:
private SignInManager<AppUser> signInManager;
I inject it via the constructor, and then I get this error:
Unable to resolve service for type 'Microsoft.AspNetCore.Identity.SignInManager1[AppUser]' while attempting to activate 'AccountController'.
Googling learnt that I should include this
services.AddIdentity<AppUser, IdentityRole>()
.AddDefaultTokenProviders();`
But that gives me this error:
Unable to resolve service for type 'Microsoft.AspNetCore.Identity.IUserStore1[AppUser]' while attempting to activate 'Microsoft.AspNetCore.Identity.AspNetUserManager1[AppUser]'.
And at that moment, I get the advice to add this:
.AddEntityFrameworkStores<ApplicationDbContext>()
But then I'm lost, because why does the SignInManager need a IUserStore, and should I add a
UserStore and a DBContext and an EntityFramework store, when I will not be using that (for my Google login)?
So the question is: can I also do my Google login without the Entityframework store?
If all you want to do is sign-in with Google, there's no need for SignInManager, UserManager or ASP.NET Core Identity itself. To achieve this, we first need to configure the Authentication services. Here's the relevant code for this, which I'll explain after:
Startup.cs
services
.AddAuthentication(o =>
{
o.DefaultScheme = "Application";
o.DefaultSignInScheme = "External";
})
.AddCookie("Application")
.AddCookie("External")
.AddGoogle(o =>
{
o.ClientId = ...;
o.ClientSecret = ...;
});
The call to AddAuthentication configures a DefaultScheme, which ends up being used as both the Application scheme and the Challenge scheme. The Application scheme is used when attempting to authenticate the user (are they signed in?). The Challenge scheme is used when a user is not signed in but the application wants to provide the option to do so. I'll discuss the DefaultSignInScheme later.
The two calls to AddCookie add cookie-based authentication schemes for both Application (our Application scheme) and External (our SignIn scheme). AddCookie can also take a second argument, that allows for configuration of e.g. the corresponding cookie's lifetime, etc.
With this in place, the challenge process will redirect the user over to /Account/Login (by default - this can be configured via the cookie authentication options too). Here's a controller implementation that handles the challenge process (again, I'll explain after):
AccountController.cs
public class AccountController : Controller
{
public IActionResult Login(string returnUrl)
{
return new ChallengeResult(
GoogleDefaults.AuthenticationScheme,
new AuthenticationProperties
{
RedirectUri = Url.Action(nameof(LoginCallback), new { returnUrl })
});
}
public async Task<IActionResult> LoginCallback(string returnUrl)
{
var authenticateResult = await HttpContext.AuthenticateAsync("External");
if (!authenticateResult.Succeeded)
return BadRequest(); // TODO: Handle this better.
var claimsIdentity = new ClaimsIdentity("Application");
claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier));
claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.Email));
await HttpContext.SignInAsync(
"Application",
new ClaimsPrincipal(claimsIdentity));
return LocalRedirect(returnUrl);
}
}
Let's break this down into the two actions:
Login
In order to arrive at the Login action, the user will have been challenged. This occurs when the user is not signed in using the Application scheme but is attempting to access a page protected by the Authorize attribute (or similar). Per your requirement, if the user is not signed in, we want to sign them in using Google. In order to achieve that, we issue a new challenge, this time for the Google scheme. We do so using a ChallengeResult that is configured with the Google scheme and a RedirectUrl, which is used for returning to our own application code once the Google sign-in process completes. As the code shows, we return to:
LoginCallback
This is where the DefaultSignInScheme from our call to AddAuthentication becomes relevant. As part of the Google sign-in process completion, the DefaultSignInScheme is used for setting a cookie that contains a ClaimsPrincipal representing the user as returned from Google (this is all handled behind the scenes). The first line of code in LoginCallback grabs hold of this ClaimsPrincipal instance, which is wrapped up inside an AuthenticateResult that is first checked for success. If everything has been successful so far, we end up creating a new ClaimsPrincipal that contains whatever claims we need (taken from Google in this case) and then signing-in that ClaimsPrincipal using the Application scheme. Lastly, we redirect to the page that caused our first challenge.
In response to a couple of follow-up comments/questions in the comments below:
Can I conclude that the SignInManager and UserManager are only used when using authentication with a database?
In some ways, yes, I think that's fair. Although it is possible to implement an in-memory store, it doesn't really make much sense with no persistence. However, the real reason not to use these classes in your situation is simply because you do not need a local user account for representing a user. That goes hand-in-hand with persistence, but it's worth making the distinction.
And what very different code from what I read in the book (which I used for setting up my Google login) and all the other answers I've read.
The documentation and the books cover the most common use-case, whereby you do want to store local users that can be linked to external accounts such as Google, etc. If you look at the SignInManager source, you'll see that it's really just sitting on top of the kind of code I've shown above (e.g. here and here). Other code can be found in the Default UI (e.g. here) and in AddIdentity.
I assume the LoginCallback gets called by Google. Does the HttpContext.AuthenticateAsync know how to check the data Google sends me? And as it's name is so generic, it looks like it knows how to do that for all external providers?
The call to AuthenticateAsync here doesn't know anything about Google - the Google-specific handling is configured by the call to AddGoogle off of AddAuthentication in ConfigureServices. After redirecting to Google for sign-in, we actually come back to /signin-google in our application. Again, this is handled thanks to the call to AddGoogle, but that code is really just issuing a cookie in the External scheme that stores the claims that came back from Google and then redirecting to our LoginCallback endpoint that we configured. If you add a call to AddFacebook, a /sigin-facebook endpoint will be configured to do something similar. The call to AuthenticateAsync is really just rehydrating a ClaimsPrincipal from the cookie that was created by e.g. the /signin-google endpoint, in order to retrieve the claims.
It's also worth noting that the Google/Facebook sign-in process is based on the OAuth 2 protocol, so it's kind of generic in itself. If you were to need support for more than just Google, you would just issue the challenge against the required scheme rather than hardcoding it to Google as I've done in the example. It's also possible to add additional properties to the challenge in order to be able to determine which provider was used when your LoginCallback endpoint is reached.
I've created a GitHub repository that contains a complete example that I built in order to write this answer here.

Executing code after user has been authenticated using Azure Active Directory

I created an ASP.NET MVC Core (1.1.0) application using VS2015. In the dialog, I selected the option to connect to Azure AD, so VS generated the boilerplate code and, as expected, the app redirects me to Microsoft's login page, where I can login with my work&school account.
Now, after the user logs in, and before serving the first page (say, /home/index) I need to get some information from the user that I have stored in a database (like the display name, the contact information such as an email, phone number, address, a picture of the user, and so on).
What I have thought so far is to add a ControllerBase with a method that retrieves this info, and then pass it to the views via ViewData. But querying the database for this info over and over seems inefficient. An alternative would be to store this info in a cookie or in a session state, thus only hitting the database once. But having to depend on a ControllerBase could lead to errors (for instance, if in some controller method one forgets to call the base's method) and doesn't feel like they way to go. Also, having this funcionality on the home controller only could fail if a user enters the URL with a predefined path (as in www.myapp.com/Users/joedoe/Detail).
I searched and found a reference to using the Events property in the OpenIdConnectOptions object passed to the application builder in the Startup class:
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
ClientId = Configuration["Authentication:AzureAd:ClientId"],
Authority = Configuration["Authentication:AzureAd:AADInstance"] + Configuration["Authentication:AzureAd:TenantId"],
CallbackPath = Configuration["Authentication:AzureAd:CallbackPath"],
Events = new OpenIdConnectEvents {
//
}
});
However, the OpenIdConnectEvents class does not have some "OnAuthenticationSucceeded" event, it only has an OnAuthenticationFailed, which is not what I want, and other callbacks whose names doesn't seem to be what I'm looking for.
So, my question, what is the callback I should be using with OpenIdConnectEvents, or, alternatively, what's the preferred way for ASP.NET MVC Core applications that connect to AAD to catch an event after the user has been authenticated?
Thanks in advance.
There is an assortment of various OpenIdConnectEvents you can hook into. Look at SecurityTokenValidated. This fires after the user has authenticated to AAD and the token had been validated. Here you can look up data in a database and add your own claims to the identity (like roles, etc).
This sample goes and resolves group names from AAD, but the concept is the same - add additional data to the claim set and you can access it through the user principal throughout the application. Using a ClaimType of role will let you use the existing attributes in ASP.net (like the Authorize(Role=...) attribute.
https://github.com/jpda/azure-ad-netcore-sample/blob/master/src/azure-ad-netcore-sample/Startup.cs

Categories