ASP.NET Core - Implement Google One Tap Sign In
insights Stats
Google one tap is a cross-platform sign-in mechanism for Web and Android. It enables Google users to sign up to a third party services like website literally with one tap only. This article provides detailed steps to implement this sign-in mechanism for ASP.NET Core website.
About Google one tap sign-up
For website implemented Google one tag sign up or sign in like Kontext, a popup window will show up if the user already signed into Google accounts. Once the user click 'Continue as ...' button, Google will return credential details to the callback service; the credential can be used to retrieve more information about the Google user.
Now let's start to implement Google one tap in an ASP.NET core application.
Register Google API client
The first step is to register a Google API client ID and configure OAuth Consent Screen. Refer to the official documentation: Get your Google API client ID | Sign In With Google. This step is required for normal Google sign-in and one tap sign-in.
The main tasks involved are:
- Create OAuth client ID credentials via Google APIs console.
- Configure OAuth Consent Screen via OAuth consent screen.
There are two data items can be used later on: ClientId and ClientSecret. ClientSecret is not mandatory for Google One Tap sign-in but is required for Google Sign-in (Google external login setup in ASP.NET Core | Microsoft Docs).
"Google": { "ClientId": "***.apps.googleusercontent.com", "ClientSecret": "***" }
Add client scripts
Now we can add Google One Tap required client scripts into client pages.
First we need to add Google JavaScript file into HTML header section of Razor pages or MVC views.
<script src="https://accounts.google.com/gsi/client" async defer></script>
And then add the following HTML to the pages that you want to show one tag sign-in popup window:
<div id="g_id_onload" data-client_id="***" data-login_uri='/GoogleOneTap'> </div>
The value for data-client_id attribute need to be replaced with Google OAuth client Id created in the previous steps. For the second attribute data-login_uri that is the callback URI once the authentication is successful. In the example, URI /GoogleOneTap is used as callback handler. We will add this controller or Razor page in next step.
Add callback handlers
Let's add a Razor page named GoogleOneTap. In this Razor page, we need to implement a handler for POST HTTP request. For MVC project, please follow similar steps.
/// <summary> /// Google one tap sign in handler /// </summary> /// <returns></returns> public async Task<IActionResult> OnPostAsync() { }
This function will be invoked once Google authenticate the user successfully and it will send a POST HTTP request to this Razor page. In the POST form body, there are two critical elements:
- g_csrf_token: CSRF token.
- credential: the returned credential that can be used in Google APIs for validation.
Two authentication related packages
Before implementing the function, we need to add two packages into the ASP.NET Core web project: Google.Apis.Auth and Microsoft.AspNetCore.Authentication.Google. The first package will be used to retrieve user details like Email, GivenName, FamilyName, JWT ID token, etc. The second package will be used to sign-in to ASP.NET Identity.
Implement the callback function
The main steps for implementing the callback function involves:
- Verify CSRF token.
- Verify returned credential (ID token).
- Sign in to ASP.NET core Identity service using SignInManager with returned payload details.
- If sign-in is not successful, it means the local account is not created yet in Identity; thus UserManager can be used to create local account and add external login details (scope subject) with provider as Google and then sign-in again before returning to the previous visited URL.
- If sign-in is successful, return to the previous visited URL (ReturnUrl) before one tap sign in.
The sample code looks like the following code snippet:
/// <summary> /// Google one tap sign in handler /// </summary> /// <returns></returns> public async Task<IActionResult> OnPostAsync() { // Validate Google CSRF first since we turned off asp.net core CSRF check. var google_csrf_name = "g_csrf_token"; var cookie = Request.Cookies[google_csrf_name]; if (cookie == null) return StatusCode((int)HttpStatusCode.BadRequest); var requestBody = Request.Form[google_csrf_name]; if (requestBody != cookie) return StatusCode((int)HttpStatusCode.BadRequest); var idToken = Request.Form["credential"]; GoogleJsonWebSignature.Payload payload = await GoogleJsonWebSignature.ValidateAsync(idToken).ConfigureAwait(false); // Sign-in users var provider = GoogleDefaults.AuthenticationScheme; var providerKey = payload.Subject; var result = await _signInManager.ExternalLoginSignInAsync(provider, providerKey, isPersistent: false, bypassTwoFactor: true).ConfigureAwait(false); if (result.Succeeded) return LocalRedirect(ReturnUrl); if (result.IsLockedOut) return RedirectToPage("./Lockout"); else { // If the user does not have an account, then create an account. var user = new ApplicationUser { UserName = payload.Email, Email = payload.Email, FirstName = payload.GivenName, LastName = payload.FamilyName, IsEnabled = true, EmailConfirmed = true, DateRegister = DateTime.Now }; await _userManager.CreateAsync(user).ConfigureAwait(false); // Add external Google login await _userManager.AddLoginAsync(user, new UserLoginInfo(provider, providerKey, provider)).ConfigureAwait(false); // Sign-in the user await _signInManager.SignInAsync(user, isPersistent: false).ConfigureAwait(false); return LocalRedirect(ReturnUrl); } }
In the above code snippet, ApplicationUser class is inherited from IdentityUser. Fields _signInManager and _userManager are the instances of Identity SignInManager and UserManager respectively.
Now, the main functions are implemented for Google one tap sign-in.
Hopefully you are now familiar with the main steps required to implement Google One Tag sign-up or sign-in in ASP.NET Core applications. A sample project will be provided later for reference.
person Rami Taher access_time 13 months ago
its saved in the default user identity table. I have the below code with no errors but no photo shown.
<div class="profile-wrapper">
<img src="@User.FindFirst("PictureSource")?.Value" class="profile" />
<div class="titles d-flex flex-column ps-3">
<h6 class="mb-0">Expense Tracher</h6>
<span class="text-muted">Logged In</span>
</div>
</div>
its saved in the default user identity table. I have the below code with no errors but no photo shown.
<div class="profile-wrapper">
<img src="@User.FindFirst("PictureSource")?.Value" class="profile" />
<div class="titles d-flex flex-column ps-3">
<h6 class="mb-0">Expense Tracher</h6>
<span class="text-muted">Logged In</span>
</div>
</div>
person Raymond access_time 2 years ago
Hi Rami, depends on where you save the photo attribute to, you just need to query it from the store. You can use entity framework for this.
Hi Rami, depends on where you save the photo attribute to, you just need to query it from the store. You can use entity framework for this.
person Rami Taher access_time 2 years ago
Hi,
I managed to save the path in the DB but now in my sidebar how to get the path from the db into the img field below:
<img class="profile-pic" src="~/profile2.jpg" />
@* change based on login *@
@if (!SignInManager.IsSignedIn(User))
{
<div class="profile-wrapper">
<img class="profile-pic" src="~/profile.jpg" />
<div class="titles d-flex flex-column ps-3">
<h6 class="mb-0">Expense Tracher</h6>
<span class="text-muted">Guest</span>
<a href="/">Please Login</a>
</div>
</div>
}
else
{
<div class="profile-wrapper">
<img class="profile-pic" src="~/profile2.jpg" />
<div class="titles d-flex flex-column ps-3">
<h6 class="mb-0">Expense Tracher</h6>
<span class="text-muted">Logged In</span>
</div>
</div>
}
Hi,
I managed to save the path in the DB but now in my sidebar how to get the path from the db into the img field below:
<img class="profile-pic" src="~/profile2.jpg" />
@* change based on login *@
@if (!SignInManager.IsSignedIn(User))
{
<div class="profile-wrapper">
<img class="profile-pic" src="~/profile.jpg" />
<div class="titles d-flex flex-column ps-3">
<h6 class="mb-0">Expense Tracher</h6>
<span class="text-muted">Guest</span>
<a href="/">Please Login</a>
</div>
</div>
}
else
{
<div class="profile-wrapper">
<img class="profile-pic" src="~/profile2.jpg" />
<div class="titles d-flex flex-column ps-3">
<h6 class="mb-0">Expense Tracher</h6>
<span class="text-muted">Logged In</span>
</div>
</div>
}
person Raymond access_time 2 years ago
Yes, photo is included in the response JWT token payload:
https://cloud.google.com/dotnet/docs/reference/Google.Apis/latest/Google.Apis.Auth.GoogleJsonWebSignature.Payload#Google_Apis_Auth_GoogleJsonWebSignature_Payload_Picture
GoogleJsonWebSignature.Payload.Picture
Yes, photo is included in the response JWT token payload:
https://cloud.google.com/dotnet/docs/reference/Google.Apis/latest/Google.Apis.Auth.GoogleJsonWebSignature.Payload#Google_Apis_Auth_GoogleJsonWebSignature_Payload_Picture
GoogleJsonWebSignature.Payload.Picture
person Rami Taher access_time 2 years ago
Hi Again Raymond,
I came up with a question. Is there a way to get the profile photo from google using the payload?
Hi Again Raymond,
I came up with a question. Is there a way to get the profile photo from google using the payload?
person Raymond access_time 2 years ago
You are welcome. I am glad it now works for you.
You are welcome. I am glad it now works for you.
person Rami Taher access_time 2 years ago
Thanks alot for helping me out. I finally managed to do it.
private readonly SignInManager<IdentityUser> _signManager;
private readonly UserManager<IdentityUser> _userManager;
public HomeController(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signManager)
{
_userManager = userManager;
_signManager = signManager;
}
and in my function:
var user = new IdentityUser { UserName = payload.Email, Email = payload.Email };
await _userManager.CreateAsync(user).ConfigureAwait(false);
await _signManager.SignInAsync(user, isPersistent: false).ConfigureAwait(false);
return RedirectToAction("Index","Category");
Thanks alot for helping me out. I finally managed to do it.
private readonly SignInManager<IdentityUser> _signManager;
private readonly UserManager<IdentityUser> _userManager;
public HomeController(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signManager)
{
_userManager = userManager;
_signManager = signManager;
}
and in my function:
var user = new IdentityUser { UserName = payload.Email, Email = payload.Email };
await _userManager.CreateAsync(user).ConfigureAwait(false);
await _signManager.SignInAsync(user, isPersistent: false).ConfigureAwait(false);
return RedirectToAction("Index","Category");
person Raymond access_time 2 years ago
What is the type of _userManager
? I am guessing you are referencing a wrong type. The type of user manager in Identity should be: UserManager<TUser> Class (Microsoft.AspNetCore.Identity) | Microsoft Learn
What is the type of _userManager
? I am guessing you are referencing a wrong type. The type of user manager in Identity should be: UserManager<TUser> Class (Microsoft.AspNetCore.Identity) | Microsoft Learn
person Rami Taher access_time 2 years ago
I did the following:
var user = new IdentityUser { UserName = payload.Email, Email = payload.Email };
await _userManager.CreateAsync(user).ConfigureAwait(false);
return RedirectToAction("Index","Home");
I get error:
Severity Code Description Project File Line Suppression State
Error CS1503 Argument 1: cannot convert from 'Microsoft.AspNetCore.Identity.IdentityUser' to 'Microsoft.PowerBI.Api.Models.User'
I did the following:
var user = new IdentityUser { UserName = payload.Email, Email = payload.Email };
await _userManager.CreateAsync(user).ConfigureAwait(false);
return RedirectToAction("Index","Home");
I get error:
Severity Code Description Project File Line Suppression State
Error CS1503 Argument 1: cannot convert from 'Microsoft.AspNetCore.Identity.IdentityUser' to 'Microsoft.PowerBI.Api.Models.User'
person Raymond access_time 2 years ago
Hi Rami,
It's not easy to discuss about Identity framework in a comment as it covers many content. I suggest you following this article Introduction to Identity on ASP.NET Core | Microsoft Learn to understand the basics of Identity in ASP.NET Core.
Hi Rami,
You can use
UserManager
class in Identity to retrieve the user information defined in your ownApplicationUser
class.UserManager<TUser>.GetUserAsync(ClaimsPrincipal) Method (Microsoft.AspNetCore.Identity) | Microsoft Learn
For parameter
principal
it is available in Razor page or MVC Controller class directly after the user is authenticated.