With server side Blazor, if OnRemoteFailure is added to AddGoogle in the startup.cs file:
services.AddAuthentication().AddGoogle(options =>
{
options.ClientId = Configuration["Authentication:Google:ClientId"];
options.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
options.Events = new OAuthEvents
{
OnRemoteFailure = context =>
{
context.HandleResponse();
var error = context.Failure.Message;
return Task.FromResult(0);
}
};
});
If you set a break point on var error, you will see it will always throw the error:
An unhandled exception occurred while processing the request.
Exception: OAuth token endpoint failure: Status: BadRequest;Headers: Vary: X-Origin, Referer, Origin,Accept-Encoding
Date: Thu, 24 Oct 2019 23:27:08 GMT
Server: scaffolding on HTTPServer2
Cache-Control: private
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Alt-Svc: quic=":443"; ma=2592000; v="46,43",h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000
Accept-Ranges: none
Transfer-Encoding: chunked
;Body: {
"error": "invalid_grant",
"error_description": "Bad Request"
};
Unknown locationException: An error was encountered while handling the remote login.
Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler.HandleRequestAsync()Exception: OAuth token endpoint failure: Status: BadRequest;Headers: Vary: X-Origin, Referer, Origin,Accept-Encoding Date: Thu, 24 Oct 2019 23:27:08 GMT Server: scaffolding on HTTPServer2 Cache-Control: private X-XSS-Protection: 0 X-Frame-Options: SAMEORIGIN X-Content-Type-Options: nosniff Alt-Svc: quic=":443"; ma=2592000; v="46,43",h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000 Accept-Ranges: none Transfer-Encoding: chunked ;Body: { "error": "invalid_grant", "error_description": "Bad Request" };
It appears to work with the Microsoft Chromium Edge Browser (Version 79.0.308.1 (Official build) dev (64-bit)), because it will log you in, but you will see the break point is still hit and the error is thrown.
With Google Chrome web browser (Version 78.0.3904.70 (Official Build) (64-bit)), it will show the error and not log you in
You can see a reproduction in the following project:
https://github.com/mytatuo/GTAVehicles.
(note, need to create a database and run this .sql script: https://github.com/mytatuo/GTAVehicles/blob/master/GTAVehicles/!SQL/01.00.00.sql)
This sample, however, will log a user in with Google:
http://blazorhelpwebsite.com/downloads/BlazorGmailAuth.zip
⚠Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.
@guardrex should we move this to https://github.com/aspnet/aspnetcore ?
It's somewhat related to an existing issue under discussion: https://github.com/aspnet/AspNetCore.Docs/issues/15160
Let's surface this to @Tratcher and @BrennanConroy now (and @danroth27) because the point of discussion over there is _IF_ the ordinary ASP.NET Core app social auth approaches that we currently cover are supposed to work _at all_ for Blazor Server apps. You can see by my RexHacks:tm: over there that it ain't pretty! 🙈 lol
How this issue (and that one) are triaged probably depend on the answer ...
If the current approaches _are supposed to work_, then yes, this issue can probably go right over to engineering for a look. We can close this ... or we can use this issue to add a note that a problem with OnRemoteFailure is under investigation in the Blazor auth doc (the Overview) with a follow-up issue to remove the added content at the time of a future release.
If our current social approaches _are not supposed to work_ (certainly not with hacks), then engineering will likely take up feature support different from what ASP.NET Core apps do for social auth ... feature support that will be in-design for a future release ... thus we close this issue or use it to add content saying that social login support isn't available yet for Blazor.
cc: @hfaran
@ADefWebserver ... Check out #15160 to see how that played out. I figured out a way to make the auth bits work, but I didn't check/test OnRemoteFailure. @hfaran found an approach, too, different from what I did.
@ADefWebserver ... Check out #15160 to see how that played out. I figured out a way to _make the auth bits work_, but I didn't check/test
OnRemoteFailure. @hfaran found an approach, too, different from what I did.
@guardrex - When I put your block of code starting with:
options.Events.OnCreatingTicket = context =>
In my repro project at https://github.com/mytatuo/GTAVehicles it will log the user in with the Edge Chromium web browser, but it will throw the error with the Google Chrome web browser.
This is why it took me so long to track this down. The error is always thrown, it seems that the Google Chrome web browser is the only one that is stopping because of it :(
So yeah, the problem is there is no way I can think of to not have OnRemoteFailure fire, other than the method I use here where I completely bypass the built-in .Net Core authentication UI code:
Google Authentication in Server Side Blazor (code is at this link)
I only tested with Firefox and Edge. I also didn't check (or don't recall checking) for errors ... didn't look at OnRemoteFailure. I'll take another look shortly and then update my remarks over there at https://github.com/aspnet/AspNetCore.Docs/issues/15160#issuecomment-544272193.
You have what I think amounts to a third approach. What I like about my hacks tho are that it requires the absolute minimum amount of code to work ... e.g., it doesn't require scaffolding the External Login form and code (or anything else) to work. It seems to work well with the OOB RevalidatingIdentityAuthenticationStateProvider that the template provides with no changes to that either.
@guardrex - Yes the problem with my approach is that it does not work with the SignIn Manager. This is why I was attempting to make the built-in authentication UI work :)
I see. Yes, I tried to get that to work and make available claims+tokens following our guidance at https://docs.microsoft.com/aspnet/core/security/authentication/social/additional-claims.
Everything that I tried failed, so I ended up with the approach (hacks! :smile:) that you see over there.
I just checked that code, and it seems to work ok on Chrome. I ran into a different problem with Firefox ... some kind of certificate error, but it's unrelated to what we're looking at here. Firefox choked on my dev cert for some reason that I'll need to troubleshoot further. Anyway ... Edge and Chrome were :+1: with my hacks.
Firefox choked on my dev cert for some reason that I'll need to troubleshoot further.
FF doesn't support the developer cert. If you want to test on FF, deploy to Azure where there's a valid cert.
We've had issues with FF and HTTP/2. It works if you disable HTTP/2 in Kestrel.
I just checked that code, and it seems to work ok on Chrome.
@guardrex - Where can I download that code? When I tried to just add the 'hack' to my repro it did not work for me...
It's a template-based Blazor Server app with auth ...
dotnet new blazorserver -au Individual
... with those code bits that I put into my issue comment. It's a 3.1 app because I'm on the 3.1 SDK (and I didn't pass the -f option), but I think what I did will work with a 3.0 app. The project file of my test app is ...
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<None Update="app.db" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="3.1.0-preview1.19508.20" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.0-preview1.19508.20" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="3.1.0-preview1.19508.20" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.0-preview1.19506.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0-preview1.19506.2" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="3.1.0-preview1.19508.20 " />
</ItemGroup>
</Project>
To CRUD the dB directly (app.db), I used DB Browser for SQLite, but their website was down. Looks like their hoster is Scaleway, and they're reporting an outage this evening. You might be able to get it from a release at their GH site or an authorized mirror somewhere :point_right: https://github.com/sqlitebrowser/sqlitebrowser ... or use something else entirely.
If you want, I can put the project up in a GH repo. Let me know if you run into trouble using a new template-based app, and I'll put this up for you.
If you want, I can put the project up in a GH repo. Let me know if you run into trouble using a new template-based app, and I'll put this up for you.
@guardrex - Could you put it in a GH repo? Thanks! I'm on 3.0 so I can answer that question.
@danroth27 we should look at this scenario Monday. When's @rynowak back?
https://github.com/guardrex/BlazorAuthTest
Add the app.db from a template app that you generate. Mine is full of real data from testing.
https://github.com/guardrex/BlazorAuthTest
Add the _app.db_ from a template app that you generate. Mine is full of real data from testing.
@guardrex - Ok I rolled it back to 3.0 and your sample still works! Good :) I will now try to make it work with my code...
To get your sample to run I commented out the code, in the startup.cs file, that was expecting the database to be there, I then ran the code and logged in using Google and created an account then the migration scripts ran. I then stopped the project, un comment the code in the startup.cs file, and then ran the project again, and everything worked.
@guardrex The only issue I still have is I tried everything and could not get your code to work in my code. Do you have a step by step on everything you changed?
I didn't change anything else. It's just the OOB template Blazor Server app with Individual identity. I didn't scaffold any Identity UI or change the RevalidatingIdentityAuthenticationStateProvider. I just added those bits you see in my other issue comment.
I can't say that going with the normal sign-in manager is going to work _at all_. I wasn't able to get the approach shown in the ASP.NET Core social auth additional claims topic to work, but I also didn't try any kind of hybrid approach.
I can certainly understand if you need both Razor Pages and Razor Components to work in the same app ... if you're looking for the sign-in manager to work and need auth support (claims, tokens, etc.) in both RP and RC. I was entirely focused on a Razor Components-only approach and the sign-in manager was a dead-end for that.
Engineering is planning to look at this whole subject further shortly. No firm ETA on that.
@guardrex - Understood :) Thank you for the complete explanation. Your example works! I put in my keys, enable roles (because my application is using roles), and your version works perfectly with all web browsers and does not hit the failure block.
@guardrex I went through the scenario with the blazor template + individual auth + social + tokens and it mostly works following the existing docs.
services.AddAuthentication()
.AddGoogle(options =>
{
options.ClientId = Configuration["Google:ClientId"];
options.ClientSecret = Configuration["Google:ClientSecret"];
// Must have this to populate the tokens ...
options.SaveTokens = true;
// When mapped, Google properties become claims. For example ...
options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
});
// If they exist, add claims to the user for:
// Given (first) name
// Locale
// Picture
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.GivenName))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst(ClaimTypes.GivenName));
}
if (info.Principal.HasClaim(c => c.Type == "urn:google:locale"))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst("urn:google:locale"));
}
if (info.Principal.HasClaim(c => c.Type == "urn:google:picture"))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst("urn:google:picture"));
}
// Include the access token in the properties
var props = new AuthenticationProperties();
props.StoreTokens(info.AuthenticationTokens);
props.IsPersistent = true;
await _signInManager.SignInAsync(user, props);
@page "/"
@using Microsoft.AspNetCore.Components.Authorization
@inject AuthenticationStateProvider AuthenticationStateProvider
@using System.Security.Claims
<h1>Auth Testing</h1>
Username: @_user.Identity.Name
<h2>All Claims</h2>
<ul>
@foreach (var claim in _user.Claims)
{
<li>@claim.Type – @claim.Value</li>
}
</ul>
<h2>Explicit Claims</h2>
<ul>
@if (_user.Identity.IsAuthenticated)
{
<li>NameId: @_user.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)?.Value</li>
<li>Name: @_user.FindFirst(c => c.Type == ClaimTypes.Name)?.Value</li>
<li>First: @_user.FindFirst(c => c.Type == ClaimTypes.GivenName)?.Value</li>
<li>Last: @_user.FindFirst(c => c.Type == ClaimTypes.Surname)?.Value</li>
<li>Email: @_user.FindFirst(c => c.Type == ClaimTypes.Email)?.Value</li>
<li>Picture: @_user.FindFirst(c => c.Type == "urn:google:picture")?.Value</li>
}
</ul>
@code {
private ClaimsPrincipal _user;
protected override async Task OnInitializedAsync()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
_user = authState.User;
}
}
I haven't found a clean way to get the auth properties for displaying the user tokens. Is that a blocker here?
Did I miss anything else?
Yes for @hfaran, who wants to keep a token for doing something with the API on the user's behalf later.
WRT what you showed ... I tested that scenario, including scaffolding ExternalLogin.cshtml.cs and updating it per the guidance, and failed. I'll try again this weekend from scratch.
@Tratcher In your test app ... for surname ...
You did a (services.AddAuthentication().AddGoogle(options =>) ...
options.Scope.Add("https://www.googleapis.com/auth/userinfo.profile");
options.ClaimActions.MapJsonKey(ClaimTypes.Surname, "family_name", "string");
... with (ExternalLogin.cshtml.cs) ...
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Surname))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst(ClaimTypes.Surname));
}
... and ...
Last (Surname): @_user.FindFirst(c => c.Type == ClaimTypes.Surname)?.Value
... and it worked for you? .... It's still fighting me.
mmm 🤔 ... not sure on the scope ... I thought that the openid Google scope provided family_name. Anyway ... it doesn't explain my missing claim.
Yeah ... I guess that scope is correct after all. Anyway ... not working (yet).
Rubber :duck: says that Google is doing the right thing ... they're sending the claim ...
System.Security.Claims.ClaimsIdentity
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname
Latham
@guardrex the claims are added to the user on first registration, not on signin. Delete the user from the local database and re-register.
ok
Cool :+1: ... ok ... claims work. I made two mistakes (well ... at least two! :smile:) ...
I'll take a look at the External claims with social topic to see about covering the second point. (The first point is there ... I missed it initially.)
Still tho ...... tokens (auth props) .... that was @hfaran's original ask.
... and what about @ADefWebserver ask here for OnRemoteFailure error? Does that go to the engineering repo?
Should both scenarios become a pair of issues there?
Yeah, @ADefWebserver that OnRemoteFailure error can go to the engineering repo. Please collect a (decrypted) Fiddler log and Debug application logs.
Still tho ...... tokens (auth props) .... that was @hfaran's original ask.
Yeah, that one needs engineering work. GetAuthenticationStateAsync should be extended to include auth properties. In the meantime you can add an API endpoint to the app that returns the tokens from the current user.
@ADefWebserver ... I'm going to close this here. Open at :point_right: https://github.com/aspnet/AspNetCore/issues
@hfaran, I'm going to close #15160 ... open an issue for engineering :point_right: https://github.com/aspnet/AspNetCore/issues ... I think your opening remark is enough with a cross-link to #15160. Please put a "cc: @guardrex" at the bottom so that I can track on it.