Google Calendar V3 2 Legged authentication fails - c#

I'm trying to create web page that access the (business) private calendar of the company and insert events if the time slot is available. Still I'm facing an authentication problem.
The API manual states that I should use an API key and Oauth2LeggedAuthenticator, so I did all this and the request that is fired is quite okey (it has a oauth token and such) But still the response is an exception with Invalid Credentials; Easy to say is that my credentials are wrong, still clientID, clientSecret and API Key are valid; I doubt the 2 last params of the 2legged authenticater, is this correct?
var provider = new NativeApplicationClient(GoogleAuthenticationServer.Description);
provider.ClientIdentifier = ClientCredentials.ClientID;
provider.ClientSecret = ClientCredentials.ClientSecret;
var authenticator =
new OAuth2LeggedAuthenticator(ClientCredentials.ClientID, ClientCredentials.ClientSecret, "myworkusername", "workdomain.com");
Google.Apis.Calendar.v3.CalendarService service = new Google.Apis.Calendar.v3.CalendarService(authenticator);
service.Key = ClientCredentials.ApiKey;
var result = service.CalendarList.List().Fetch();
Assert.IsTrue(result.Items.Count > 0);

NB: At the time of writing you can only used 2-legged authentication with Google Apps for Business/Eduction, this won't work on personal accounts as there's no way to get an OAuth 1.0 key/secret pair, you will have to use online authentication at least once (but you can use the out-of-browser option so you don't have to create a dedicated page).
Your code is correct apart from you don't need the first three lines relating to the NativeApplicationClient. This is most likely failing because you haven't properly set the OAuth keys, this causes 401s.
The other thing that causes 401s is using "matt#example.com" instead of "matt" as the username, the username is without including your domain.
To setup OAuth follow the instructions in this article from Google.
The most important parts to note are "Allow access to all APIs" must be unchecked and you have to individually grant access to all the APIs. If this hasn't been done you will get a 401 Invalid Credentials error. You then also need to turn those services on in the api console. If the api console step hasn't been done you will get a different error of 403 Daily Limit Exceeded.
This will cause you problems if you were previously relying on the "Allow access to all APIs" to use various services, you will have to grant them all individually as far as I understand it to use the v3 APIs. This seems to have been confirmed by google (4th reply by Nicolas Garnier) and is supposedly a bug, but that is an old post so it looks as if it's here to stay.
For reference once this has been done, this code will work, which in essence is the same as yours:
var auth = new OAuth2LeggedAuthenticator(domainName, consumerSecret, usernameWithoutDomain, domainName); //domainName is presently used as the OAuth ConsumerKey for Google's 2legged OAuth
var service = new CalendarService(auth);
service.Key = serviceKey;
var results = service.CalendarList.List().Fetch();
Console.WriteLine(results.Items.Count);
So in summary:
In Google Apps "Manage this Domain" > "Advanced Tools"
Using "Manage OAuth domain key" enable key, generate secret, uncheck "Allow access to all APIs".
Using "Manage third party OAuth Client access" enable the APIs you want access to using your domain as "Client Name" and the APIs you want to access e.g. "http://www.google.com/calendar/feeds/" for the calendar.
Then finally create a project in the API console, use the APIKey as the serviceKey in the above example and turn on the APIs you need to access.
I am answering this as I kept hitting this question when I was trying to find out why my code was constantly returning 401s. Hope this helps someone as the Google instructions are awful and scattered all over the place at the moment.

Related

PayPal oauth2 authorization and .net core

So I'm trying to set up PayPal sign-in within my .net core application. I'm using this 3rd party library to do so: https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
I'm building my application upon Umbraco CMS, and within the appropriate section I use the following:
builder.AddMemberExternalLogins(logins =>
{
logins.AddMemberLogin(
memberAuthenticationBuilder =>
{
memberAuthenticationBuilder.AddPaypal(
memberAuthenticationBuilder.SchemeForMembers(PayPalMemberExternalLoginProviderOptions.SchemeName),
options =>
{
options.ClientId = clientId;
options.ClientSecret = clientSecret;
});
});
});
Out of the box, this does not work. From reading around it seems that it seems the authorization endpoints are using the live values, and these are not appropriate for testing with Paypal's sandbox endpoints.
After reading the following thread: https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers/issues/198 I can see that this user had to update the endpoints manually to use different values:
.AddPaypal(options =>
{
options.ClientId = Startup.Configuration["Authentication:Paypal:ClientId"];
options.ClientSecret = Startup.Configuration["Authentication:Paypal:ClientSecret"];
options.AuthorizationEndpoint = "https://www.sandbox.paypal.com/webapps/auth/protocol/openidconnect/v1/authorize";
options.TokenEndpoint = "https://api.sandbox.paypal.com/v1/identity/openidconnect/tokenservice";
options.UserInformationEndpoint = "https://api.sandbox.paypal.com/v1/identity/openidconnect/userinfo?schema=openid";
})
Now once updating these values, the authorization seems to work. But I've found it to be very unreliable. On my personal dev machine it seems to allow me to authorize, but when testing with another colleague, he finds he is constantly logged out and sometimes it doesn't authorize at all.
When I look at the previous thread I can see that this was written in 2017, and I'm wondering if the endpoints have been updated or the process has been changed entirely by Paypal, but I can't find any documentation on this.
Basically I'm looking for the simplest way to integrate Paypal sign-in on my application. When I read Paypal's documentation (https://developer.paypal.com/docs/log-in-with-paypal/integrate/) I only see mention of refreshing the token, I don't see anything about an authorization endpoint or user information endpoint.
I'm getting quite confused and I'm wondering if anyone has successfully managed to set up a Paypal sign-in with a .net core application in 2022 and whether they have any sample code.
Any pointers would be greatly appreciated. Thanks.
That does seem like an old integration. I would advise following https://developer.paypal.com/docs/log-in-with-paypal/integrate/ from scratch, which can be implemented in any language for the eventual server-side calls. The initial client-side authorization is done in either the JS popup or using a redirect (JS is better)
After exchanging the resulting authorization_code for a refresh token, that token can then be used to obtain information about the user's account (or do API operations involving it), according to the scopes that were authorized.

Token access blocked when posting request from published Azure function

I am struggling to get a token from "https://login.microsoftonline.com/common/oauth2/token" with an Azure function by a post-request. The token will give permissions to access SharePoint though CSOM. Here is my code snippet with the post request:
var clientId = defaultAADAppId;
var body = $"resource={resource}&client_id={clientId}&grant_type=password&username={HttpUtility.UrlEncode(username)}&password={HttpUtility.UrlEncode(password)}";
using (var stringContent = new StringContent(body, Encoding.UTF8, "application/x-www-form-urlencoded"))
{
var result = await httpClient.PostAsync(tokenEndpoint, stringContent);
var tokenResult = JsonSerializer.Deserialize<JsonElement>(result);
var token = tokenResult.GetProperty("access_token").GetString();
}
When testing locally, both when running the function in Visual studio and when I try with Postman, I am able to achieve an access token. However, as soon as I publish the function to my Function app in Azure I receive the following error message:
"AADSTS53003: Access has been blocked by Conditional Access policies. The access policy does not allow token issuance"
I have enabled an app registration in the portal and as mentioned, it all works fine until I publish everything to Azure.
Any ideas on how to solve this?
I got it to work now. First of all I reviewed the CA policies as #CaseyCrookston suggested. What I found out was that our CA policies blocked calls outside the country we operate from. However, the calls from the App registration/Azure function were registered from the Azure data centre location and thus, blocked by our CA policies. When running them locally the calls where registered in my country and therefore no errors were showing while debugging.
My first step was trying to add my Client app to the CA policy, which was not possible. The client/secret authentication that I used based on the suggestions in this CSOM guide by Microsoft prevented the App registration to be whitelisted from the CA policies (Github issue).
Based on this I had to change the authentication to a Certificate-based authentication as suggested here: Access token request with a certificate and here: SO answer. With this I was able to whitelist the App registration in the CA policies and successfully authenticate to the Sharepoint CSOM.
As the error message says, your app is blocked by CA policy. Possible causes can be unknown client app, blocking external IP addresses, etc.
You can perform one of the below workarounds:
Add your Client app to your CA policy.
I wouldn’t recommend this because this affects your security - if you take the risk you could exclude the “Microsoft Azure Management” from your CA policy which blocks unknown clients / requires device state and still protect the sign-in with MFA.
A better approach is to use another OAuth 2.0 and OpenID connect flow like the delegated flow where you sign-in directly within the app, if possible.

What is the proper way to work with the OneDrive API?

I want to interact with OneDrive in my WinForms application. Sadly, the Azure quick start samples do not include WinForms, just UWD.
The flow on what I have to do is consistent, namely given my Client ID, I have to obtain an Authentication Code. Given the authentication code, I can then obtain an Access Code, which will allow me to interact in a RESTful way with the OneDrive API. My plan is to have the authentication piece go in a .Net Framework Library and the file IO calls will go in another library that has no user interface access, as it will go in a Windows Service. I would pass the Access Token to the service.
AADSTS50059: No tenant-identifying information found in either the request or implied by any provided credentials.
This error corresponds to the following code fragment that I lifted from the sample .Net Core daemon quick start code.
Note: I was playing around with Scopes as I kept receiving scope errors and I saw one article, whose link I should have kept, which stated to use the API and default scope.
public bool GetRestAuthenticationToken(out string tokenAuthentication)
{
tokenAuthentication = null;
try
{
IConfidentialClientApplication app;
app = ConfidentialClientApplicationBuilder.Create(Authenticate.AppClientId)
.WithClientSecret(Authenticate.AppClientSecret)
.WithAuthority(new Uri(#"https://login.microsoftonline.com/common/oauth2/nativeclient"))
.Build();
string scope = $"onedrive.readwrite offline_access";
System.Collections.Generic.List<string> enumScopes = new System.Collections.Generic.List<string>();
enumScopes.Add("api://<GUID>/.default");
//enumScopes.Add(Authenticate.Scopes[1]);
var result = Task.Run(async () => await app.AcquireTokenForClient(enumScopes).ExecuteAsync()).Result;
...
}
...
}
I believe that I have my application configured properly now on Azure, but am not 100% positive.
API Permissions:
Authentication:
Desktop Applications: https://login.microsoftonline.com/common/oauth2/nativeclient
Desktop Applications: https://login.live.com/oauth20_desktop.srf
Implicit Grants: Access tokens & ID tokens
Live SDK support (Yes)
Default client type (Yes)
Others:
I do have a client secret and kept note of all the Overview GUIDs
Microsoft Doc 1
I tried several different URLs, but only the one not commented out works with the fragment above, but throws the referenced error.
//string redirect_uri = #"https://www.myapp.com/auth";
//string redirect_uri = "https://login.live.com/oauth20_desktop.srf";
string url = #"https://login.microsoftonline.com/common/oauth2/nativeclient";
//string url = $"https://login.live.com/oauth20_authorize.srf?client_id={appClientId}&scope={scope}&response_type=code&redirect_uri={redirect_uri}";
//string url = $"https://login.microsoftonline.com/common/oauth2/v2.0/authorize?" +
// $"client_id={Authenticate.AppClientId}&" +
// $"scope={scope}&" +
// $"response_type=token&" +
// $"redirect_uri={redirect_uri}";
The goal is the same, namely to obtain an access token that I can use with RESTful calls to work with files and/or directories on OneDrive, e.g.
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
client.GetAsync(...);
You are trying to implement Client credentials grant type to get the access token.
Based on MSAL initialization, Authority is
(Optional) The STS endpoint for user to authenticate. Usually
https://login.microsoftonline.com/{tenant} for public cloud, where
{tenant} is the name of your tenant or your tenant Id.
We assume that your tenant is "myTenent.onmicrosoft.com", then you should set it as https://login.microsoftonline.com/myTenent.onmicrosoft.com here.
I notice that you specify a scope "onedrive.readwrite" in your code. But it's not a valid permission of Microsoft Graph. The default scope of Microsoft Graph is https://graph.microsoft.com/.default.

My AzureAD - registered application doesn't have the rights to do anything ...?

I'm writing a c# program right now that tries to authenticate with Azure to make a generic http request. I finally got the code working and I wanted to test the features but for every request I make I get the following error code in response:
{"error":{"code": "AuthorizationFailed", "message":"The client "(id of the app I registered in AzureAD)" with object id "(same value as before)" does not have authorization to perform action 'Microsoft.Authorization/roleAssignments/read' over scope '/subscriptions/(mysubscriptionid)'."}}.
The thing is ... The account I use to set everything up is a global admin. I checked every permission box in AzureAD I could find...
(that's 8 Application Permissions and 9 Delegated permissions in the Windows Azure Active Directory API and 1 delegated Permission in the Windows Azure Service Management API, though I don't know why there aren't more possible permissions for Azure Service Management)
the relevant code is rather basic but it works so I don't feel like I need post it, I'll just say that I obtain the Token using Microsoft.IdentityModel.Clients.ActiveDirectory.AcquireTokenAsync() with
authorityUri = "https://login.windows.net/(mytenantid)",
string resourceUri = "https://management.azure.com/";
AuthenticationContext authContext = new AuthenticationContext(authorityUri);
var res = authContext.AcquireTokenAsync(resourceUri, new
ClientCredential(clientId,clientSecret));
return res.Result;
and make the Request to
https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Authorization/roleAssignments?api-version=2016-03-01&$filter=atScope()
(as an example, this one is supposed to call the roles).
Tried several different types of GET Requests to different URIs, all give similar errors.
I'm thinking it might not be an issue with the code but a setting in the AzurePortal but everything I found there seems set up right (or rather "universal access").
According to your description, you forget to grant your service principal. You could add it on Azure Portal. You could grant Contributor or Owner role to it.
Please refer to this link:Assign application to role.

Service Account Google Analytics OAuth AccessType = Offline C#

I've got credentials of an account with access to Google Analytics,
I'm looking to utilise the Analytics Core Reporting API http://code.google.com/apis/analytics/docs/gdata/home.html
I've found examples which use username/password calling setUserCredentials, but have seen comments this is less secure/has a low request limit (And doesn't exist in the lastest client).
Plus I've seem examples which use oauth, but require user interaction and grant access to the users google account.
However I'm looking to run a service which doesn't require any user interaction, and connects to a predefined google account (un-related to the user viewing it).
I can then store the results in a database, and end users can query the results from the database.
I've seen information about using AccessType = Offline when you first login, which then returns an access token and a refreshtoken.
http://code.google.com/apis/accounts/docs/OAuth2WebServer.html#offline
In my example though, the end user will never login to the application.
Could I have a seperate admin application which gets a refresh token, and stores the refresh token in the config/lookup table?
Then the main application can use the refresh token pulling from the config/lookup table, and get an access token to be able to query the Google Analytics account.
I'm looking for a C# example which uses AccessType = Offline, and seperates out the fetching of the refresh token and using the refresh token to get an access token to query the google analytics account.
Create your app https://code.google.com/apis/console/
For you App, turn on access to Google Analytics, and create an OAuth 2.0 client ID for your website.
Browse to:
https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=YOUR_APP_ID.apps.googleusercontent.com&access_type=offline&scope=https://www.googleapis.com/auth/analytics.readonly&redirect_uri=HTTP://YOUR_CALL_BACK_URL
Having changed YOUR_APP_ID, YOUR_CALL_BACK_URL to the relevant values.
Important to include access_type=offline.
Press Grant Access, this will redirect to HTTP://YOUR_CALL_BACK_URL?code=THIS_IS_YOUR_CODE. Copy the code in the URL.
With the code, request the Refresh Token using CMD prompt.
curl -d "code=THIS_IS_YOUR_CODE&client_id=YOUR_APP_ID.apps.googleusercontent.com&client_secret=YOUR_APPS_SECRET_CODE&redirect_uri=HTTP://YOUR_CALL_BACK_URL&grant_type=authorization_code" https://accounts.google.com/o/oauth2/token
Having changed THIS_IS_YOUR_CODE, YOUR_APP_ID, YOUR_APPS_SECRET_CODE, YOUR_CALL_BACK_URL to the relevant values.
Record the refresh_token returned.
Download the latest version of the Core Reporting V3.0 .net libraries
http://code.google.com/p/google-api-dotnet-client/wiki/Downloads
There is a bug in the current version of Google.Apis.Analytics.v3.cs, to fix this copy the code in this file to your local solution (And don’t reference Google.Apis.Analytics.v3.bin)
http://code.google.com/p/google-api-dotnet-client/source/browse/Services/Google.Apis.Analytics.v3.cs?repo=samples&name=20111123-1.1.4344-beta
And change the property Dimensions from a List<system.string> to a string.
Or you'll get an error like me and this guy did http://www.evolutiadesign.co.uk/blog/using-the-google-analytics-api-with-c-shar/
You can then use your Refresh Token, to generate you an Access Token without user interaction, and use the Access Token to run a report against Google Analytics.
using System;
using DotNetOpenAuth.OAuth2;
using Google.Apis.Authentication.OAuth2;
using AnalyticsService = Google.Apis.Analytics.v3.AnalyticsService;
class Program
{
public static void Main()
{
var client = new WebServerClient(GoogleAuthenticationServer.Description, "YOUR_APP_ID.apps.googleusercontent.com", "YOUR_APPS_SECRET_CODE");
var auth = new OAuth2Authenticator<WebServerClient>(client, Authenticate);
var asv = new AnalyticsService(auth);
var request = asv.Report.Get("2012-02-20", "2012-01-01", "ga:visitors", "ga:YOUR_GOOGLE_ANALYTICS_ACCOUNT_ID");
request.Dimensions = "ga:pagePath";
request.Sort = "-ga:visitors";
request.MaxResults = 5;
var report = request.Fetch();
Console.ReadLine();
}
private static IAuthorizationState Authenticate(WebServerClient client)
{
IAuthorizationState state = new AuthorizationState(new string[]{}) { RefreshToken = "REFRESH_TOKEN" };
client.RefreshToken(state);
return state;
}
}
Great Answer Ian and it helped me to get going in the correct Direction more than any other answer I could find online. Something must have changed in the AnalyticsService object because the line:
var request = asv.Report.Get("2012-02-20", "2012-01-01", "ga:visitors", "ga:YOUR_GOOGLE_ANALYTICS_ACCOUNT_ID");
did not work for me and I had to use the following:
var request = asv.Data.Ga.Get("ga:YOUR_GOOGLE_ANALYTICS_ACCOUNT_ID", "2012-01-01", "2012-02-20", "ga:visitors");
Hopefully this will help others like your answer helped me. Thanks!
Ian's answer helped me a lot but I kept getting an error running the curl command. Did some research and found that the steps to get the access code and refresh token can be made easier by going to https://code.google.com/oauthplayground/ and checking your oAuth configuration settings. Top right of the page there is a settings button. selected "Use your own OAuth credentials". You can get your access code and request a refresh token all from here.
Hope this helps.
You can manually get a refresh token from the OAuth Playground.
If you are needing a refresh token for a Service Account as I was, make sure you
Click on the settings on the right.
Check Use your own OAuth credentials
Fill in your Client ID and Secret
Close the settings
Click the Refresh button on step 2
Then save the refresh token for use in your app

Categories