5

I have been following Web applications (ASP.NET MVC) Attempting to connect to one of the Google APIs.

using System;
using System.Web.Mvc;

using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Mvc;
using Google.Apis.Drive.v2;
using Google.Apis.Util.Store;

namespace Google.Apis.Sample.MVC4
{
    public class AppFlowMetadata : FlowMetadata
    {
        private static readonly IAuthorizationCodeFlow flow =
            new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
                {
                    ClientSecrets = new ClientSecrets
                    {
                        ClientId = "PUT_CLIENT_ID_HERE",
                        ClientSecret = "PUT_CLIENT_SECRET_HERE"
                    },
                    Scopes = new[] { DriveService.Scope.Drive },
                    DataStore = new FileDataStore("Drive.Api.Auth.Store")
                });

        public override string GetUserId(Controller controller)
        {
            // In this sample we use the session to store the user identifiers.
            // That's not the best practice, because you should have a logic to identify
            // a user. You might want to use "OpenID Connect".
            // You can read more about the protocol in the following link:
            // https://developers.google.com/accounts/docs/OAuth2Login.
            var user = controller.Session["user"];
            if (user == null)
            {
                user = Guid.NewGuid();
                controller.Session["user"] = user;
            }
            return user.ToString();

        }

        public override IAuthorizationCodeFlow Flow
        {
            get { return flow; }
        }
    }
}

The sample doesn't appear to work with .net core as it cant find FlowMetadata

1 Answer 1

4

The problem is that the above sample was not designed for .net core.

clientinfo.cs

/// <summary>
    /// Client auth information, loaded from a Google user credential json file.
    /// Set the TEST_CLIENT_SECRET_FILENAME environment variable to point to the credential file.
    /// </summary>
    public class ClientInfo
    {
        public static ClientInfo Load()
        {
            const string ClientSecretFilenameVariable = "TEST_CLIENT_SECRET_FILENAME";
            string clientSecretFilename = Environment.GetEnvironmentVariable(ClientSecretFilenameVariable);
            if (string.IsNullOrEmpty(clientSecretFilename))
            {
                throw new InvalidOperationException($"Please set the {ClientSecretFilenameVariable} environment variable before running tests.");
            }
            var secrets = JObject.Parse(Encoding.UTF8.GetString(File.ReadAllBytes(clientSecretFilename)))["web"];
            var projectId = secrets["project_id"].Value<string>();
            var clientId = secrets["client_id"].Value<string>();
            var clientSecret = secrets["client_secret"].Value<string>();
            return new ClientInfo(projectId, clientId, clientSecret);
        }

        private ClientInfo()
        {
            Load();
        }


        private ClientInfo(string projectId, string clientId, string clientSecret)
        {
            ProjectId = projectId;
            ClientId = clientId;
            ClientSecret = clientSecret;
        }

        public string ProjectId { get; }
        public string ClientId { get; }
        public string ClientSecret { get; }
    }

Program.cs

 public class Program
    {
        public static void Main(string[] args)
        {
            BuildWebHost(args).Run();
        }

        public static IWebHost BuildWebHost(string[] args)
        {
            var clientInfo = ClientInfo.Load();

            return WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .ConfigureServices(services =>
                {
                    services.AddSingleton(clientInfo);
                })
                .Build();
        }
    }

startup.cs configure services

 services.AddAuthentication(options =>
                {
                    options.DefaultScheme = "Cookies";
                    options.DefaultChallengeScheme = "Google";
                })
                .AddCookie("Cookies")
                .AddGoogleOpenIdConnect("Google", options =>
                {
                    var clientInfo = (ClientInfo)services.First(x => x.ServiceType == typeof(ClientInfo)).ImplementationInstance;
                    options.ClientId = clientInfo.ClientId;
                    options.ClientSecret = clientInfo.ClientSecret;
                    options.Scope.Add("profile");   

                });

        }

add the following to configure as well app.UseAuthentication();

controler

[GoogleScopedAuthorize("https://www.googleapis.com/auth/analytics.readonly")]
        public async Task<IActionResult> GoogleAnalyticsReport([FromServices] IGoogleAuthProvider auth, [FromServices] ClientInfo clientInfo, long ViewId)
        {
            var cred = await auth.GetCredentialAsync();
            var service = new AnalyticsReportingService(new BaseClientService.Initializer
            {
                HttpClientInitializer = cred
            });

LogOut

 public async Task<IActionResult> Logout()
    {
        await HttpContext.SignOutAsync();
        return RedirectToAction("Index");
    }

Its now logging my user in and i can request data.

Sign up to request clarification or add additional context in comments.

5 Comments

Note that you don't need the Scope.Add("https://www.googleapis.com/auth/analytics.readonly"); in startup.cs, because the GoogleScopedAuthorize attribute in the controller will automatically request permission for this scope from the user if required (ie if this scope hasn't already been requested).
Do you know how it handles refreshing of the access token?
Access token refresh is handled in GoogleAuthProvider.cs. It's important that you don't persist the credential returned from IGoogleAuthProvider.GetCredentialAsync() as this credential is not refreshed. Always request a new credential from the provider each time it is needed.
Is there a reason why credentials are not refreshed as with the other login methods?
Instead of copy/pasting the scope url, plenty of them are available as constants, such as DriveService.ScopeConstants.DriveReadonly (in Google.Apis.Drive.v3)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.