I have an installed c# app with code working that gets the authorization code and exchanges it for an access token. I am storing off the refresh token. I know at some point I need to use it to get a new access token. Let's assume that I am periodically calling the following method to monitor the files that have been shared with my Drive account.
/// <summary>
/// Retrieve a list of File resources.
/// </summary>
/// <param name="service">Drive API service instance.</param>
/// <returns>List of File resources.</returns>
public static List<File> retrieveAllFiles(DriveService service) {
List<File> result = new List<File>();
FilesResource.ListRequest request = service.Files.List();
request.Q = "sharedWithMe and trashed=false";
do {
try {
FileList files = request.Fetch();
result.AddRange(files.Items);
request.PageToken = files.NextPageToken;
} catch (Exception e) {
Console.WriteLine("An error occurred: " + e.Message);
request.PageToken = null;
}
} while (!String.IsNullOrEmpty(request.PageToken));
return result;
}
}
I assume that at some point the call to service.Files.List() is going to fail. How do I know it has failed due to an expired access token and what is the code to use the refresh token? I already have some code (below) that I gleaned from here to use the refresh token. Will this method get called when the access token expires?
private static IAuthorizationState GetAuthorization(NativeApplicationClient arg)
{
// If we already have a RefreshToken, use that
if (!string.IsNullOrEmpty(RefreshToken))
{
state.RefreshToken = RefreshToken;
if (arg.RefreshToken(state)) {
mTextBox.Text = "RF: " + RefreshToken;
return state;
}
}
// authCode is a TextBox on the form
var result = arg.ProcessUserAuthorization(mTextBox.Text, state);
RefreshToken = state.RefreshToken;
return result;
}
An access token will expire after 1 hour - after that time you will begin to receive "401 Invalid Credentials" errors when you make calls against a Google API.
I'm not familiar with the .NET Google API Client library - the Java and Python libraries will automatically make a request for a new access token when this occurs, depending on how you are creating the DriveService object. I would expect the .NET library to have similar semantics.
If someone still have Problems with refreshing the AccessToken, maybe this can help you finding a solution:
Google.GData.Client.RequestSettings settings = new RequestSettings("<AppName>");
Google.GData.Client.OAuth2Parameters parameters = new OAuth2Parameters()
{
ClientId = "<YourClientId>",
ClientSecret = "<YourClientSecret>",
AccessToken = "<OldAccessToken>", //really necessary?
RedirectUri = "urn:ietf:wg:oauth:2.0:oob",
RefreshToken = "<YourRefreshToken>",
AccessType = "offline",
TokenType = "refresh",
Scope = "https://www.google.com/m8/feeds/" //Change to needed scopes, I used this for ContactAPI
};
try
{
Google.GData.Client.OAuthUtil.RefreshAccessToken(parameters);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
When to use the refresh token:
From what I understand you use the refresh token when you do not wish to authenticate your app each time it boots. This is extremely useful for debugging during application development (as manual authentication can get annoying after a while).
How to use the refresh token:
In the most basic sense:
public static GOAuth2RequestFactory RefreshAuthenticate(){
OAuth2Parameters parameters = new OAuth2Parameters(){
RefreshToken = "<YourRefreshToken>",
AccessToken = "<AnyOfYourPreviousAccessTokens>",
ClientId = "<YourClientID>",
ClientSecret = "<YourClientSecret>",
Scope = "https://spreadsheets.google.com/feeds https://docs.google.com/feeds",
AccessType = "offline",
TokenType = "refresh"
};
string authUrl = OAuthUtil.CreateOAuth2AuthorizationUrl(parameters);
return new GOAuth2RequestFactory(null, "<YourApplicationName>", parameters);
}
You would use this method in other code with a service, perhaps like this
GOAuth2RequestFactory requestFactory = RefreshAuthenticate();
SpreadsheetsService service = new SpreadsheetsService("<YourApplicationName>");
service.RequestFactory = requestFactory;
Hope this helps!
Spent the last two days figuring out how to use and renew the access token using the refresh token. My answer is posted in another thread here:
How Google API V 3.0 .Net library and Google OAuth2 Handling refresh token
Related
I'm currently trying to find a way to access a web api thru web api using on-behalf-of flow of MSAL.NET
So far, I use the token in the Authorization header to generate the token to be used for the third party group's API
but I'm hoping of finding alternative to this since getting the access token from the header is I think not a healthy approach.
I hope somebody could help me of getting a fresh token that I can use to generate another token with on-behalf-of flow
this is the code that I have:
var cba = ConfidentialClientApplicationBuilder.Create("<our azure client ID>")
.WithAuthority(new Uri("https://login.microsoftonline.com/<Tenant ID>/oauth2/v2.0"))
.WithClientSecret("<Our azure client secret>")
.WithTenantId("<Tenant ID>")
.Build();
AuthenticationResult result = null;
try
{
var authToken = HttpContext.Current.Request.Headers["Authorization"];
var user = new UserAssertion(authToken.Split(' ')[1]);
var scopes = new string[] { "<third party resource client id>/1234.ThirdParty.Api" };
result = await cba.AcquireTokenOnBehalfOf(scopes,user).ExecuteAsync().ConfigureAwait(false);
return new Jwt
{
AccessToken = result.AccessToken,
TokenType = "Bearer",
RefreshToken = null,
ExpiresIn = 3600
};
}
catch (MsalUiRequiredException ex)
{
throw ex;
}
catch (MsalClientException ex)
{
throw ex;
}
I have a customer that is trying to access their calendars from our web application. Everything works for all of our other customers, so I am not sure what is different here except this customer is in Australia and using a non gmail.com email address.
The customer is able to authorize our application and we do get a oauth token for the user. We request calendar access and the customer granted it. When we request a list of all of the calendars, we get the invalid grant message.
Below is the code that we use to access their calendars. The method being called is GetAllWritableCalendars.
public class GoogleCalendarAdapter : ICalendarAdapter {
#region attributes
private readonly ISiteAuthTokenQueryRepository _tokenRepo;
private readonly GoogleCalendarSettings _settings;
private const string APPNAME = "SomeAppName";
private const string ACL_OWNER = "owner";
private const string ACL_WRITER = "writer";
#endregion
#region ctor
public GoogleCalendarAdapter(ISiteAuthTokenQueryRepository tokenRepo,
GoogleCalendarSettings settings) {
_tokenRepo = tokenRepo;
_settings = settings;
}
#endregion
#region methods
private GoogleAuthorizationCodeFlow BuildAuthorizationCodeFlow() {
return new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer() {
ClientSecrets = BuildClientSecrets(),
Scopes = BuildScopeList()
});
}
private CalendarService BuildCalendarService(SiteAuthToken token) {
return new CalendarService(new BaseClientService.Initializer() {
ApplicationName = APPNAME,
HttpClientInitializer = BuildUserCredential(token)
});
}
private ClientSecrets BuildClientSecrets() {
return new ClientSecrets() {
ClientId = _settings.ClientId,
ClientSecret = _settings.ClientSecret
};
}
private string[] BuildScopeList() {
return new [] { CalendarService.Scope.Calendar };
}
private UserCredential BuildUserCredential(SiteAuthToken token) {
TokenResponse responseToken = new TokenResponse() {
AccessToken = token.AccessToken,
RefreshToken = token.RefreshToken
};
return new UserCredential(BuildAuthorizationCodeFlow(), APPNAME, responseToken);
}
public async Task<List<Cal>> GetAllWritableCalendars(Guid siteGuid) {
SiteAuthToken token = await GetToken(siteGuid);
CalendarService svc = BuildCalendarService(token);
IList<CalendarListEntry> calendars = svc.CalendarList
.List()
.Execute()
.Items;
return calendars.Where(c => c.AccessRole.Equals(ACL_OWNER, StringComparison.CurrentCultureIgnoreCase) ||
c.AccessRole.Equals(ACL_WRITER, StringComparison.CurrentCultureIgnoreCase))
.Select(c => new Cal() {
Id = c.Id,
Name = c.Summary
})
.OrderBy(o => o.Name)
.ToList();
}
private async Task<SiteAuthToken> GetToken(Guid siteGuid) {
SiteAuthToken retVal = await _tokenRepo.GetSiteAuthToken(siteGuid);
if (retVal == null) {
throw new ApplicationException($"Could not find a SiteAuthToken for specified site (SiteGuid: {siteGuid})");
}
return retVal;
}
#endregion
The credentials are the authorization from Google to Your Application to use the scopes you have set-up, this is okay to have it in a database if you update it every time you add new scopes to your app.
The Access Token is the authorization from the user to your application to get it's Google Data (calendar in this case). It has a limited lifetime so this is not okay to save in a database.
The Refresh Token is the token that allows your application to get more tokens for a client. It has a limited lifetime as well.
For more info see: Using OAuth 2.0 to Access Google APIs
Every time you change your scopes or add more scopes you have to re-generate the credentials. You have 50 refresh tokens per user account per client, see Token expiration. So having the tokens in a database makes no sense since they are going to get deprecated at some point, if you have 51 clients the 1st token will get deprecated.
Check:
How do you have it set-up on your database
If you renew properly the tokens
If you are using the correct tokens for the users
You can delete all the tokens (NOT the CREDENTIALS) and your current users will only have to go through the consent screen and allow it again, they will not lose the connection.
I asked the question later in a different way. Maybe it was a little more relevant. Perhaps there was a little more information available. What ever the case may be, I discovered how to test things properly.
Look at this question
I "just" want to integrate google calendar api to my little web project. The user should be able to add calendar entries to his calendar - via a c# core (2.2) mvc project. The problem is I can't find any complete example how to do this and tried a lot without any solution.
The main problem - how can I get the permission? And how can I set the redirect url?
Why google does not provide a complete example für c# core?
I build a simple console projekt (based on an example) - that works if I set the permission manually. But I must ask my user to give the permission.
Btw - I created and saved the ClientId, ClientSecret and so on at/from https://console.developers.google.com/.
Thanks Ralf
public IActionResult GoogleCalendar(string id)
{
string refreshToken = string.Empty;
string credentialError;
var credential = GetUserCredential(out credentialError);
if (credential != null && string.IsNullOrWhiteSpace(credentialError))
{
//Save RefreshToken into Database
refreshToken = credential.Token.RefreshToken;
}
string addEventError;
string calendarEventId = string.Empty;
calendarEventId = AddCalenderEvents(refreshToken, "mytestuser#googlemail.com", "Test-Event " + DateTime.Now, DateTime.Now.AddMinutes(5), DateTime.Now.AddHours(2), out addEventError);
return View();
}
public static UserCredential GetUserCredential(out string error)
{
UserCredential credential = null;
error = string.Empty;
try
{
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = ClientId,
ClientSecret = ClientSecret
},
Scopes,
"mytestuser#googlemail.com",
CancellationToken.None,
new FileDataStore("Google Oauth2 Client App")).Result;
}
catch (Exception ex)
{
credential = null;
error = "Failed to UserCredential Initialization: " + ex.ToString();
}
return credential;
}
I get from google
That’s an error.
Error: invalid_client
The OAuth client was not found.
On a (changing) local port an url I have never set.
access_type=offline
response_type=code
client_id=xxxxxx-yyyyyyyyyyyyy.apps.googleusercontent.com
redirect_uri=http://127.0.0.1:56253/authorize/
scope=https://www.googleapis.com/auth/calendar
The provided samples from google are quite confusing, the provided console demo application only works for local environments because it tries to launch the browser and use it to authenticatie.
To authenticatie users on a web app, check the following google guide: https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web-applications-aspnet-mvc
So I believe my APIservice should be fine since I can return results through Swagger? I am calling from a WPF project. I launch the program and it asks me to login, then it continues and will tell me I don't have permission.
I'm super green to WebAPI2 and think I may just be constructing my call incorrectly. It does seem that I get a token back correctly from my site, the only issue is when I try to actually call on the API for data.
Here is my code:
public static string clientId = "{#Calling App Id}";
public static string commonAuthority = "https://login.windows.net/{#my Azure AD tenant}";
public static Uri returnUri = new Uri("http://MyDirectorySearcherApp");
const string ResourceUri = "https://{#Api App Service}.azurewebsites.net";
public static async Task<List<User>> LoadBands(IPlatformParameters parent)
{
AuthenticationResult authResult = null;
List<User> results = new List<User>();
try {
//get token or use refresh
AuthenticationContext authContext = new AuthenticationContext(commonAuthority);
if (authContext.TokenCache.ReadItems().Count() > 0)
authContext = new AuthenticationContext(authContext.TokenCache.ReadItems().First().Authority);
authResult = await authContext.AcquireTokenAsync(ResourceUri, clientId, returnUri, parent);
} catch (Exception ee) {
throw ex;
}
using (var httpClient = new HttpClient()) {
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, $"{ResourceUri}/api/Band/")) {
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
using (var response = await httpClient.SendAsync(request)) {
string responseData = await response.Content.ReadAsStringAsync();
//responseData always equals "You do not have permission to view this directory or page"
return results;
}
}
}
Edit: Maybe helpful to note I'm using a DataAPI that is called by a Rest API, the rest API is secured by Azure AD.
Edit: I'm calling from a Portable Class Library.
Edit: Well, I'm getting authenticated but it does not appear to make any difference. If I completely remove the Auth header I get the same result
It seems that the token is incorrect for the web API which protected by Azure AD. Please check the aud claim in the token which should match the Audience you config in the web API project. You can check the aud claim by parse the token from this site.
And if you still have the problem please share the code how you protect the web API.
Update
If you were using the Express mode like below, you need to acquire the access_token using the app which you associate with the web API.
If you were using the Advanced mode, we should also use the that app to acquire the token and the ResourceUri should matched the value you config in ALLOWED TOKEN AUDIENCES like below:
According to the docuemtntation for Moments.insert with the Google+ API autentication with the following scope is required
https://www.googleapis.com/auth/plus.login
I am authenticating with all of the possible PlusService scopes but i am still getting the following error
Google.Apis.Requests.RequestError Unauthorized [401] Errors [
Message[Unauthorized] Location[ - ] Reason[unauthorized]
Domain[global]
//Scopes for use with Google+ API
// activating Google+ API in console
// Documentation: https://developers.google.com/+/api/oauth
string[] scopes = new string[] {
PlusService.Scope.PlusLogin,
PlusService.Scope.UserinfoEmail,
PlusService.Scope.UserinfoProfile
};
string _client_id = "2046123799103-d0vpdthl4ms0soutcrpe036ckqn7rfpn.apps.googleusercontent.com";
string _client_secret = "NDmluNfTgUk6wgmy7cFo64RV";
PlusService service = null;
UserCredential credential = null;
try {
// here is where we Request the user to give us access, or use the Refresh Token that was previously stored in %AppData%
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets {
ClientId = _client_id, ClientSecret = _client_secret
},
scopes,
Environment.UserName,
CancellationToken.None,
new FileDataStore("Daimto.GooglePlus.Auth.Store")).Result;
} catch (Exception ex) {
//If the user hits cancel you wont get access.
if (ex.InnerException.Message.IndexOf("access_denied") != -1) {
Console.WriteLine("User declined access");
Console.ReadLine();
return;
} else {
Console.WriteLine("Unknown Authentication Error:" + ex.Message);
Console.ReadLine();
return;
}
}
// Now we create a Google service. All of our requests will be run though this.
service = new PlusService(new BaseClientService.Initializer() {
HttpClientInitializer = credential,
ApplicationName = "Google Plus Sample",
});
Moment body = new Moment();
body.Type = "http://schema.org/AddAction";
ItemScope itemScope = new ItemScope();
itemScope.Id = "target-id-1";
itemScope.Type = "http://schema.org/AddAction";
itemScope.Name = "The Google+ Platform";
itemScope.Description = "A page that describes just how awesome Google+ is!";
itemScope.Image = "https://developers.google.com/+/plugins/snippet/examples/thing.png";
body.Object = itemScope;
try {
var l = service.Moments.Insert(body, "me", MomentsResource.InsertRequest.CollectionEnum.Vault);
l.Execute();
} catch (Exception ex) {
int i = 1;
}
I have tested authentication and it is working i am able to list activities and other things. Its only inserting moments that is giving me this error. I have also tried doing this in PHP and am getting the same error. What am I missing?
Update: I found something in the documentation for moments.insert
When authenticating for moments.insert, you must include the
data-requestvisibleactions parameter to specify which types of App
Activities your application will write.
I have not figured out yet how to set this data-requestvisibleactions.
As you've noticed, you need to add the request_visible_actions parameter to the request. Most of the other OAuth libraries from Google have added a shortcut to do this, but it looks like the .NET library hasn't. For example, the PHP library has setRequestVisibleActions().
Internally, the convenience method GoogleWebAuthorizationBroker.AuthorizeAsync() calls AuthorizationCodeFlow.CreateAuthorizationCodeRequest() to generate the URL used in the call. You can subclass AuthorizationCodeFlow and AuthorizationCodeRequestUrl (which it returns) to add the parameter in question and then go through the flow more directly.
You can see an example of how to do this at https://github.com/gguuss/google-dotnet-demo/blob/master/AuthWithAppActivities/Program.cs
You can also use a Google+ Sign-In button to do the initial auth flow and pass the one-time code to the server, which can then proceed to turn this into a valid token. Since the Sign-In button has the data-requestvisibleactions attribute, this will take care of that part of the auth for you.