4

Server Setup: ASP.NET forms on .net 4.5.1 integrated pipeline running on iis8 on server 2012.

I'm attempting to get credentials from google's GoogleWebAuthorizationBroker but i keep getting "access is denied".

Thinking it was perhaps access issues i tried creating an in-memory IDataStore (entered here)

internal class DummyData : IDataStore
    {
        internal class item
        {
            public string Key { get; set; }
            public string value { get; set; }
        }
        internal static List<item> pKeys = new List<item>();
        public async Task ClearAsync()
        {
            pKeys = new List<item>();
        }

        public async Task DeleteAsync<T>(string key)
        {
            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentException("Key MUST have a value");
            }
                var generatedKey = GenerateStoredKey(key, typeof(T));
            if (pKeys.Any(x => x.Key == generatedKey))
            {
                pKeys.Remove(pKeys.First(x => x.Key == generatedKey));
            }
        }

        public Task<T> GetAsync<T>(string key)
        {
            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentException("Key MUST have a value");
            }
                var generatedKey = GenerateStoredKey(key, typeof(T));
                var item = pKeys.FirstOrDefault(x => x.Key == generatedKey);
                T value = item == null ? default(T) : JsonConvert.DeserializeObject<T>(item.value);
                return Task.FromResult<T>(value);

        }

        public async Task StoreAsync<T>(string key, T value)
        {
            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentException("Key MUST have a value");
            }

            using (var context = new Platform4AutoDB())
            {
                var generatedKey = GenerateStoredKey(key, typeof(T));
                string json = JsonConvert.SerializeObject(value);

                var item = pKeys.FirstOrDefault(x => x.Key == generatedKey);                    

                if (item == null)
                {
                    pKeys.Add(new item { Key = generatedKey, value = json });                        
                }
                else
                {
                    item.value = json;
                }
            }
        }

        private static string GenerateStoredKey(string key, Type t)
        {
            return string.Format("{0}-{1}", t.FullName, key);
        }
    }

the basic call i'm using to test is as follows:

public string Test()
    {
        var xStore = new DummyData();
        var pSecrect = p4_db.GoogleAccounts.FirstOrDefault(x => x.ClientID == m_nClientID);
        if (string.IsNullOrEmpty(pSecrect.V3ClientSecret))
        {
            return "You are not set up to upload you tube videos.";
        }

        UserCredential credential;
        Sec pSecret = new Sec();

        pSecret.Web.client_secret = pSecrect.V3ClientSecret;
        var json = new JavaScriptSerializer().Serialize(pSecret);

        using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
        {
            credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
                GoogleClientSecrets.Load(stream).Secrets,
                // This OAuth 2.0 access scope allows an application to upload files to the
                // authenticated user's YouTube channel, but doesn't allow other types of access.
                new[] { YouTubeService.Scope.YoutubeUpload },
                "user",
                CancellationToken.None,
                xStore
            ).Result;
        }
        return " ";
    }

the retrieval of the client secret is via EF6 and works without an issue.

When running this on my staging server i get the following error:

System.Web.HttpUnhandledException (0x80004005): Exception of type 'System.Web.HttpUnhandledException' was thrown. ---> System.AggregateException: One or more errors occurred. ---> System.ComponentModel.Win32Exception: Access is denied
at Microsoft.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at Microsoft.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccess(Task task)
at Google.Apis.Auth.OAuth2.GoogleWebAuthorizationBroker.d__1.MoveNext() in c:\code\google.com\google-api-dotnet-client\default\Tools\Google.Apis.Release\bin\Debug\test\default\Src\GoogleApis.Auth.DotNet4\OAuth2\GoogleWebAuthorizationBroker.cs:line 59

line 59 in GoogleWebAuthorizationBroker is this:

return await AuthorizeAsyncCore(initializer, scopes, user, taskCancellationToken, dataStore)
            .ConfigureAwait(false)

AuthorizeAsyncCore looks like this:

  private static async Task<UserCredential> AuthorizeAsyncCore(
        GoogleAuthorizationCodeFlow.Initializer initializer, IEnumerable<string> scopes, string user,
        CancellationToken taskCancellationToken, IDataStore dataStore = null)
    {
        initializer.Scopes = scopes;
        initializer.DataStore = dataStore ?? new FileDataStore(Folder);
        var flow = new GoogleAuthorizationCodeFlow(initializer);

        // Create an authorization code installed app instance and authorize the user.
        return await new AuthorizationCodeInstalledApp(flow, new LocalServerCodeReceiver()).AuthorizeAsync
            (user, taskCancellationToken).ConfigureAwait(false);
    }

since the datastore is not null, this shouldn't be trying to access the disk in anyway.

Has anyone else implemented this successfully in a similar invironment, or have any other ideas?

Kelvin
  • 41
  • 1
  • 4

2 Answers2

0

I did get this working - my credentials block now looks like this:`

UserCredential credential;            
        var pJ = new
        {
            web = new
             {
                 client_id = ConfigurationManager.AppSettings["YoutubeClientID"],
                 client_secret = ConfigurationManager.AppSettings["YoutubeClientSecret"], 
                 redirect_uris = ConfigurationManager.AppSettings["YouTubeClientRedirectUris"].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) 
             }
        };
        var json = new JavaScriptSerializer().Serialize(pJ);
        using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
        {
            credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
                GoogleClientSecrets.Load(stream).Secrets,                 
                new[] { YouTubeService.Scope.Youtube },
                this.m_nClientID.ToString(),
                CancellationToken.None,
                pStore
            ).Result;
        }

I also made 100% sure that i had a valid token before hitting this - for some reason the dll seems to default to a filedatastore when your token is invalid.

While it's not the best fix, it does work.

`

Bacteria
  • 8,406
  • 10
  • 50
  • 67
kelvin_ds
  • 49
  • 4
  • Thanks for sharing. Actually I see no fundamental difference in your code if compared to your code in question - same approach is utilized. The problem with "Access denied" is related to inability of HttpListener to grab a port for an ordinary user when performing OAuth handshake. – AntonK May 26 '17 at 21:07
  • @AntonK I am facing same problem in dot net core 3.1. Did you resolved this error? – Vishal Kiri Oct 01 '22 at 10:15
0

I know this is an old question but, GoogleWebAuthorizationBroker.AuthorizeAsync is used for installed applications it will open the web browser window on the machine its running on. For web applications you need to open the consent window on the users machine.

Web applications need to be configured differently Consult the documentation for a full example

public void ConfigureServices(IServiceCollection services)
{
    ...

    // This configures Google.Apis.Auth.AspNetCore3 for use in this app.
    services
        .AddAuthentication(o =>
        {
            // This forces challenge results to be handled by Google OpenID Handler, so there's no
            // need to add an AccountController that emits challenges for Login.
            o.DefaultChallengeScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
            // This forces forbid results to be handled by Google OpenID Handler, which checks if
            // extra scopes are required and does automatic incremental auth.
            o.DefaultForbidScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
            // Default scheme that will handle everything else.
            // Once a user is authenticated, the OAuth2 token info is stored in cookies.
            o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        })
        .AddCookie()
        .AddGoogleOpenIdConnect(options =>
        {
            options.ClientId = {YOUR_CLIENT_ID};
            options.ClientSecret = {YOUR_CLIENT_SECRET};
        });
}
Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449