【问题标题】:Adding Google API Offline access to a .NET Core app添加对 .NET Core 应用程序的 Google API 离线访问
【发布时间】:2019-12-28 04:31:57
【问题描述】:

我编写了一个 ASP.NET Core Web 应用程序,它使用 Auth0 作为其主要的用户授权机制,中间管理着一大堆外部身份验证端点,如 Google 和 Facebook。这很好用,我没有任何问题。

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

webapp 在用户登录和离线时执行分析。

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

  1. 用户使用任何提供商(Google、Facebook、电子邮件/密码)通过 Auth0 登录并访问私人仪表板。
  2. 用户设置“公司”并单击按钮以授权我们的 Web 应用访问带有 Analytics 的特定 Google 帐户。
  3. 用户被重定向回私人仪表板,并存储 Google 帐户的刷新令牌以供将来使用。

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

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

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

【问题讨论】:

    标签: c# google-api asp.net-core-mvc auth0 google-api-dotnet-client


    【解决方案1】:

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

    用户的浏览器获取以下内容并被重定向到 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);
    }
    

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

    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);
            }
        }
    }
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多