The system administrator enabled 2FA so I'm having to go through and update some programs to utilizes this for accessing the Dynamics API. Otherwise, we received the following:
{
"error":"interaction_required",
"error_description":"AADSTS50076: Due to a configuration change made by your administrator, or because you moved to a new location, you must use multi-factor authentication to access '00000007-0000-0000-c000-000000000000'.\r\nTrace ID: 24822bc6-9e93-476d-8580-fd04e3889300\r\nCorrelation ID: efd5dbc5-dead-4665-a5a6-570ae15a55fb\r\nTimestamp: 2020-02-24 20:35:15Z",
"error_codes":[
50076
],
"timestamp":"2020-02-24 20:35:15Z",
"trace_id":"24822bc6-9e93-476d-8580-fd04e3889300",
"correlation_id":"efd5dbc5-dead-4665-a5a6-570ae15a55fb",
"error_uri":"https://login.windows.net/error?code=50076",
"suberror":"basic_action"
}
This article makes it sound pretty straight forward and is the process we had to use for Outlook and other apps. Basically, generating an App Password.
However, I'm trying to use the App Password instead of the Default password we've used for a while and still am unable to get an access token for the program to use.
Here is what we've been using:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace CrmQbInvoiceSync
{
class CrmAuthorization
{
// Serialize the JSON response for the access_token
public class AccessToken
{
public string access_token { get; set; }
}
public static async Task<string> GetCrmAccessToken()
{
var values = new Dictionary<string, string>
{
// Connection parameters
{"client_id", ConfigurationManager.AppSettings["clientId"]},
{"client_secret", ConfigurationManager.AppSettings["clientSecret"]},
{"resource", ConfigurationManager.AppSettings["crmOrg"]},
{"username", ConfigurationManager.AppSettings["username"]},
{"password", ConfigurationManager.AppSettings["userPassword"]},
{"grant_type", "password"}
};
// Console.WriteLine(values);
// Convert to x-www-form-urlencoded
var content = new FormUrlEncodedContent(values);
try
{
// Send the x-www-form-urlencoded info to the OAuth2 end point
HttpResponseMessage response = await Services.Client.PostAsync(ConfigurationManager.AppSettings["crmUri"], content);
// Get the body from the response
var responseContent = await response.Content.ReadAsStringAsync();
// Extract out the access token from the response
AccessToken responseBody = JsonConvert.DeserializeObject<AccessToken>(responseContent);
// Test if there is an access token present
if (responseBody.access_token != null)
{
// If there is an access token, take it and use it in
// generating the query
var accessToken = responseBody.access_token;
return accessToken;
}
else
{
var accessToken = "Could not get the access token.";
Services.WriteLogFile(accessToken);
Console.WriteLine(accessToken);
return null;
}
}
catch (Exception e)
{
var error = e;
Services.WriteLogFile(error.ToString());
Console.WriteLine(error);
throw;
}
}
}
}
The {"password", ConfigurationManager.AppSettings["userPassword"]} line is what should be affected so I updated the AppSettings with the new App Password. Get this error, but seems like it should be working given I'm using the App Password:
Formatted JSON Data
{
"error":"invalid_grant",
"error_description":"AADSTS50126: Error validating credentials due to invalid username or password.\r\nTrace ID: 17bf1365-32a0-439e-bd99-9eaf8e3bab00\r\nCorrelation ID: 4d24cac1-dae9-49b7-961f-c7c739f885f4\r\nTimestamp: 2020-02-24 20:33:43Z",
"error_codes":[
50126
],
"timestamp":"2020-02-24 20:33:43Z",
"trace_id":"17bf1365-32a0-439e-bd99-9eaf8e3bab00",
"correlation_id":"4d24cac1-dae9-49b7-961f-c7c739f885f4",
"error_uri":"https://login.windows.net/error?code=50126"
}
Really, not sure if I should be updating something else in the program to accommodate MFA, but articles I've read indicate I should just be generating the App Password and it should be good. Suggestions?
I suggest you use a refresh token to refresh the access token. With refresh token, you can bypass this limitation of MFA.
To get a refresh token, you need to follow Azure AD OAuth2 auth code flow to get a refresh token interactively. And then you can get a new token with the refresh token you got.
Notice that the refresh token should be kept in secret. If it was compromised, you can revoke all refresh tokens of a specific use with PowerShell Revoke-AzureADUserAllRefreshToken
Related
I'm trying to list my coinbase accounts with the following API https://api.coinbase.com/.
I used the following path GET https://api.coinbase.com/v2/accounts.
I systematically get a reject 'Unauthorized (401) error'
Below some important checks I have done:
My software correctly get ressource which does not require specific authorization level like /v2/time for instance
I have waited 48 hours since my API key creation
I have set all the privileges on all the accounts (BTC wallet, EUR wallet etc)
I manage to view my balance on my trading account https://api.pro.coinbase.com/
Any ideas ?
HttpClient _httpClient
var timeStamp = GetSecondsSinceEpoch();
var signature = ComputeSignature($"{timeStamp}GET/v2/accounts", privateKey);
_httpClient.DefaultRequestHeaders.Add("User-Agent", "MyAppClient");
_httpClient.DefaultRequestHeaders.Add("CB-ACCESS-KEY", publicKey);
_httpClient.DefaultRequestHeaders.Add("CB-ACCESS-SIGN", signature);
_httpClient.DefaultRequestHeaders.Add("CB-ACCESS-TIMESTAMP", timeStamp);
private static string ComputeSignature(string preHashString, string privateKey)
{
try
{
using (var hmac = new HMACSHA256(Convert.FromBase64String(privateKey)))
{
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(preHashString));
return Convert.ToBase64String(hash);
}
}
catch (Exception)
{
//
}
return string.Empty;
}
var response = await _httpClient.DefaultRequestHeaders.GetAsync(new Uri("https://api.coinbase.com/v2/accounts"));
response.EnsureSuccessStatusCode();
var jsonString = await response.Content.ReadAsStringAsync();
EDIT
In fact for access to GET https://api.coinbase.com/v2/accounts, it seems that we should use OAuth authorization and not API KEY, so it takes the form of :
_httpClient.DefaultRequestHeaders.Authorization
= new AuthenticationHeaderValue("Bearer","MyAccessToken")
Once I have created Oauth access via my Coinbase Main Account, I successfully manage to retrieve my accounts information by using Invoke-WebRequest command but I still get Unauthorized (401) error with the CSharp code equivalent
I have the following function to call users from active directory use graph api.
This function is hit on each keyup of a text box. But i am getting following error
Code: TokenNotFound Message: User not found in token cache. Maybe the
server was restarted.
at the line
var user = await graphClient.Users.Request().GetAsync();
Entire function Below:
public async Task<string> GetUsersJSONAsync(string textValue)
{
// email = email ?? User.Identity.Name ?? User.FindFirst("preferred_username").Value;
var identifier = User.FindFirst(Startup.ObjectIdentifierType)?.Value;
var graphClient = _graphSdkHelper.GetAuthenticatedClient(identifier);
string usersJSON = await GraphService.GetAllUserJson(graphClient, HttpContext, textValue);
return usersJSON;
}
public static async Task<string> GetAllUserJson(GraphServiceClient graphClient, HttpContext httpContext, string textValue)
{
// if (email == null) return JsonConvert.SerializeObject(new { Message = "Email address cannot be null." }, Formatting.Indented);
try
{
// Load user profile.
var user = await graphClient.Users.Request().GetAsync();
return JsonConvert.SerializeObject(user.Where(u => !string.IsNullOrEmpty(u.Surname) && ( u.Surname.ToLower().StartsWith(textValue) || u.Surname.ToUpper().StartsWith(textValue.ToUpper()))), Formatting.Indented);
}
catch (ServiceException e)
{
switch (e.Error.Code)
{
case "Request_ResourceNotFound":
case "ResourceNotFound":
case "ErrorItemNotFound":
//case "itemNotFound":
// return JsonConvert.SerializeObject(new { Message = $"User '{email}' was not found." }, Formatting.Indented);
//case "ErrorInvalidUser":
// return JsonConvert.SerializeObject(new { Message = $"The requested user '{email}' is invalid." }, Formatting.Indented);
case "AuthenticationFailure":
return JsonConvert.SerializeObject(new { e.Error.Message }, Formatting.Indented);
case "TokenNotFound":
await httpContext.ChallengeAsync();
return JsonConvert.SerializeObject(new { e.Error.Message }, Formatting.Indented);
default:
return JsonConvert.SerializeObject(new { Message = "An unknown error has occured." }, Formatting.Indented);
}
}
}
// Gets an access token. First tries to get the access token from the token cache.
// Using password (secret) to authenticate. Production apps should use a certificate.
public async Task<string> GetUserAccessTokenAsync(string userId)
{
_userTokenCache = new SessionTokenCache(userId, _memoryCache).GetCacheInstance();
var cca = new ConfidentialClientApplication(
_appId,
_redirectUri,
_credential,
_userTokenCache,
null);
if (!cca.Users.Any()) throw new ServiceException(new Error
{
Code = "TokenNotFound",
Message = "User not found in token cache. Maybe the server was restarted."
});
try
{
var result = await cca.AcquireTokenSilentAsync(_scopes, cca.Users.First());
return result.AccessToken;
}
// Unable to retrieve the access token silently.
catch (Exception)
{
throw new ServiceException(new Error
{
Code = GraphErrorCode.AuthenticationFailure.ToString(),
Message = "Caller needs to authenticate. Unable to retrieve the access token silently."
});
}
}
Can you help whats going wrong?
I know this is 4 months old - is this still an issue for you?
As the previous respondent pointed out, the error you're seeing is being thrown in the catch block in your code meant to handle an empty users collection.
In case you're stuck on this, or anyone else comes here - if you used this sample (or using ConfidentialClientApplication in any respect) and are throwing this exception, it's because your _userTokenCache has no users*. Of course, it's not because your AD has no users, otherwise you wouldn't be able to authenticate. Most likely, it is because a stale cookie in your browser is being passed as the access token to your authProvider. You can use Fiddler (or just check your localhost browser cookies) to find it (should be called AspNetCore.Cookies, but you may want to clear all of them).
If you're storing the tokencache in session (as the example is), remember that each time you start and stop the application, your working memory will be thrown out so the token provided by your browser will no longer match the new one your application will retrieve upon starting up again (unless, again, you've cleared the browser cookies).
*cca.Users is no longer used or supported by MSAL - you have to use cca.GetAccountsAsync(). If you have a deployed application running with the deprecated IUser implementation, you'll have to change this. Otherwise, in development your compiler will complain and not let you build, so you'll already know about this.
Looking at the code, it seems some chunks of logic are missing. For example, you got the method
public async Task<string> GetUserAccessTokenAsync(string userId)
but I can't see where this is being called. Besides that, I don't see the code for fetching a token from Azure AD either. Lastly, the error message you mention
Code: TokenNotFound Message: User not found in token cache. Maybe the server was restarted.
Seems like the error you're throwing
if (!cca.Users.Any()) throw new ServiceException(new Error
{
Code = "TokenNotFound",
Message = "User not found in token cache. Maybe the server was restarted."
});
Since the code isn't complete, I will try and make an assumption on what might be going wrong.
Firstly, assuming you're using MSAL.Net, a step in the acquisition of a token is missing.
The general flow is (Using GetTokenByAuthorizationCodeAsync())
Client challenges the user
User gets redirected and logs in
Callback is called and the client receives a code from the login process
Pass the code to GetTokenByAuthorizationCodeAsync() to obtain an id_token and depending on the permissions an access token.
GetTokenByAuthorizationCodeAsync() will store the token in the cache
that has been provided to the ConfidentialClientApplication
Retrieve the token from the cache with AcquireTokenSilentAsync()
If we fail to retrieve a token from the cache with AcquireTokenSilentAsync(), we'll request a new one from via
AcquireTokenAsync()
Most of this flow seems to be in place in your code, but it could be you're missing the actual token acquisition. Since no token is retrieved, no user is added to the ConfidentialClientApplication, which means cca.Users.Any() returns false, resulting in an ServiceError
Assuming the whole flow is in place, and you're actually acquiring a token, my second assumption would be that the _memoryCache are different. The _memoryCache in which you saved your token differs from the one you use to acquire a token silently.
I would recommend reading the documentation on token acquisition to determine the type of retrieving is the right fit for your application.
EDIT
Actually, I assume your code is inspired by this example.
What's especially interesting is this part
public GraphServiceClient GetAuthenticatedClient(string userId)
{
_graphClient = new GraphServiceClient(new DelegateAuthenticationProvider(
async requestMessage =>
{
// Passing tenant ID to the sample auth provider to use as a cache key
var accessToken = await _authProvider.GetUserAccessTokenAsync(userId);
...
}
return _graphClient;
}
What seems to be happening is that calling var user = await graphClient.Users.Request().GetAsync(); invokes the delegate that is provided to the GraphServiceClient. This in turn calls _authProvider.GetUserAccessTokenAsync(userId); which brings us to the public async Task<string> GetUserAccessTokenAsync(string userId) method. Our error most likely originates here, due to no Users being present in the ConfidentialClientApplication.Users collection
Hope this helps!
My code snippet below is supposed to return the list of beacons. When having Google API Console generate an API Key I have whitelisted my public IP address and associated with the api key. When the code calls ExecuteAsync() method, I keep receiving an exception with error code 403 (forbidden). What may have I done wrong and how to mitigate the issue?
public async void TestApiKey()
{
var apikey = "739479874ABCDEFGH123456"; //it's not the real key I'm using
var beaconServices = new ProximitybeaconService(new Google.Apis.Services.BaseClientService.Initializer
{
ApplicationName = "My Project",
ApiKey = apikey
});
var result = await beaconServices.Beacons.List().ExecuteAsync();
// Display the results.
if (result.Beacons != null)
{
foreach (var api in result.Beacons)
{
Console.WriteLine(api.BeaconName + " - " + api.Status);
}
}
}
You are using a Public API key. Public API keys only work with public data.
beacons.list Authenticate using an OAuth access token from a
signed-in user with viewer, Is owner or Can edit permissions.
Requires the following OAuth scope:
•https://www.googleapis.com/auth/userlocation.beacon.registry
The method you are trying to access is accessing private user data. You need to be authentication before you can use it. Switch to Oauth2 authentication. Setting it to public probably wont work because you cant to my knowledge supply a scope to a public api key.
I am using TweetSharp to send tweets to users (currently testing it) however it keeps coming back with Bad Authentication Data
{"errors":[{"code":215,"message":"Bad Authentication data."}]}
I have checked my app settings and it has full read and write access. I have also tried to regenerate my consumer keys but still not luck.
here is my code
public ActionResult AccessToken()
{
string oauth_consumer_key = "<consumer key>";
string oauth_consumer_secret = "<consumer secret>";
var service = new TwitterService(oauth_consumer_key, oauth_consumer_secret);
// Now we need the Token and TokenSecret
OAuthRequestToken requestToken = service.GetRequestToken("http://localhost:37808/");
string authURL = service.GetAuthorizationUri(requestToken).ToString();
Process.Start(authURL);
SendTweetOptions options = new SendTweetOptions();
options.Status = "Hello there Twitter";
service.SendTweet(options);
var re = service.Response.Response;
return View();
}
Am I doing anything wrong?
Finally solved the issue and it works well. Based upon comments from Yort.
public ActionResult AccessToken()
{
// Step 1 - Retrieve an OAuth Request Token
TwitterService service = new TwitterService(ConfigurationManager.AppSettings["TwitterConsumerKey"], ConfigurationManager.AppSettings["TwitterConsumerSecret"]);
// This is the registered callback URL
OAuthRequestToken requestToken = service.GetRequestToken("http://localhost:37808/Twitter/OToken");
// Step 2 - Redirect to the OAuth Authorization URL
Uri uri = service.GetAuthorizationUri(requestToken);
return new RedirectResult(uri.ToString(), false /*permanent*/);
//return View();
}
public ActionResult OToken()
{
return View();
}
public ActionResult UserInfo(string oauth_token, string oauth_verifier)
{
var requestToken = new OAuthRequestToken { Token = oauth_token };
// Step 3 - Exchange the Request Token for an Access Token
TwitterService service = new TwitterService(ConfigurationManager.AppSettings["TwitterConsumerKey"],
ConfigurationManager.AppSettings["TwitterConsumerSecret"]);
OAuthAccessToken accessToken = service.GetAccessToken(requestToken, oauth_verifier);
// Step 4 - User authenticates using the Access Token
service.AuthenticateWith(accessToken.Token, accessToken.TokenSecret);
TwitterUser user = service.VerifyCredentials(new VerifyCredentialsOptions());
ViewBag.Message = string.Format("{0}", user.ScreenName);
// Step 5 - Send Tweet to User TimeLine
SendTweetOptions options = new SendTweetOptions();
string URL = "file:\\C:\\Users\\<User>\\Desktop\\test.jpg";
string path = new Uri(URL).LocalPath;
// Sending with Media
using (var stream = new FileStream(path, FileMode.Open))
{
service.SendTweetWithMedia(new SendTweetWithMediaOptions
{
Status = "<status>",
Images = new Dictionary<string, Stream> { { path, stream } }
});
}
var responseText = service.Response.StatusCode;
if (responseText.ToString() == "OK")
{
ViewBag.Message = "Tweet Successful";
}
else
{
ViewBag.Message = "Tweet Unsuccessful";
}
return View();
}
}
I don't believe you can send Tweets as just a consumer, the Tweets have to be "owned" by a user account. You need to register a Twitter account, then do the full oauth authentication process to get an access token (in addition to the consumer token), then reauthorise the TweetSharp service using both tokens.
Your code above nearly gets there (I think). After the Process.start call there needs to be logic to use the verifier returned in the browser (a number displayed after the user logs in) to complete the auth process and act as that user. At the moment, your code gets half way through that process but does not complete it, so when you try to tweet your TweetSharp service is only authed as the app and not the user.
The originalTweetSharp readme.md does include the missing bits of code. Step 3 needs the actual verifier returned in the browser after login:
// Step 3 - Exchange the Request Token for an Access Token
string verifier = "123456"; // <-- This is input into your application by your user
OAuthAccessToken access = service.GetAccessToken(requestToken, verifier);
// Step 4 - User authenticates using the Access Token
service.AuthenticateWith(access.Token, access.TokenSecret);
//Now your tweet call should work here.
It also looks like you're doing this in a web app on the server? In which case you're using entirely the wrong oauth flow (I believe). This one is designed for desktop apps, hence the call that starts a new browser process for the user to login with. I'm not entirely sure how the web flow works as I've never used it, but I believe you need to redirect the user to the authorisation url you receive, and the callback registered with Twitter should point back to your site. I think there is some kind of state parameter that can be passed back through the oauth flow so you can implement your own logic to pickup where you left off based on a session id or similar.
I worked on this subject before. You have to developer account before the send tweet because you need tokens and keys. It's my windows service project.
I wrote my tokens and key codes in App.config
<appSettings>
<add key="twitterAccessToken" value="*****"/>
<add key="twitterAccessTokenSecret" value="*****"/>
<add key="twitterConsumerKey" value="*****"/>
<add key="twitterConsumerSecret" value="*****"/>
public static void SendTweet()
{
try
{
GetPixelImageFile();
string key = ConfigurationSettings.AppSettings.Get("twitterConsumerKey");
string secret = ConfigurationSettings.AppSettings.Get("twitterConsumerSecret");
string token = ConfigurationSettings.AppSettings.Get("twitterAccessToken");
string tokenSecret = ConfigurationSettings.AppSettings.Get("twitterAccessTokenSecret");
string message = "Color, Colorful, Pixel, Art, PixelColouring, Follow";
var service = new TweetSharp.TwitterService(key, secret);
service.AuthenticateWith(token, tokenSecret);
using (var stream = new FileStream(#"C:\Images\Pixel.png", FileMode.Open))
{
var result = service.SendTweetWithMedia(new SendTweetWithMediaOptions
{
Status = message,
Images = new Dictionary<string, Stream> { { "john", stream } }
});
SendMail("SendTweet", (result == null ? "" : result.Text));
}
}
catch (Exception ex)
{
SendMail("SendTweet", ex.Message);
}
}
I'm trying to use the Google+ API to access info for the authenticated user. I've copied some code from one of the samples, which works fine (below), however I'm having trouble making it work in a way I can reuse the token across app-launches.
I tried capturing the "RefreshToken" property and using provider.RefreshToken() (amongst other things) and always get a 400 Bad Request response.
Does anyone know how to make this work, or know where I can find some samples? The Google Code site doesn't seem to cover this :-(
class Program
{
private const string Scope = "https://www.googleapis.com/auth/plus.me";
static void Main(string[] args)
{
var provider = new NativeApplicationClient(GoogleAuthenticationServer.Description);
provider.ClientIdentifier = "BLAH";
provider.ClientSecret = "BLAH";
var auth = new OAuth2Authenticator<NativeApplicationClient>(provider, GetAuthentication);
var plus = new PlusService(auth);
plus.Key = "BLAH";
var me = plus.People.Get("me").Fetch();
Console.WriteLine(me.DisplayName);
}
private static IAuthorizationState GetAuthentication(NativeApplicationClient arg)
{
// Get the auth URL:
IAuthorizationState state = new AuthorizationState(new[] { Scope });
state.Callback = new Uri(NativeApplicationClient.OutOfBandCallbackUrl);
Uri authUri = arg.RequestUserAuthorization(state);
// Request authorization from the user (by opening a browser window):
Process.Start(authUri.ToString());
Console.Write(" Authorization Code: ");
string authCode = Console.ReadLine();
Console.WriteLine();
// Retrieve the access token by using the authorization code:
return arg.ProcessUserAuthorization(authCode, state);
}
}
Here is an example. Make sure you add a string setting called RefreshToken and reference System.Security or find another way to safely store the refresh token.
private static byte[] aditionalEntropy = { 1, 2, 3, 4, 5 };
private static IAuthorizationState GetAuthorization(NativeApplicationClient arg)
{
// Get the auth URL:
IAuthorizationState state = new AuthorizationState(new[] { PlusService.Scopes.PlusMe.GetStringValue() });
state.Callback = new Uri(NativeApplicationClient.OutOfBandCallbackUrl);
string refreshToken = LoadRefreshToken();
if (!String.IsNullOrWhiteSpace(refreshToken))
{
state.RefreshToken = refreshToken;
if (arg.RefreshToken(state))
return state;
}
Uri authUri = arg.RequestUserAuthorization(state);
// Request authorization from the user (by opening a browser window):
Process.Start(authUri.ToString());
Console.Write(" Authorization Code: ");
string authCode = Console.ReadLine();
Console.WriteLine();
// Retrieve the access token by using the authorization code:
var result = arg.ProcessUserAuthorization(authCode, state);
StoreRefreshToken(state);
return result;
}
private static string LoadRefreshToken()
{
return Encoding.Unicode.GetString(ProtectedData.Unprotect(Convert.FromBase64String(Properties.Settings.Default.RefreshToken), aditionalEntropy, DataProtectionScope.CurrentUser));
}
private static void StoreRefreshToken(IAuthorizationState state)
{
Properties.Settings.Default.RefreshToken = Convert.ToBase64String(ProtectedData.Protect(Encoding.Unicode.GetBytes(state.RefreshToken), aditionalEntropy, DataProtectionScope.CurrentUser));
Properties.Settings.Default.Save();
}
The general idea is as follows:
You redirect the user to Google's Authorization Endpoint.
You obtain a short-lived Authorization Code.
You immediately exchange the Authorization Code for a long-lived Access Token using Google's Token Endpoint. The Access Token comes with an expiry date and a Refresh Token.
You make requests to Google's API using the Access Token.
You can reuse the Access Token for as many requests as you like until it expires. Then you can use the Refresh Token to request a new Access Token (which comes with a new expiry date and a new Refresh Token).
See also:
The OAuth 2.0 Authorization Protocol
Google's OAuth 2.0 documentation
I also had problems with getting "offline" authentication to work (i.e. acquiring authentication with a refresh token), and got HTTP-response 400 Bad request with a code similar to the OP's code. However, I got it to work with the line client.ClientCredentialApplicator = ClientCredentialApplicator.PostParameter(this.clientSecret); in the Authenticate-method. This is essential to get a working code -- I think this line forces the clientSecret to be sent as a POST-parameter to the server (instead of as a HTTP Basic Auth-parameter).
This solution assumes that you've already got a client ID, a client secret and a refresh-token. Note that you don't need to enter an access-token in the code. (A short-lived access-code is acquired "under the hood" from the Google server when sending the long-lived refresh-token with the line client.RefreshAuthorization(state);. This access-token is stored as part of the auth-variable, from where it is used to authorize the API-calls "under the hood".)
A code example that works for me with Google API v3 for accessing my Google Calendar:
class SomeClass
{
private string clientID = "XXXXXXXXX.apps.googleusercontent.com";
private string clientSecret = "MY_CLIENT_SECRET";
private string refreshToken = "MY_REFRESH_TOKEN";
private string primaryCal = "MY_GMAIL_ADDRESS";
private void button2_Click_1(object sender, EventArgs e)
{
try
{
NativeApplicationClient client = new NativeApplicationClient(GoogleAuthenticationServer.Description, this.clientID, this.clientSecret);
OAuth2Authenticator<NativeApplicationClient> auth = new OAuth2Authenticator<NativeApplicationClient>(client, Authenticate);
// Authenticated and ready for API calls...
// EITHER Calendar API calls (tested):
CalendarService cal = new CalendarService(auth);
EventsResource.ListRequest listrequest = cal.Events.List(this.primaryCal);
Google.Apis.Calendar.v3.Data.Events events = listrequest.Fetch();
// iterate the events and show them here.
// OR Plus API calls (not tested) - copied from OP's code:
var plus = new PlusService(auth);
plus.Key = "BLAH"; // don't know what this line does.
var me = plus.People.Get("me").Fetch();
Console.WriteLine(me.DisplayName);
// OR some other API calls...
}
catch (Exception ex)
{
Console.WriteLine("Error while communicating with Google servers. Try again(?). The error was:\r\n" + ex.Message + "\r\n\r\nInner exception:\r\n" + ex.InnerException.Message);
}
}
private IAuthorizationState Authenticate(NativeApplicationClient client)
{
IAuthorizationState state = new AuthorizationState(new string[] { }) { RefreshToken = this.refreshToken };
// IMPORTANT - does not work without:
client.ClientCredentialApplicator = ClientCredentialApplicator.PostParameter(this.clientSecret);
client.RefreshAuthorization(state);
return state;
}
}
The OAuth 2.0 spec is not yet finished, and there is a smattering of spec implementations out there across the various clients and services that cause these errors to appear. Mostly likely you're doing everything right, but the DotNetOpenAuth version you're using implements a different draft of OAuth 2.0 than Google is currently implementing. Neither part is "right", since the spec isn't yet finalized, but it makes compatibility something of a nightmare.
You can check that the DotNetOpenAuth version you're using is the latest (in case that helps, which it might), but ultimately you may need to either sit tight until the specs are finalized and everyone implements them correctly, or read the Google docs yourself (which presumably describe their version of OAuth 2.0) and implement one that specifically targets their draft version.
I would recommend looking at the "SampleHelper" project in the Samples solution of the Google .NET Client API:
Samples/SampleHelper/AuthorizationMgr.cs
This file shows both how to use Windows Protected Data to store a Refresh token, and it also shows how to use a Local Loopback Server and different techniques to capture the Access code instead of having the user enter it manually.
One of the samples in the library which use this method of authorization can be found below:
Samples/Tasks.CreateTasks/Program.cs