I am using OAuth2 for users to sign in with their Google Account and authenticate Google API requests.

I have not used the built-in ASP.NET Identity provider because this requires an app database of local user accounts (passwords, etc). I just want Google to tell us who the user is, keep hold of their auth/refresh tokens, and set an auth cookie to remember them.

Helper class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Responses;
using Google.Apis.Auth.OAuth2.Web;
using Google.Apis.Oauth2.v2;
using Google.Apis.Requests;
using Google.Apis.Services;
using Google.Apis.Util.Store;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;

public static class GoogleOAuth2
  private static string AppName { get; set; }
  private static GoogleAuthorizationCodeFlow Flow { get; set; }
  private static GoogleAuthorizationCodeFlow FlowPromptConsent { get; set; }

  public static void Configure(string appName, IEnumerable<string> scopes, string clientId, string clientSecret, IDataStore dataStore)
    AppName = appName;
    Flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer {
      DataStore = dataStore,
      ClientSecrets = new ClientSecrets {
        ClientId = clientId,
        ClientSecret = clientSecret
      Scopes = scopes
    FlowPromptConsent = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer {
      DataStore = dataStore,
      ClientSecrets = new ClientSecrets {
        ClientId = clientId,
        ClientSecret = clientSecret
      Scopes = scopes,
      UserDefinedQueryParams = new[] { KeyValuePair.Create("prompt", "consent") }

  public static string GetEmail(this ClaimsPrincipal user)
    var claims = (ClaimsIdentity)user.Identity;
    return claims.FindFirst(ClaimTypes.Email)?.Value;

  public static async Task<BaseClientService.Initializer> GetInitializerAsync(string email)
    var token = await Flow.DataStore.GetAsync<TokenResponse>(email) ?? throw new KeyNotFoundException("User token not found in the data store.");
    return new BaseClientService.Initializer {
      ApplicationName = AppName,
      HttpClientInitializer = new UserCredential(Flow, email, token)

  public static async Task<ActionResult> LoginWithGoogleAsync(this Controller controller)
    var userId = controller.HttpContext.Session.Id;
    var uri = controller.Request.GetDisplayUrl();
    var redirectUri = uri.Substring(0, uri.Length - controller.Request.QueryString.Value.Length); // Current URI without query
    var code = controller.Request.Query["code"].ToString();
    string appReturnUrl = null;

    UserCredential credential = null;

    if (code == string.Empty) {
      // First pass: 'code' is empty
      if (controller.Request.Query.ContainsKey("ReturnUrl")) {
        controller.TempData["ReturnUrl"] = controller.Request.Query["ReturnUrl"].ToString();
      var auth = await new AuthorizationCodeWebApp(Flow, redirectUri, redirectUri).AuthorizeAsync(userId, CancellationToken.None);
      if (auth.RedirectUri != null) {
        return controller.Redirect(auth.RedirectUri); // To Google login page
      credential = auth.Credential;
    } else {
      // On return from the Google login page:
      var token = await Flow.ExchangeCodeForTokenAsync(userId, code, redirectUri, CancellationToken.None);
      credential = new UserCredential(Flow, userId, token);

    var oauthService = new Oauth2Service(new BaseClientService.Initializer { HttpClientInitializer = credential });
    var userInfo = await oauthService.Userinfo.Get().ExecuteAsync();

    // Check if we already have a token for this email address

    var store = Flow.DataStore;
    var oldToken = await store.GetAsync<TokenResponse>(userInfo.Email);
    var newToken = await store.GetAsync<TokenResponse>(userId);
    await store.DeleteAsync<TokenResponse>(userId); // Clean up token store
    await store.DeleteAsync<string>("oauth_" + userId); // Clean up token store

    if (newToken.RefreshToken == null) {

      // If Google didn't give us a refresh token, take the previously saved one if
      // it exists, or redirect back to Google and force a consent screen

      if (oldToken?.RefreshToken != null) {
        newToken.RefreshToken = oldToken.RefreshToken;
      } else {
        var auth = await new AuthorizationCodeWebApp(FlowPromptConsent, redirectUri, redirectUri).AuthorizeAsync(userId, CancellationToken.None);
        return controller.Redirect(auth.RedirectUri);

    await store.StoreAsync(userInfo.Email, newToken); // Save new token, using user's email as key

    var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
    identity.AddClaim(new Claim(ClaimTypes.Name, $"{userInfo.GivenName} {userInfo.FamilyName}"));
    identity.AddClaim(new Claim(ClaimTypes.Email, userInfo.Email));
    await controller.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity), new AuthenticationProperties { IsPersistent = true });

    appReturnUrl = controller.TempData["ReturnUrl"]?.ToString() ?? "/";
    return controller.Redirect(appReturnUrl);

Note that session state is used to hold a temporary User Id while communicating with Google, but once we've got an email address for the user this is used as the permanent key in the token store.


public async Task<IActionResult> Login()
  if (User.Identity.IsAuthenticated) return Redirect("/");
  return await this.LoginWithGoogleAsync();

Google API calls:

var service = new CalendarService(await GoogleOAuth2.GetInitializerAsync(User.GetEmail()));
var calendars = await service.CalendarList.List().ExecuteAsync();
  • 1
    \$\begingroup\$ What did you end up doing? \$\endgroup\$ – Simon_Weaver Dec 7 '17 at 7:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.