I have a consumer google account of the form
"me#gmail.com" for which I have a service account of the form
"Something#developer.gserviceaccount.com". I am trying to use the private key generated for this service account to generate an access token and then may be edit or view the calendar associated with "me#gmail.com".
The authentication code:
String emailAddress = "something#developer.gserviceaccount.com";
JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
File file = new File("path to .p12 file");
HttpTransport httpTransport = GoogleNetHttpTransport
.newTrustedTransport();
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(emailAddress)
.setServiceAccountPrivateKeyFromP12File(file)
.setServiceAccountScopes(
Collections.singleton("https://www.googleapis.com/auth/calendar"))
.setServiceAccountUser("me#gmail.com")
.build();
String accessToken = credential.getAccessToken();
But the access token generated is null. The service account has edit permissions. The program is able to access the .p12 file.
Any cue as to where am I going wrong?
I think you've misunderstood how Service Accounts work. Impersonating a user only works within a Google Apps domain. You can't use a Service Account to impersonate a gmail account.
I doubt you get an access token when using a service account. If you were using OAuth2 dance and prompting the user for permissions then yes can get an access token, etc.. This is the correct way to initialize the API Calendar instance from a Google Credential object:
import com.google.api.services.calendar.Calendar;
Calendar service = new Calendar.Builder(httpTransport, jsonFactory, null)
.setHttpRequestInitializer(credential).build();
You can then use the Calendar instance to make API calls. More information can be found here:
https://developers.google.com/admin-sdk/directory/v1/guides/delegation
https://developers.google.com/google-apps/calendar/quickstart/java
Related
I'm building a server on top of MS Graph API and all I need to do is upload and download images to OneDrive. End users of the application will not access OneDrive directly nor will they all even have accounts. The files need only be accessible to the application itself and a handful of power users who do have their own credentials.
I would like to be able to just configure the credentials for a service account to be the only one accessing the bucket, but it seems all the auth flows require an end user to login. Is there an API-centric way to do this transparently? Or should I infer from the lack of explicit support that this isn't a valid use case for OneDrive and I should look at Azure Blob Storage?
You can authenticate to MS Graph API with username + password using the class UserPasswordCredential (https://learn.microsoft.com/en-us/dotnet/api/microsoft.identitymodel.clients.activedirectory.userpasswordcredential?view=azure-dotnet). Sample:
UserCredential creds = new UserPasswordCredential("fred#contoso.onmicrosoft.com","password");
var bearerToken = AcquireToken("https://graph.microsoft.com", "d1ddf0e4-d672-4dae-b554-9d5bdfd93547", creds).Result;
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + result.AccessToken);
var graphInfo = client.GetAsync("https://graph.microsoft.com/v1.0/me/").Result;
Warning: There are only few use cases where using username + password directly is legitimate (see http://www.cloudidentity.com/blog/2014/07/08/using-adal-net-to-authenticate-users-via-usernamepassword/). Additionally, UserPasswordCredential is not available in DotNetCore.
I have the following permissions granted in my Azure AD App:
App:
Read calendars in all mailboxes
Read and write calendars in all mailboxes
Read calendars in all mailboxes
Delegated:
Read user and shared calendars
Read and write user and shared calendars
Read and write user calendars
Read user calendars
Read and write user and shared calendars
Read user and shared calendars
Registration Screen Shot
I am successfully generating an Access Token like the following:
const string clientId = "my-client-id";
const string clientSecret = "my-secret"; // C
var tenant = "mytenant.onmicrosoft.com";
var authContext = new AuthenticationContext($"https://login.windows.net/{tenant}/oauth2/token");
var credentials = new ClientCredential(clientId, clientSecret);
AuthenticationResult authResult = await authContext.AcquireTokenAsync("https://graph.microsoft.com/", credentials);
With that Access Token, I am trying to make a basic request to /CalendarView passing the Bearer token header:
https://outlook.office.com/api/v2.0/users/my#email.com/calendarview?startDateTime=2017-11-12&endDateTime=2017-11-13
However, I keep receiving Access Denied. Are there additional permissions I need to set? Am I calling into the correct endpoint?
You don't include the body of the error response, but my guess is that you're hitting this because Exchange won't accept a token generated with a shared secret. Instead, you need to use a certificate-based assertion to request the token. Azure documents this a "Second case: Access token request with a certificate" here.
I actually was able to figure this out. Instead of using the Exchange API, I just applied the permissions in the Graph API.
Hit the following endpoint:
https://graph.microsoft.com/v1.0/users/{email}/calendarview?startDateTime={startDate}&endDateTime={endDate}
It's not very clear the difference between which API to use... but I'm moving forward now.
I'm having some issues with google API while generating the token. The code works fine in the local machine but the publish version gives "Access is denied" error. I know that must be something related with the folder's permissions, but I don't know how to solve it. Actually our authorization function is like this:
public static DriveService AuthenticateOauth(string clientId, string clientSecret, string userName)
{
String folder = System.Web.HttpContext.Current.Server.MapPath("/App_Data/MyGoogleStorage/");
string[] scopes = new string[] { DriveService.Scope.Drive };
// here is where we Request the user to give us access, or use the Refresh Token that was previously stored in %AppData%
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets { ClientId = clientId, ClientSecret = clientSecret }
, scopes
, userName
, CancellationToken.None
, new FileDataStore(folder)).Result;
DriveService service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Drive API",
});
return service;
}
The website is written in ASP NET MVC 5 and is hosted in Azure websites.
Thank you for your help.
The code works fine in the local machine but the publish version gives "Access is denied" error.
I have a same issue after I deploy the web application to Azure websites.
I refer to “Web applications (ASP.NET MVC)” section from this article to get user Credential and use DriveService, and I find it works fine on Azure websites.
I finally decided to use only my own drive account. So I created a token and I'm using it all the time. I followed these topics:
1.- Create a token for Google Drive API: https://stackoverflow.com/a/19766913/4965910
2.- Add Google API to MVC properly and use our token:
GoogleWebAuthorizationBroker in MVC For Google Drive Access
You must be careful, is easy to get stuck in some steps.
This only woks for ASP NET MVC applications with one drive account. I know is not solves the problem directly, this way is avoided it. Hope it helps.
I have a MVC application that it's using local AD to authenticate users and get their Roles for set permissions to the application. Now I have a new requirement, I'll use an external AD to authenticate the users rather than the local one. So, my application will redirect users to the client's login page, users will enter username and password that will be validated on their AD and then redirect back to my application with a token. I need to get this token and post once again to the customer endpoint in order to get a JWT token.
The problem is that I could not find a good example of something similar to that. I was trying use Open Id:
app.UseOpenIdConnectAuthentication(new
OpenIdConnectAuthenticationOptions {
ClientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
Authority = "https://xx.xxxxxxxxx.xxx/adfs/oauth2/authorize",
RedirectUri = "http://localhost:27100/token",
ResponseType = "code",
Scope = "openid email",
Resource = "xxxxxxxx",
UseTokenLifetime = false,
SignInAsAuthenticationType = "Cookies",
});
but I'm getting the following error:
Unable to create to obtain configuration from:
'https://xx.xxxxxxxxx.xxx/adfs/oauth2/authorize/.well-known/openid-configuration
If I open the full url on my local browser I can see the login page to the customer AD. I'm not sure if OpenIdAuthentication is the right one for this scenario, I was trying this one because is has all the properties that I need to set. Is there any other way to achieve that?
Are you trying to do an on-behalf of authentication?
Refer to https://technet.microsoft.com/en-us/windows-server-docs/identity/ad-fs/development/ad-fs-on-behalf-of-authentication-in-windows-server-2016
For OpenID Connect with AD FS please refer to https://technet.microsoft.com/en-us/windows-server-docs/identity/ad-fs/development/enabling-openid-connect-with-ad-fs-2016
The .well-know/openid-configuration endpoint should resolve to something that looks like:
You probably can just update the Authority property to one of these values (depending on where the STS is running):
"https://xx.xxxxxxxxx.xxx/adfs/"
"https://xx.xxxxxxxxx.xxx/adfs/oauth2"
My end goal is to be able to retrieve place details from Google's API.
I need to do this as a Service Account, since this is kicked off as a background task on my server. Service Accounts require you to exchange a JWT (JSON Web Token) for an access_token. I finally got that working and am receiving an access_token. Phew.
Now however, I don't know what to do with this access_token.
The Place Details API says that the key parameter is required, but I don't have a key. Just an access_token. Using that value for key or changing the name of the paramater to access_token is not working.
Ultimately I need to be able to hit a URL like so:
https://maps.googleapis.com/maps/api/place/details/json?reference={MY_REFERENCE}&sensor=false&key={MY_ACCESS_TOKEN}
How do I use my Access Token to make a request to the Google Place Detail APIs?
Update 1
Still no success, but I thought I'd post the details of my request in case there's something wrong with what I'm submitting to Google.
I'm using the JWT Ruby library, and here are the values of my claim set:
{
:iss => "54821520045-c8k5dhrjmiotbi9ni0salgf0f4iq5669#developer.gserviceaccount.com",
:scope => "https://www.googleapis.com/auth/places",
:aud => "https://accounts.google.com/o/oauth2/token",
:exp => (Time.now + 3600),
:iat => Time.now.to_i
}
Looks sane to me.
Create the service account and its credentials
You need to create a service account and its credentials. During this procedure you need to gather three items that will be used later for the Google Apps domain-wide delegation of authority and in your code to authorize with your service account. These three items are your service account:
• Client ID.
• Private key file.
• Email address.
In order to do this, you first need a working Google APIs Console project with the Google Calendar API enabled. Follow these steps:
Go to the Google APIs Console.
Open your existing project or create a new project.
Go to the Service section.
Enable the Calendar API (and potentially other APIs you need access to).
You can now create the service account and its credentials. Follow these steps:
Go to the API Access section.
Create a client ID by clicking Create an OAuth 2.0 client ID...
Enter a product name, specify an optional logo and click Next.
Select Service account when asked for your Application type and click Create client ID.
At this point you will be presented with a dialog allowing you to download the Private Key as a file (see image below). Make sure to download and keep that file securely, as there will be no way to download it again from the APIs Console.
After downloading the file and closing the dialog, you will be able to get the service account's email address and client ID.
You should now have gathered your service account's Private Key file, Client ID and email address. You are ready to delegate domain-wide authority to your service account.
Delegate domain-wide authority to your service account
The service account that you created now needs to be granted access to the Google Apps domain’s user data that you want to access. The following tasks have to be performed by an administrator of the Google Apps domain:
Go to your Google Apps domain’s control panel. The URL should look like: www.google.com/a/cpanel/mydomain.com
Go to Advanced tools... > Manage third party OAuth Client access.
In the Client name field enter the service account's Client ID.
In the One or More API Scopes field enter the list of scopes that your application should be granted access to (see image below). For example if you need domain-wide access to the Google Calendar API enter: www.googleapis.com/auth/calendar.readonly
Click the Authorize button.
Your service account now has domain-wide access to the Google Calendar API for all the users of your domain, and potentially the other APIs you’ve listed in the example above.
Below is a description that uses a service account to access calendar data in PHP
The general process for service account access to user calendars is a follows:
• Create the Google client
• Set the client application name
• If you already have an Access token then check to see if it is expired
• If the Access token is expired then set the JWT assertion credentials and get a new token
• Set the client id
• Create a new calendar service object based on the Google client
• Retrieve the calendar events
Note: You must save the Access token and only refresh it when it is about to expire otherwise you will receive an error that you have exceeded the limit for the number of access tokens in a time period for a user.
Explanation of Google PHP Client library functions used:
The client object has access to many parameters and methods all of the following are accessed through the client object:
Create a new client object:
$client = new Google_Client();
Set the client application name:
$client->setApplicationName(“My Calendar App”);
Set the client access token if you already have one saved:
$client->setAccessToken($myAccessToken);
Check to see if the Access token has expired, there is a 30 second buffer, so this will return true if the token is set to expire in 30 seconds or less. The lifetime of an Access token is one hour. The Access token is actually a JSON object which contains the time of creation, it’s lifetime in seconds, and the token itself. Therefore no call is made to Google as the token has all of the information locally to determine when it will expire.
$client->isAccessTokenExpired();
If the token has expired or you have never retrieved a token then you will need to set the assertion credentials in order to get an Access token:
$client->setAssertionCredentials(new Google_AssertionCredentials(SERVICE_ACCOUNT_NAME,array(CALENDAR_SCOPE), $key,'notasecret','http://oauth.net/grant_type/jwt/1.0/bearer',$email_add));
Where:
SERVICE_ACCOUNT_NAME is the the service account email address setup earlier.
For example:’abcd1234567890#developer.gserviceaccount.com’
CALENDAR_SCOPE is the scope setup in the Google admin interface.
For example: ‘https://www.googleapis.com/auth/calendar.readonly’
$key is the content of the key file downloaded when you created the project in Google apps console.
$email_add is the Google email address of the user for whom you want to retrieve calendar data.
Set the client id:
$client-setClientId(SERVICE_CLIENT_ID);
Where:
SERVICE_CLIENT_ID is the service account client ID setup earlier.
For example: ‘abcd123456780.apps.googleusercontent.com’
Create a new calendar service object:
$cal = new Google_CalendarService($client);
Several options can be set for calendar retrieval I set a few of them in the code below, they are defined in the api document.
$optEvents = array('timeMax' => $TimeMax, 'timeMin' => $TimeMin, 'orderBy' => 'startTime', 'singleEvents' => 'True');
Get the list of calendar events and pass the above options to the call:
$calEvents = $cal->events->listEvents('primary', $optEvents);
Loop through the returned event list, the list is paged so we need to fetch pages until the list is exhausted:
foreach ($calEvents->getItems() as $event) {
// get event data
$Summary = $event->getSummary();
$description = $event->getDescription();
$pageToken = $calEvents->getNextPageToken();
if ($pageToken) { // if we got a token the fetch the next page of events.
$optParams = array('pageToken' => $pageToken);
$calEvents = $cal->events->listEvents('primary', $optParams);
} else {
break;
}
}
Get the Access token:
$myAccessToken=$client->getAccessToken();
Save the access token to your permanent store for the next time.
The language isn't important php, ruby, .net, java the process is the same. The api's console shows the Places API as supporting service accounts so it should be possible to access it.
As far as using the token please have a look at https://code.google.com/p/google-api-ruby-client/ code as the usage is clearly defined in the code repository. Doesn't make any difference if the access token is for a service account or a single user the process for using the token is the same. See the section titled "Calling a Google API" in the following link: https://developers.google.com/accounts/docs/OAuth2InstalledApp
The access token is sent in the http authorization header along with the request.For a calendar request it would look something like the following:
GET /calendar/v3/calendars/primary HTTP/1.1
Host: www.googleapis.com
Content-length: 0
Authorization: OAuth ya29.AHES6ZTY56eJ0LLHz3U7wc-AgoKz0CXg6OSU7wQA