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 {        controller.TempData.Keep(ReturnUrl);        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.Login:[AllowAnonymous][Route(/login)]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();
OAuth2 login for ASP.NET Core without local accounts
c#;authentication;oauth;asp.net core