C
C#15mo ago
Joschi

❔ How to configure an external provider in .Net8 Blazor with individual auth?

Hey guys, I'm having problems adding an external authentication provider to the basic Blazor WebApp template.
services.AddAuthentication(IdentityConstants.ApplicationScheme).AddBattleNet(options =>
{
var bnSection = config.GetSection("Authentication:BattleNet");
var clientId = bnSection["ClientId"];
var clientSecret = bnSection["ClientSecret"];

options.Region = BattleNetAuthenticationRegion.Europe;
options.ClientId = clientId;
options.ClientSecret = clientSecret;
options.Scope.Add("oidc");
}).AddCookie(IdentityConstants.ExternalScheme).AddApplicationCookie();
services.AddAuthentication(IdentityConstants.ApplicationScheme).AddBattleNet(options =>
{
var bnSection = config.GetSection("Authentication:BattleNet");
var clientId = bnSection["ClientId"];
var clientSecret = bnSection["ClientSecret"];

options.Region = BattleNetAuthenticationRegion.Europe;
options.ClientId = clientId;
options.ClientSecret = clientSecret;
options.Scope.Add("oidc");
}).AddCookie(IdentityConstants.ExternalScheme).AddApplicationCookie();
When doing it like this the oidc flow works fine, but now navigating to "Account/Manage" gives an "Error: Unable to load user with ID 'BattleNetId'". This seems to happen, because GetUserAsync(principal) in UserAccessor.cs tries to fetch a user with the BattleNetId from AspNetUsers instead of doing a lookup in AspNetUserLogins first. Is the way I registered that external provider even correct, because I don't entirely understand those AddCookie calls, but without that I get a runtime Error. How do I "correctly" fetch the user if he is logged in using an external provider?
34 Replies
Joschi
JoschiOP15mo ago
Adding options.SignInScheme = IdentityConstants.ExternalScheme; to the provider config now results in the new user being stored and fetched correctly.
JakenVeina
JakenVeina15mo ago
what Blazor framework is this?
Joschi
JoschiOP15mo ago
It's the .Net8 rc2 Blazor WebApp template created in Rider with individual authentication.
JakenVeina
JakenVeina15mo ago
okay what Blazor framework is this?
Joschi
JoschiOP15mo ago
Sorry I'm confused, what you want to know specifically. All the code I shared is from the template which uses EFCore and Identity. With a a Nugget for the additional provider. It all runs server side.
JakenVeina
JakenVeina15mo ago
so, Blazor Server?
Joschi
JoschiOP15mo ago
Yes, currently I haven't registered the WASM services and don't have anything running as WASM or Auto component.
JakenVeina
JakenVeina15mo ago
do you plan to? that's a weird thing to say if you're actually using Blazor Server
Joschi
JoschiOP15mo ago
I may want to try running mostly in auto. But that will prove difficult with database access. And I haven't thought about it that much
JakenVeina
JakenVeina15mo ago
auto?
Joschi
JoschiOP15mo ago
The new .NET 8 Auto InteractiveAuto mode that fetches the wasm runtime in the background and switches to wasm on reload
JakenVeina
JakenVeina15mo ago
that sounds horrifying the server-side pre-rendering option, basically anyway so
gives an "Error: Unable to load user with ID 'BattleNetId'"
what gives this error?
Joschi
JoschiOP15mo ago
UserAccessor redirects to the Error Page with this explicit string when it can't find the user.
JakenVeina
JakenVeina15mo ago
what is UserAccessor?
Joschi
JoschiOP15mo ago
A class in the template that is used to get the user from the db.
JakenVeina
JakenVeina15mo ago
let's see it
Joschi
JoschiOP15mo ago
Can't rn don't have my laptop
JakenVeina
JakenVeina15mo ago
mm-kay
Joschi
JoschiOP15mo ago
That's the method that's called from there. The rest of the class is just dependency injection
public async Task<User> GetRequiredUserAsync()
{
var principal = httpContextAccessor.HttpContext?.User ??
throw new InvalidOperationException(
$"{nameof(GetRequiredUserAsync)} requires access to an {nameof(HttpContext)}.");

var user = await userManager.GetUserAsync(principal);

if (user is null)
{
// Throws NavigationException, which is handled by the framework as a redirect.
redirectManager.RedirectToWithStatus("/Account/InvalidUser",
$"Error: Unable to load user with ID '{userManager.GetUserId(principal)}'.");
}

return user;
}
public async Task<User> GetRequiredUserAsync()
{
var principal = httpContextAccessor.HttpContext?.User ??
throw new InvalidOperationException(
$"{nameof(GetRequiredUserAsync)} requires access to an {nameof(HttpContext)}.");

var user = await userManager.GetUserAsync(principal);

if (user is null)
{
// Throws NavigationException, which is handled by the framework as a redirect.
redirectManager.RedirectToWithStatus("/Account/InvalidUser",
$"Error: Unable to load user with ID '{userManager.GetUserId(principal)}'.");
}

return user;
}
I just found that in a history I sadly can't share the rest rn.
JakenVeina
JakenVeina15mo ago
sure so, either the user doesn't exist in the database, or principal doesn't contain a user ID (or whatever userManager uses to lookup a user is BattleNetId the ID of a test user you created?
Joschi
JoschiOP15mo ago
The priciple contains a claim from the external provider that carries the id of that external provider. The BattleNetid (Sorry seems like that original question wasn't too clear)
JakenVeina
JakenVeina15mo ago
is BattleNetId the ID of a valid user? in the database?
Joschi
JoschiOP15mo ago
It does a lookup for this external id on the user table and doesn't find anything. Because the user itself has its own id. What should happen is, that it looks up in AspUserLogins (or however that table is called exactly). There a mapping of external provider+Id to internal userid is stored.
JakenVeina
JakenVeina15mo ago
okay so the principal is likely malformed let's look at it
Joschi
JoschiOP15mo ago
I will come back to you with this tomorrow if that's fine. I'm on a trip right now and as I mentioned can't really post or test any code. But thanks for trying to help!
JakenVeina
JakenVeina15mo ago
this is a chat app that's how it works respond when you can
Joschi
JoschiOP15mo ago
Still rude to be in a conversation and just stop responding imo .
JakenVeina
JakenVeina15mo ago
meh you already stated you don't have access to your code at the moment
Accord
Accord15mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Joschi
JoschiOP15mo ago
No description
JakenVeina
JakenVeina15mo ago
is one of those blacked-out values "BattleNetId"?
Joschi
JoschiOP15mo ago
In the first collapsed claim the nameidentifier is the id. The rest are the name
JakenVeina
JakenVeina15mo ago
but are any of them "BattleNetId"? presumably not so, I guess our assumption was wrong then principal seems to be fine, let's dig into GetUserAsync()
Accord
Accord15mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server