Google drive service accounts refresh token - c#

My session expires every hour and I can't find documentation on how to refresh token when I'm using service accounts authentication method. For installed applications I'm able to get RefreshToken from state object
AuthorizationState state = new AuthorizationState(new[]
{
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/drive.metadata.readonly",
"https://www.googleapis.com/auth/drive.readonly"
})
{
Callback = new Uri(NativeApplicationClient.OutOfBandCallbackUrl)
}
state = client.ProcessUserAuthorization(GetAuthorizationCode(), state);
Console.WriteLine(state.RefreshToken);
but how to do that for service accounts?
X509Certificate2 certificate = new X509Certificate2(SERVICE_ACCOUNT_PKCS12_FILE_PATH, "mysecret", X509KeyStorageFlags.Exportable);
var provider = new AssertionFlowClient(GoogleAuthenticationServer.Description, certificate)
{
ServiceAccountId = SERVICE_ACCOUNT_EMAIL,
Scope = DriveService.Scopes.Drive.GetStringValue(),
ServiceAccountUser = "myemail#mydomain.com",
};
var auth = new OAuth2Authenticator<AssertionFlowClient>(provider, AssertionFlowClient.GetState);
DriveService service = DriveService(auth);
from Google SDK source codes I found that AssertionFlowClient.GetState function performs the following
IAuthorizationState state = new AuthorizationState(provider.Scope.Split(' '));
if (provider.RefreshToken(state, null)) {
return state;
} else {
return null;
}
So looks like it does token refresh. I added this function call to my token refresh timer, but it doesn't help. I still continue to get Invalid credentials exception after one hour.

Service accounts do not use refresh tokens. You simply need to request another access token using the same procedure you used the first time. See Google's documentation on what to do when access tokens expire.

Related

Login using AWS cognito with Google auth

I am applying Google Login with CognitoCachingCredentialsProvider and AmazonCognitoIdentityClient but user entry added in the identity pool, not in the user pool. I need to get JWT token for auth. I did research on lots of document but no one gives a proper solution. Please help me with how to achieve social login with AWS Cognito.
AmazonCognitoIdentityClient cognitoClient = new AmazonCognitoIdentityClient(AWSConnection.AWS_AccessKey,AWSConnection.AWS_SecretKey,AWSConnection.AWSRegion);
Dictionary<string, string> keyValuePairs = new Dictionary<string, string>() { };
keyValuePairs.Add("accounts.google.com", idToken);
GetIdRequest idRequest = new GetIdRequest
{
//idRequest.AccountId = _openIdConnect.ClientId;
IdentityPoolId = "us-west-2:xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx",
// set the Dictionary of logins if you are authenticating users
// through an identity provider
Logins = keyValuePairs
};
// The identity id is in the IdentityId parameter of the response object
GetIdResponse idResp = await cognitoClient.GetIdAsync(idRequest);
// TODO: At this point, you should save this identifier so you won’t
// have to make this call the next time a user connects
// create a new request object
GetOpenIdTokenRequest openIdReq = new GetOpenIdTokenRequest
{
IdentityId = idResp.IdentityId,
// set the Dictionary of logins if you are authenticating users
// through an identity provider
Logins = keyValuePairs
};
GetOpenIdTokenResponse openIdResp = await cognitoClient.GetOpenIdTokenAsync(openIdReq);
I am getting IdToken but I need JWT token for the same.
Also, I used this code.
// Initialize the Amazon Cognito credentials provider
CognitoAWSCredentials credentials = new CognitoAWSCredentials(
"us-west-2:xxxxxxx-xxxx-xxx-xxxx-xxxx", // Identity pool ID
RegionEndpoint.USWest2 // Region
);
credentials.AddLogin("accounts.google.com", idToken);
var sd = credentials.GetCredentials();
I am getting idToken token from google that is id_token.

B2B access in multi tenant application

I have succesfully setup a multi tenant application.
For now, I am able to authenticate the user and use tokens to access other resources. (Microsoft Graph & Microsoft AD Graph)
Now I want to get B2B working.
Current flow:
- User signs in
- AuthorizationCodeReceived gets the acquires the token (via $commonAuthority endpoint)
- When requesting a token for the Ad Graph, I am using the $tenantAuthority
This works perfectly when $tenantAuthority is the same tenant authority as the one where the account was created in.
However, if I login with another user (from another tenant, given trust to the actual tenant) and use $tenantAuthority = trusted authority, then I always the following error:
Failed the refresh token:
AADSTS65001: The user or administrator has not consented to use the application with ID
If I change $tenantAuthority to the 'source' tenant authority where the user was created in, everything works fine.
Any help would be greatly appreciated.
Update: Code sample
App has two tenants (tenantA en tenantB) and I will use a user from tenantB with tenantA given a trust to this user.
AuthorizationCodeReceived = async context =>
{
TenantContext.TenantId = "someguid";
var tenantId =
TenantContext.TenantId;
// get token cache via func, because the userid is only known at runtime
var getTokenCache = container.Resolve<Func<string, TokenCache>>();
var userId = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.ObjectIdentifier).Value;
var tokenCache = getTokenCache(userId);
var authenticationContext = new AuthenticationContext($"{configuration.Authority}",
tokenCache);
await authenticationContext.AcquireTokenByAuthorizationCodeAsync(
context.Code,
new Uri(context.Request.Uri.GetLeftPart(UriPartial.Authority)),
new ClientCredential(configuration.ClientId, configuration.ClientSecret),
configuration.GraphResourceId);
}
This code works perfectly. Login in with a user from both tenants works perfectly.
But when I need the Graph Service Client or ActiveDirectoryClient, I need to obtain access tokens to been able to address an api for a certain tenant. I retrieve the access tokens like this:
public IGraphServiceClient CreateGraphServiceClient()
{
var client = new GraphServiceClient(
new DelegateAuthenticationProvider(
async requestMessage =>
{
Logger.Debug("Retrieving authentication token to use in Microsoft Graph.");
string token;
var currentUserHomeTenantId = TenantContext.TenantId;
var currentUserObjectId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.ObjectIdentifier).Value;
var authenticationContext =
new AuthenticationContext($"{_configuration.TenantAuthorityPrefix}{currentUserHomeTenantId}",
_tokenCacheFactoryMethod(currentUserObjectId));
var clientCredential = new ClientCredential(_configuration.ClientId, _configuration.ClientSecret);
try
{
token = await GetTokenSilently(authenticationContext, _configuration.GraphResourceId, currentUserObjectId);
}
catch (AdalSilentTokenAcquisitionException e)
{
Logger.Error("Failed to retrieve authentication token silently, trying to refresh the token.", e);
var result = await authenticationContext.AcquireTokenAsync(_configuration.GraphResourceId, clientCredential);
token = result.AccessToken;
}
requestMessage.Headers.Authorization = new AuthenticationHeaderValue(AuthenticationHeaderKeys.Bearer, token);
}));
return client;
}
public IActiveDirectoryClient CreateAdClient()
{
var currentUserHomeTenantId = TenantContext.TenantId;
var currentUserObjectId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.ObjectIdentifier).Value;
var graphServiceUrl = $"{_configuration.AdGraphResourceId}/{currentUserHomeTenantId}";
var tokenCache = _tokenCacheFactoryMethod(currentUserObjectId);
var client = new ActiveDirectoryClient(new Uri(graphServiceUrl),
() => GetTokenSilently(
new AuthenticationContext(
$"{_configuration.TenantAuthorityPrefix}{ClaimsPrincipal.Current.FindFirst(ClaimTypes.TenantId).Value}", tokenCache
),
_configuration.AdGraphResourceId, currentUserObjectId
));
return client;
}
When I do a request with one of the two client SDK's, I got the following error:
Failed the refresh token: AADSTS65001: The user or administrator has not consented to use the application with ID.
Changing the catch method when retrieving the Token did the trick:
if(e.ErrorCode == "failed_to_acquire_token_silently")
{
HttpContext.Current.Response.Redirect(authenticationContext.GetAuthorizationRequestUrlAsync(resourceId, _configuration.ClientId, new Uri(currentUrl),
new UserIdentifier(currentUserId, UserIdentifierType.UniqueId), string.Empty);
}
I don't see that you mention that so: in a B2B collaboration you've to invite user from other tenant first. The steps are like that:
invite and authorize a set of external users by uploading a comma-separated values - CSV file
Invitation will be send to external users.
The invited user will either sign in to an existing work account with Microsoft (managed in Azure AD), or get a new work account in Azure AD.
After signed in, user will be redirected to the app that was shared with them
That works perfectly in my case.
Regarding some problems which I've detect:
Trailing "/" at the end of the active directory resource - try to remove it as this may cause problems. Bellow you will find some code to get authentication headers:
string aadTenant = WebServiceClientConfiguration.Settings.ActiveDirectoryTenant;
string clientAppId = WebServiceClientConfiguration.Settings.ClientAppId;
string clientKey = WebServiceClientConfiguration.Settings.ClientKey;
string aadResource = WebServiceClientConfiguration.Settings.ActiveDirectoryResource;
AuthenticationContext authenticationContext = new AuthenticationContext(aadTenant);
ClientCredential clientCredential = new ClientCredential(clientAppId, clientKey);
UserPasswordCredential upc = new UserPasswordCredential(WebServiceClientConfiguration.Settings.UserName, WebServiceClientConfiguration.Settings.Password);
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync(aadResource, clientAppId, upc);
return authenticationResult.CreateAuthorizationHeader();
Applications provisioned in Azure AD are not enabled to use the OAuth2 implicit grant by default. You need to explicitly opt in - more details can be found here: Azure AD OAuth2 implicit grant

Error when calling any method on Service Management API

I'm looking to start an Azure runbook from a c# application which will be hosted on an Azure web app.
I'm using certificate authentication (in an attempt just to test that I can connect and retrieve some data)
Here's my code so far:
var cert = ConfigurationManager.AppSettings["mgmtCertificate"];
var creds = new Microsoft.Azure.CertificateCloudCredentials("<my-sub-id>",
new X509Certificate2(Convert.FromBase64String(cert)));
var client = new Microsoft.Azure.Management.Automation.AutomationManagementClient(creds, new Uri("https://management.core.windows.net/"));
var content = client.Runbooks.List("<resource-group-id>", "<automation-account-name>");
Every time I run this, no matter what certificate I use I get the same error:
An unhandled exception of type 'Hyak.Common.CloudException' occurred in Microsoft.Threading.Tasks.dll
Additional information: ForbiddenError: The server failed to authenticate the request. Verify that the certificate is valid and is associated with this subscription.
I've tried downloading the settings file which contains the automatically generated management certificate you get when you spin up the Azure account... nothing I do will let me talk to any of the Azure subscription
Am I missing something fundamental here?
Edit: some additional info...
So I decided to create an application and use the JWT authentication method.
I've added an application, given the application permissions to the Azure Service Management API and ensured the user is a co-administrator and I still get the same error, even with the token...
const string tenantId = "xx";
const string clientId = "xx";
var context = new AuthenticationContext(string.Format("https://login.windows.net/{0}", tenantId));
var user = "<user>";
var pwd = "<pass>";
var userCred = new UserCredential(user, pwd);
var result = context.AcquireToken("https://management.core.windows.net/", clientId, userCred);
var token = result.CreateAuthorizationHeader().Substring("Bearer ".Length); // Token comes back fine and I can inspect and see that it's valid for 1 hour - all looks ok...
var sub = "<subscription-id>";
var creds = new TokenCloudCredentials(sub, token);
var client = new AutomationManagementClient(creds, new Uri("https://management.core.windows.net/"));
var content = client.Runbooks.List("<resource-group>", "<automation-id>");
I've also tried using other Azure libs (like auth, datacentre etc) and I get the same error:
ForbiddenError: The server failed to authenticate the request. Verify that the certificate is valid and is associated with this subscription.
I'm sure it's just 1 tickbox I need to tick buried somewhere in that monolithic Management Portal but I've followed a few tutorials on how to do this and they all end up with this error...
public async Task StartAzureRunbook()
{
try
{
var subscriptionId = "azure subscription Id";
string base64cer = "****long string here****"; //taken from http://stackoverflow.com/questions/24999518/azure-api-the-server-failed-to-authenticate-the-request
var cert = new X509Certificate2(Convert.FromBase64String(base64cer));
var client = new Microsoft.Azure.Management.Automation.AutomationManagementClient(new CertificateCloudCredentials(subscriptionId, cert));
var ct = new CancellationToken();
var content = await client.Runbooks.ListByNameAsync("MyAutomationAccountName", "MyRunbookName", ct);
var firstOrDefault = content?.Runbooks.FirstOrDefault();
if (firstOrDefault != null)
{
var operation = client.Runbooks.Start("MyAutomationAccountName", new RunbookStartParameters(firstOrDefault.Id));
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
Also in portal:
1) Application is multitenant
2) Permissions to other applications section - Windows Azure Service Manager - Delegated permissions "Access Azure Service Management(preview)"
Ensure that your Management certificate has private key and was not made from the .CER file. The fact that you're not supplying a password when generating the X509Certificate object makes me think you're using public key only
Ensure that your Managemnet's certificate public key (.CER file) has been uploaded to the Azure management portal (legacy version, Management Certificate area)
Use CertificateCloudCredentials and not any other credential type of an object
Ok, stupid really but one of the tutorials I followed suggested installing the prerelease version of the libs.
Installing the preview (0.15.2-preview) has fixed the issue!

Posting to FB Page error(s)

Scenario: I want to get user access token of the fb page admin by JS login and retrieving token ONE TIME, and will store that in database. Then daily, I want to do wall post to those page.
I am using JS to get the initial token and storing it. Then using c# FacebookSDK for the web requests.
FB.login(function (response) {
var r = response;
// get access token of the user and update in database
$("#FacebookAccessToken").val(response.authResponse.accessToken);
},
{
scope: 'manage_pages,publish_stream'
});
Now I am saving this token in database as I will be using this for future graph calls - is this right?
On server side when I need to do a post after a day I retrieve that token and do the processing as below:
// step 1 get application access token
var fb1 = new FacebookClient();
dynamic appTokenCLient = fb1.Get("oauth/access_token", new
{
client_id = appId,
client_secret = appSecret,
grant_type = "client_credentials",
scope = "manage_pages,publish_stream",
redirect_uri = siteUrl
});
var fbTokenSettingVal = GetTokenFromDB(); // getting access token from database which was stored during JS fb login
// step 2 extend token
var fb2 = new FacebookClient(appTokenCLient.access_token);
dynamic extendedToken = fb2.Get("oauth/access_token", new
{
client_id = appId,
client_secret = appSecret,
grant_type = "fb_exchange_token",
fb_exchange_token = fbTokenSettingVal.Val
});
var userExtendedToken = extendedToken.access_token; // get extended token and update database
// step 3 get page access token from the pages, that the user manages
var fb3 = new FacebookClient { AppId = appId, AppSecret = appSecret, AccessToken = userExtendedToken };
var fbParams = new Dictionary<string, object>();
var publishedResponse = fb3.Get("/me/accounts", fbParams) as JsonObject;
var data = JArray.Parse(publishedResponse["data"].ToString());
var pageToken = "";
foreach (var account in data)
{
if (account["name"].ToString().ToLower().Equals("PAGE_NAME"))
{
pageToken = account["access_token"].ToString();
break;
}
}
// step 4 post a link to the page - throws error !
var fb4 = new FacebookClient(pageToken);
fb4.Post("/PAGE_NAME/feed",
new
{
link = "http://www.stackoverflow.com"
});
The last 4th step throws error, when posting to selected page:
The user hasn't authorized the application to perform this action
Have tried several different ways, but in vain. Assume that there is just a simple step which I am doing wrong here.
Also, is it ok to extend the fb access token for user every time before making request?
Any way to check if token is expired or not?
If you want to use that access token for future. You need to take offline_access token and that token you need to store in database.
This offline accesstoken will be expire once user will change the password or delete your application from facebook account.
private void GetPermenentAccessTokenOfUser(string accessToken)
{
var client2 = new FacebookClient(accessToken);
//get permenent access token
dynamic result = client2.Post("oauth/access_token", new
{
client_id = _apiKey,
client_secret = _apiSecret,
grant_type = "fb_exchange_token",
fb_exchange_token = accessToken
});
_accessToken = result.access_token;
}
Looks like for new apps we need to apply for manage_pages permission from our application:
which I am doing now. As it shows error when doing login:
Also, the app needs to be live, so they can reproduce this permission and verify that we do need this permission to post to pages. Its good for fb users safety but a time taking process for developers.
Any way this can be skipped for testing purpose?

Authorizing with stored credentials. Google Drive API for .NET

I'm trying to create desktop application which will allow to list files and folders on google drive account. On this momment I'm able to do it but there is a one issue. I have to re-login each time I want to open google drive account from my application. Is it possible to use stored locally AccessToken/Refresh tokens in order to avoid re-authorization each time?
Here method which is used to get authorization.
private IAuthorizationState GetAuthorization(NativeApplicationClient arg)
{
IAuthorizationState state = new AuthorizationState(new[] { "https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile" });
// Get the auth URL:
state.Callback = new Uri("urn:ietf:wg:oauth:2.0:oob");
UriBuilder builder = new UriBuilder(arg.RequestUserAuthorization(state));
NameValueCollection queryParameters = HttpUtility.ParseQueryString(builder.Query);
queryParameters.Set("access_type", "offline");
queryParameters.Set("approval_prompt", "force");
queryParameters.Set("user_id", email);
builder.Query = queryParameters.ToString();
//Dialog window wich returns authcode
GoogleWebBrowserAuthenticator a = new GooogleWebBrowserAuthenticator(builder.Uri.ToString());
a.ShowDialog();
//Request authorization from the user (by opening a browser window):
string authCode = a.authCode;
// Retrieve the access token by using the authorization code:
return arg.ProcessUserAuthorization(authCode, state);
}
SOLVED:
In order to invoke methods from Google Drive sdk first you need to instance of service:
var provider = new NativeApplicationClient(GoogleAuthenticationServer.Description, GoogleDriveHelper.CLIENT_ID, GoogleDriveHelper.CLIENT_SECRET);
var auth = new OAuth2Authenticator<NativeApplicationClient>(provider, GetAuthorization);
Service = new DriveService(auth);
Those CLIENT_ID and CLIENT_SECRET you will have after you sign up for application in Google API console.
Then you need to define GetAuthorization routine, which might look as following:
private IAuthorizationState GetAuthorization(NativeApplicationClient arg)
{
IAuthorizationState state = new AuthorizationState(new[] { "https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile" });
state.Callback = new Uri("urn:ietf:wg:oauth:2.0:oob");
state.RefreshToken = AccountInfo.RefreshToken;
state.AccessToken = AccountInfo.AccessToken;
arg.RefreshToken(state);
return state;
}
It will works if you already have Refresh and Access tokens (at least Refresh). So you need to authorize for some user account first.
Then you can use that Service instance to invoke sdk methods.
Hope it will help someone.

Categories

Resources