Google Shopping API using OAuth 2 method for authentication - c#

I am trying to create a console app that will be executed by a scheduled task to create products for sale on Google Shopping. From what I have read OAuth 2 is the method of Authentication I should be using.
In the Google Developer Console I have created a Project and got back a Project Id. I have enabled the Content API for Shopping, Created a Client Id for native application which gives me the Client Id, Client Secret and Redirect URIS. (not sure what the Redirect URIS are all about?)
In my C# code I have:
string clientId = "27127807975-j71ghj7<=== MY CLIENT ID from console ===>apps.googleusercontent.com"; // Provided in the APIs Console
string clientSecret = "WFM<== My Secret from console ==>osVg"; // Provided in the APIs Console
string scope = "https://accounts.google.com/o/oauth2/auth";
string redirectUri = "urn:ietf:wg:oauth:2.0:oob";
OAuth2Parameters parameters = new OAuth2Parameters()
{
ClientId = clientId,
ClientSecret = clientSecret,
RedirectUri = redirectUri,
Scope = scope
};
string authorizeUrl = OAuthUtil.CreateOAuth2AuthorizationUrl(parameters);
However the URL returned from this call gives this error when loaded in browser, (I read I need to load this url in a browser to get an Access Code returned):
Error: invalid_client
no application name
Would really like to know what I am doing wrong here or maybe a link to some example code that posts a product to Google Shopping if anyone knows of any.

Related

how reproduce result of powershell > az login in c#?

The result of "az login" is list of subscriptions: like here:
https://learn.microsoft.com/en-us/rest/api/resources/subscriptions/list?source=docs
How to make token for this request without providing tenantId or clientId exactly how it was made on website's login?
I can make token is quite close to required but do not have what I see inside token from website:
var functionCred = new Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential(clientId, secret);
var context = new AuthenticationContext("https://login.microsoftonline.com/" + tenantId, false);
var result = context.AcquireTokenAsync("https://management.core.windows.net/", functionCred).Result;
var token44 = result.AccessToken;
What should I do to improve token?
By default, az login command logs in with a user account. CLI will try to launch a web browser to log in interactively, if browser is not available then CLI will fall back to device code login.
For the question -
How to make token for this request without providing tenantId or clientId exactly how it was made on website's login?
It is not feasible to get an authentication token without using tenantId and clientId. Mostly token is generated by client id and secret. Token can be acquired via app registration + tenentId or user credentials + tenentId.
Below code snippet shows how you get the access token using Azure Active Directory Authentication Library (ADAL).
static AuthenticationResult AccessToken()
{
string resourceUri = "https://datacatalog.azure.com";
// register a client app and get a Client ID
string clientId = clientIDFromAzureAppRegistration;
//A redirect uri gives AAD more details about the specific application that it will authenticate.
string redirectUri = "https://login.live.com/oauth20_desktop.srf";
// Create an instance of AuthenticationContext to acquire an Azure access token
string authorityUri = "https://login.windows.net/common/oauth2/authorize";
AuthenticationContext authContext = new AuthenticationContext(authorityUri);
// AcquireToken takes a Client Id that Azure AD creates when you register your client app.
return authContext.AcquireToken(resourceUri, clientId, new Uri(redirectUri), PromptBehavior.RefreshSession);
}
For more information check this Steps to get an access token section of the Microsoft Document.

Unable to get access token from Google for Service Account

I'm trying to configure an application able to work with Gmail API. As you know to work with it we must have an access token. There are several way of requesting this token, but for my needs it should be a service account, because in future this program code will be inside the Windows Service... (so, there is no opportunity to receive the token manually by redirecting from Google URL, only a web-request and response is a way out)
So, what I have done already:
Created new project (in Google Cloud Platform);
Created new service account in this project (according to the steps mentioned here: https://developers.google.com/identity/protocols/oauth2/service-account#creatinganaccount );
Generated and downloaded *.P12 key;
Enabled domain-wide delegation [before step 4 as were suggested in many similar questions];
Authorized the scope "https://mail.google.com/" in G Suite admin account for correct Client Id (according to the steps mentioned here: https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority );
Used such simple code for authorization and requesting token:
const string serviceAccountEmail = "***test#oauthtester-271011.iam.gserviceaccount.com";
const string serviceAccountCertPath = #"C:\Users\user\Documents\Visual Studio 2017\Projects\OAuthTester\OAuthTester\bin\Debug\oauthtester-271011-bd2cced31ea5.p12";
const string serviceAccountCertPassword = "notasecret";
const string userEmail = "***oauthtest#***.com";
X509Certificate2 certificate = new X509Certificate2(
serviceAccountCertPath,
serviceAccountCertPassword,
X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { GoogleScope.ImapAndSmtp.Name }, //"https://mail.google.com/"
User = userEmail
}.FromCertificate(certificate));
credential.RequestAccessTokenAsync(CancellationToken.None).Wait();
Unfortunately, I'm facing with an error:
Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested.
I have also tried:
To change serviceAccountEmail to ClientId;
To create, remove and add again the Authorized access in G Suite for the same Client Id;
To delete and create another service account and then Authorize new Client Id in G Suite.
Unfortunately, each time I'm facing with the same error. Maybe somebody guesses what I do wrong?

Azure REST Api Authentication using C#

I would like to be able to get information about one of my Azure SQL databases using this call: https://learn.microsoft.com/en-gb/rest/api/sql/manageddatabases/manageddatabases_get
When I use the Try It button and login to my account it works perfectly, however I can't get my C# function app to get an authentication token so it can work in C#. I've spent 3 days on this. I have tried the Keyvault way but haven't managed to set up the permissions correctly. Forgetting Keyvault, the nearest I've got I think is by using this code but I don't know what my app password is:
// I am using:
// tenant id is the Azure AD client id
// client id is the application id of my function app in Azure AD
public static string GetAccessToken(string tenantId, string clientId, string clientSecret)
{
var authContextUrl = "https://login.windows.net/" + tenantId;
var authenticationContext = new AuthenticationContext(authContextUrl);
var credential = new ClientCredential(clientId, clientSecret );
var result = authenticationContext.AcquireTokenAsync(resource: "https://management.azure.com/", clientCredential: credential).Result;
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
var token = result.AccessToken;
return token;
}
When I use the Try It button and login to my account it works perfectly
When you click the Try it, you use the user credential with username and user_password to authenticate. And the code you provided is using App registered in Azure AD to authenticate, and it would work well with the following steps you have followed.
1.As silent said, you need to create a Service Principle in Azure Active Directory. You could refer to this article.
2.The Sign in value about TenantId, clientId and clientSecret you could refer to this link.
3.Finally, you would access to Azure SQL Database, you need to add permission to you Azure AD App. Click the App you registered in Azure AD before and click Settings, and add Require Permission. After adding API access, Grant Permission.
I found an answer that worked for me (after 3 days of trying different things and trying to read articles about it on the web - its not very well documented I don't think).
This link contains some powershell steps:
https://msftstack.wordpress.com/2016/01/03/how-to-call-the-azure-resource-manager-rest-api-from-c/
These are the steps I tried in PowerShell
Login-AzureRmAccount
Get-AzureRmSubscription
Select-AzureRmSubscription –SubscriptionID “id”
$SecurePassword=ConvertTo-SecureString <my password> –asplaintext –force
$azureAdApplication = New-AzureRmADApplication -DisplayName “my ARM App” -HomePage
“https://<a home page>” -IdentifierUris “https://<a home page>” -Password $SecurePassword
New-AzureRmADServicePrincipal -ApplicationId $azureAdApplication.ApplicationId
New-AzureRmRoleAssignment -RoleDefinitionName Contributor -ServicePrincipalName $azureAdApplication.ApplicationId
Get-AzureRmSubscription
$subscription = Get-AzureRmSubscription –SubscriptionId "id"
$creds=get-credential
(enter application id and password at this point)
Login-AzureRmAccount -Credential $creds -ServicePrincipal -Tenant $subscription.TenantId

Google Data API Authorization Redirect URI Mismatch

Background
I am wanting to write a small, personal web app in .NET Core 1.1 to interact with YouTube and make some things easier for me to do and I am following the tutorials/samples in Google's YouTube documentation. Sounds simple enough, right? ;)
Authenticating with Google's APIs seems impossible! I have done the following:
Created an account in the Google Developer Console
Created a new project in the Google Developer Console
Created a Web Application OAuth Client ID and added my Web App debug URI to the list of approved redirect URIs
Saved the json file provided after generating the OAuth Client ID to my system
In my application, my debug server url is set (and when my application launches in debug, it's using the url I set which is http://127.0.0.1:60077).
However, when I attempt to authenticate with Google's APIs, I recieve the following error:
That’s an error.
Error: redirect_uri_mismatch
The redirect URI in the request, http://127.0.0.1:63354/authorize/,
does not match the ones authorized for the OAuth client.
Problem
So now, for the problem. The only thing I can find when searching for a solution for this is people that say
just put the redirect URI in your approved redirect URIs
Unfortunately, the issue is that every single time my code attempts to authenticate with Google's APIs, the redirect URI it is using changes (the port changes even though I set a static port in the project's properties). I cannot seem to find a way to get it to use a static port. Any help or information would be awesome!
NOTE: Please don't say things like "why don't you just do it this other way that doesn't answer your question at all".
The code
client_id.json
{
"web": {
"client_id": "[MY_CLIENT_ID]",
"project_id": "[MY_PROJECT_ID]",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": "[MY_CLIENT_SECRET]",
"redirect_uris": [
"http://127.0.0.1:60077/authorize/"
]
}
}
Method That Is Attempting to Use API
public async Task<IActionResult> Test()
{
string ClientIdPath = #"C:\Path\To\My\client_id.json";
UserCredential credential;
using (var stream = new FileStream(ClientIdPath, FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
new[] { YouTubeService.Scope.YoutubeReadonly },
"user",
CancellationToken.None,
new FileDataStore(this.GetType().ToString())
);
}
var youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = this.GetType().ToString()
});
var channelsListRequest = youtubeService.Channels.List("contentDetails");
channelsListRequest.Mine = true;
// Retrieve the contentDetails part of the channel resource for the authenticated user's channel.
var channelsListResponse = await channelsListRequest.ExecuteAsync();
return Ok(channelsListResponse);
}
Project Properties
The Original Answer works, but it is NOT the best way to do this for an ASP.NET Web Application. See the update below for a better way to handle the flow for an ASP.NET Web Application.
Original Answer
So, I figured this out. The issue is that Google thinks of a web app as a JavaScript based web application and NOT a web app with server side processing. Thus, you CANNOT create a Web Application OAuth Client ID in the Google Developer Console for a server based web application.
The solution is to select the type Other when creating an OAuth Client ID in the Google Developer Console. This will have Google treat it as an installed application and NOT a JavaScript application, thus not requiring a redirect URI to handle the callback.
It's somewhat confusing as Google's documentation for .NET tells you to create a Web App OAuth Client ID.
Feb 16, 2018 Updated Better Answer:
I wanted to provide an update to this answer. Though, what I said above works, this is NOT the best way to implement the OAuth workflow for a ASP.NET solution. There is a better way which actually uses a proper OAuth 2.0 flow. Google's documentation is terrible in regards to this (especially for .NET), so I'll provide a simple implementation example here. The sample is using ASP.NET core, but it's easily adapted to the full .NET framework :)
Note: Google does have a Google.Apis.Auth.MVC package to help simplifiy this OAuth 2.0 flow, but unfortunately it's coupled to a specific MVC implementation and does not work for ASP.NET Core or Web API. So, I wouldn't use it. The example I'll be giving will work for ALL ASP.NET applications. This same code flow can be used for any of the Google APIs you've enabled as it's dependent on the scopes you are requesting.
Also, I am assuming you have your application set up in your Google Developer dashboard. That is to say that you have created an application, enabled the necessary YouTube APIs, created a Web Application Client, and set your allowed redirect urls properly.
The flow will work like this:
The user clicks a button (e.g. Add YouTube)
The View calls a method on the Controller to obtain an Authorization URL
On the controller method, we ask Google to give us an Authorization URL based on our client credentials (the ones created in the Google Developer Dashboard) and provide Google with a Redirect URL for our application (this Redirect URL must be in your list of accepted Redirect URLs for your Google Application)
Google gives us back an Authorization URL
We redirect the user to that Authorization URL
User grants our application access
Google gives our application back a special access code using the Redirect URL we provided Google on the request
We use that access code to get the Oauth tokens for the user
We save the Oauth tokens for the user
You need the following NuGet Packages
Google.Apis
Google.Apis.Auth
Google.Apis.Core
Google.apis.YouTube.v3
The Model
public class ExampleModel
{
public bool UserHasYoutubeToken { get; set; }
}
The Controller
public class ExampleController : Controller
{
// I'm assuming you have some sort of service that can read users from and update users to your database
private IUserService userService;
public ExampleController(IUserService userService)
{
this.userService = userService;
}
public async Task<IActionResult> Index()
{
var userId = // Get your user's ID however you get it
// I'm assuming you have some way of knowing if a user has an access token for YouTube or not
var userHasToken = this.userService.UserHasYoutubeToken(userId);
var model = new ExampleModel { UserHasYoutubeToken = userHasToken }
return View(model);
}
// This is a method we'll use to obtain the authorization code flow
private AuthorizationCodeFlow GetGoogleAuthorizationCodeFlow(params string[] scopes)
{
var clientIdPath = #"C:\Path\To\My\client_id.json";
using (var fileStream = new FileStream(clientIdPath, FileMode.Open, FileAccess.Read))
{
var clientSecrets = GoogleClientSecrets.Load(stream).Secrets;
var initializer = new GoogleAuthorizationCodeFlow.Initializer { ClientSecrets = clientSecrets, Scopes = scopes };
var googleAuthorizationCodeFlow = new GoogleAuthorizationCodeFlow(initializer);
return googleAuthorizationCodeFlow;
}
}
// This is a route that your View will call (we'll call it using JQuery)
[HttpPost]
public async Task<string> GetAuthorizationUrl()
{
// First, we need to build a redirect url that Google will use to redirect back to the application after the user grants access
var protocol = Request.IsHttps ? "https" : "http";
var redirectUrl = $"{protocol}://{Request.Host}/{Url.Action(nameof(this.GetYoutubeAuthenticationToken)).TrimStart('/')}";
// Next, let's define the scopes we'll be accessing. We are requesting YouTubeForceSsl so we can manage a user's YouTube account.
var scopes = new[] { YouTubeService.Scope.YoutubeForceSsl };
// Now, let's grab the AuthorizationCodeFlow that will generate a unique authorization URL to redirect our user to
var googleAuthorizationCodeFlow = this.GetGoogleAuthorizationCodeFlow(scopes);
var codeRequestUrl = googleAuthorizationCodeFlow.CreateAuthorizationCodeRequest(redirectUrl);
codeRequestUrl.ResponseType = "code";
// Build the url
var authorizationUrl = codeRequestUrl.Build();
// Give it back to our caller for the redirect
return authorizationUrl;
}
public async Task<IActionResult> GetYoutubeAuthenticationToken([FromQuery] string code)
{
if(string.IsNullOrEmpty(code))
{
/*
This means the user canceled and did not grant us access. In this case, there will be a query parameter
on the request URL called 'error' that will have the error message. You can handle this case however.
Here, we'll just not do anything, but you should write code to handle this case however your application
needs to.
*/
}
// The userId is the ID of the user as it relates to YOUR application (NOT their Youtube Id).
// This is the User ID that you assigned them whenever they signed up or however you uniquely identify people using your application
var userId = // Get your user's ID however you do (whether it's on a claim or you have it stored in session or somewhere else)
// We need to build the same redirect url again. Google uses this for validaiton I think...? Not sure what it's used for
// at this stage, I just know we need it :)
var protocol = Request.IsHttps ? "https" : "http";
var redirectUrl = $"{protocol}://{Request.Host}/{Url.Action(nameof(this.GetYoutubeAuthenticationToken)).TrimStart('/')}";
// Now, let's ask Youtube for our OAuth token that will let us do awesome things for the user
var scopes = new[] { YouTubeService.Scope.YoutubeForceSsl };
var googleAuthorizationCodeFlow = this.GetYoutubeAuthorizationCodeFlow(scopes);
var token = await googleAuthorizationCodeFlow.ExchangeCodeForTokenAsync(userId, code, redirectUrl, CancellationToken.None);
// Now, you need to store this token in rlation to your user. So, however you save your user data, just make sure you
// save the token for your user. This is the token you'll use to build up the UserCredentials needed to act on behalf
// of the user.
var tokenJson = JsonConvert.SerializeObject(token);
await this.userService.SaveUserToken(userId, tokenJson);
// Now that we've got access to the user's YouTube account, let's get back
// to our application :)
return RedirectToAction(nameof(this.Index));
}
}
The View
#using YourApplication.Controllers
#model YourApplication.Models.ExampleModel
<div>
#if(Model.UserHasYoutubeToken)
{
<p>YAY! We have access to your YouTube account!</p>
}
else
{
<button id="addYoutube">Add YouTube</button>
}
</div>
<script>
$(document).ready(function () {
var addYoutubeUrl = '#Url.Action(nameof(ExampleController.GetAuthorizationUrl))';
// When the user clicks the 'Add YouTube' button, we'll call the server
// to get the Authorization URL Google built for us, then redirect the
// user to it.
$('#addYoutube').click(function () {
$.post(addYoutubeUrl, function (result) {
if (result) {
window.location.href = result;
}
});
});
});
</script>
As referred here, you need to specify a fix port for the ASP.NET development server like How to fix a port number in asp.NET development server and add this url with the fix port to the allowed urls. Also as stated in this thread, when your browser redirects the user to Google's oAuth page, you should be passing as a parameter the redirect URI you want Google's server to return to with the token response.
I noticed that there is easy non-programmatic way around.
If you have typical monotlith application built in typical MS convention(so not compatible with 12factor and typical DDD) there is an option to tell your Proxy WWW server to rewrite all requests from HTTP to HTTPS so even if you have set up Web App on http://localhost:5000 and then added in Google API url like: http://your.domain.net/sigin-google, it will work perfectly and it is not that bas because it is much safer to set up main WWW to rewrite all to HTTPS.
It is not very good practice I guess however it makes sense and does the job.
I've struggled with this issue for hours in a .net Core application. What finally fixed it for me was, in the Google developers console, to create and use a credential for "Desktop app" instead of a "Web application".
Yeah!! Using credentials of desktop app instead of web app worked for me fine. It took me more than 2 days to figure out this problem. The main problem is that google auth library dose not adding or supporting http://localhost:8000 as redirect uri for web app creds but credentials of desktop app fixed that issue. Cause its supporting http://___ connection instead of https: connection for redirect uri

Differences between AcquireTokenAsync and LoginAsync in Xamarin Native

TL;DR
What is the difference between authenticating users with AuthenticationContext.AcquireTokenAsync() and MobileServiceClient.LoginAsync() ?
Can I use the token from the first method to authenticate a user in the second?
Long Version
I've been trying to authenticate users via a mobile device (iOS) for a mobile service in Azure with Xamarin Native (not Forms).
There are enough tutorials online to get you started but in the process, I got lost and confused...
What's working at the moment is the following; which has the user enter his credentials in another page and returns a JWT token which (if decoded here1) has the claims listed here2.
Moreover, this token is authorized in controllers with the [Authorize] attribute in requests with an Authorization header and a Bearer token.
Note: the following constants are taken from the registered applications in Active Directory (Native and Web App / API).
public const string Authority = #"https://login.windows.net/******.com";
public const string GraphResource = #"https://*******.azurewebsites.net/********";
public const string ClientId = "046b****-****-****-****-********0290";
public const string Resource = #"https://******.azurewebsites.net/.auth/login/done";
var authContext = new AuthenticationContext(Authority);
if (authContext.TokenCache.ReadItems().Any(c => c.Authority == Authority))
{
authContext = new AuthenticationContext(authContext.TokenCache.ReadItems().First().Authority);
}
var uri = new Uri(Resource);
var platformParams = new PlatformParameters(UIApplication.SharedApplication.KeyWindow.RootViewController);
AuthenticationResult authResult = await authContext.AcquireTokenAsync(GraphResource, ClientId, uri, platformParams);
Another working authentication flow I tried is the following; which does the same with the difference that it informs the user that the app requires permissions to access some resources.
If allowed, a JWT token (with less characters than the previous one) is returned with less payload data. This token though, won't pass the authorization attribute just like the previous one.
public const string AadResource = #"https://******.azurewebsites.net/.auth/aad";
var client = new MobileServiceClient(AadResource);
var rootView = UIApplication.SharedApplication.KeyWindow.RootViewController;
MobileServiceUser user = await client.LoginAsync(rootView, "aad");
Obviously, the return type is different, but, what is the main difference between these two authentication methods?
Additionally, another headache comes from trying to achieve this3 at the very end of the article. I already have the token from the first aforementioned method but when I try to follow the client flow with the token in client.LoginAsync() the following error is returned:
The resource you are looking for has been removed, had its name changed, or is temporarily unavailable.
Link References:
https://jwt.io/
https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-token-and-claims
https://adrianhall.github.io/develop-mobile-apps-with-csharp-and-azure/chapter2/enterprise/
https://www.reddit.com/r/xamarindevelopers/comments/6dw928/differences_between_acquiretokenasync/
Edit (30 May 2017)
The Why are they different? has been answered on this4 reddit post by the same person (pdx mobilist / saltydogdev) and the simple answer is claims.
Yes. You can insert a token into the MobileServicesClient and then use it had been authenticated directly. That's the beauty of bearer tokens.
Just set the MobileServiceClient CurrentUser:
MobileServiceclient Client;
...
Client.CurrentUser = new MobileServiceUser(username)
{ MobileServiceAuthenticationToken = authtoken};
Edit:
The reason they are different is because each library is requesting a different set of claims. The reason they still work is that the basic information for authenticating/validating the token is there. I'm not sure what are the specific required claims. At a minimum it would be the user id AND that the signature is valid. They are doing the same basic thing, MobileServiceClient just requests less claims.
I believe that the MobileServicesClient can authenticate against Azure AD, if you set up the mobile service correctly. So you should be able to just use the MobileServiceClient.
Here is the document that describes how this works: https://learn.microsoft.com/en-us/azure/app-service-mobile/app-service-mobile-how-to-configure-active-directory-authentication

Categories