添加 Google API 离线访问 .NET Core 应用



我编写了一个 ASP.NET Core Web应用程序,它使用Auth0作为用户的主要授权机制,它中间化了一大堆外部身份验证端点,如Google和Facebook。这工作正常,我在那里没有问题。

网络应用程序的核心是利用Google Analytics来执行自己的分析和业务逻辑。我的网络应用程序正在分析的Google Analytics帐户可能并且可能与用户自己的Google帐户不同。要清楚我的意思是,用户很可能会使用他们希望的任何登录提供商登录,然后他们将附加一个特定的Google企业帐户,可以访问其企业Google Analytics系统。

Web 应用在用户登录时和用户脱机时执行分析。

因此,我一直将用户身份验证 (Auth0( 步骤与分析帐户步骤的身份验证分开。一般流程如下:

  1. 用户使用任何提供商(Google,Facebook,电子邮件/通行证(通过Auth0登录并访问私人仪表板。
  2. 用户设置"公司"并点击一个按钮,授权我们的网络应用程序访问带有分析功能的特定Google帐户。
  3. 用户被重定向回私人仪表板,并存储Google帐户的刷新令牌以备将来使用。

以前我也一直在通过 Auth0 推送分析身份验证,并且我使用缓存的 Auth0 刷新令牌脱机工作。但是,它会在几天后过期,并且 Auth0 似乎不提供长期脱机访问。

所以我认为最简单的办法就是简单地不使用auth0进行分析身份验证步骤,直接使用Google API进行身份验证并长期存储Google刷新令牌。但是,我找不到任何具体的例子来实现这一目标!

  • 官方Google API .NET示例 - 这似乎很旧,ASPNET Core并不真正支持。我看不到一种明确的方法将其塑造成任何可用的东西,搜索 SO 会发现它存在明显的问题。
  • 所以回答一个类似的问题 - 这是一个很好的答案,但实现是针对用户身份验证的,我不相信在我的场景中会起作用。

我终于破解了它!我最终扔掉了所有的库,发现使用普通的旧 REST API 是最简单的。下面的代码示例适合那些好奇的人:

用户的浏览器 GET 以下内容,并被重定向到 Google 以获取身份验证令牌:

public IActionResult OnGet([FromQuery]int id, [FromQuery]string returnAction)
{
var org = context.Organizations.Include(o => o.UserOrgs).First(o => o.Id == id);
var user = GetUser();
if (!IsUserMemberOfOrg(user, org)) return BadRequest("User is not a member of this organization!");
var redirectUri = Uri.EscapeUriString(GetBaseUri()+"dash/auth/google?handler=ReturnCode");
var uri = $"https://accounts.google.com/o/oauth2/v2/auth?"+
$"scope={Uri.EscapeUriString("https://www.googleapis.com/auth/analytics.readonly")}"+
$"&prompt=consent"+
$"&access_type=offline"+
//$"&include_granted_scopes=true"+
$"&state={Uri.EscapeUriString(JsonConvert.SerializeObject(new AuthState() { OrgId = id, ReturnAction = returnAction }))}"+
$"&redirect_uri={redirectUri}"+
$"&response_type=code"+
$"&client_id={_configuration["Authentication:Google:ClientId"]}";
return Redirect(uri);
}

谷歌重定向回以下内容,以及我从网络服务器到谷歌API的POST以将身份验证令牌交换为刷新令牌并将其存储以供以后使用:

public async Task<IActionResult> OnGetReturnCode([FromQuery]string state, [FromQuery]string code, [FromQuery]string scope)
{
var authState = JsonConvert.DeserializeObject<AuthState>(state);
var id = authState.OrgId;
var returnAction = authState.ReturnAction;
var org = await context.Organizations.Include(o => o.UserOrgs).SingleOrDefaultAsync(o => o.Id == id);
if (org == null) return BadRequest("This Org doesn't exist!");
using (var httpClient = new HttpClient())
{
var redirectUri = Uri.EscapeUriString(GetBaseUri()+"dash/auth/google?handler=ReturnCode");
var dict = new Dictionary<string, string>
{
{ "code", code },
{ "client_id", _configuration["Authentication:Google:ClientId"] },
{ "client_secret", _configuration["Authentication:Google:ClientSecret"] },
{ "redirect_uri", redirectUri },
{ "grant_type", "authorization_code" }
};
var content = new FormUrlEncodedContent(dict);
var response = await httpClient.PostAsync("https://www.googleapis.com/oauth2/v4/token", content);
var resultContent = JsonConvert.DeserializeObject<GoogleRefreshTokenPostResponse>(await response.Content.ReadAsStringAsync());
org.GoogleAuthRefreshToken = resultContent.refresh_token;
await context.SaveChangesAsync();
return Redirect($"{authState.ReturnAction}/{authState.OrgId}");
}
}

最后,我们可以稍后使用刷新令牌获取新的访问令牌,而无需用户干预:

public async Task<string> GetGoogleAccessToken(Organization org)
{
if(string.IsNullOrEmpty(org.GoogleAuthRefreshToken))
{
throw new Exception("No refresh token found. " +
"Please visit the organization settings page" +
" to setup your Google account.");
}
using (var httpClient = new HttpClient())
{
var dict = new Dictionary<string, string>
{
{ "client_id", _configuration["Authentication:Google:ClientId"] },
{ "client_secret", _configuration["Authentication:Google:ClientSecret"] },
{ "refresh_token", org.GoogleAuthRefreshToken },
{ "grant_type", "refresh_token" }
};
var resp = await httpClient.PostAsync("https://www.googleapis.com/oauth2/v4/token", 
new FormUrlEncodedContent(dict));
if (resp.IsSuccessStatusCode)
{
dynamic returnContent = JObject.Parse(await resp.Content.ReadAsStringAsync());
return returnContent.access_token;
} else
{
throw new Exception(resp.ReasonPhrase);
}
}
}

最新更新