Impersonate user with google calendar API oauth2 service account (node.js) - node.js

I am trying to use a server for server authentication through the google calendar API. In the documentation they mention you can impersonate a user. I have added owner permissions to the account I want to impersonate and made sure the domain-wide delegation is enabled. From what I have read, the 'user#example' should specify the impersonator, but it does not work. I have all the functions of creating events etc working, but I can't get it to be from an email other than the randomly generated google one.
Here's my code:
var google = require('googleapis');
var calendar = google.calendar('v3');
var scopes = ['https://www.googleapis.com/auth/calendar'];
var key = require ('./xxx.json'); // private json
console.log("Calendar Service connected");
var jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
scopes,
'user#example.com'
);
jwtClient.authorize(function(err, token) {
if(err) {
//console.log(err);
}
console.log('token',token);
//listCalendars(jwtClient);
});
module.exports = {};

I got it to work after:
Enabling domain-wide delegation
Adding user to the service account as the owner
Most importantly: Going onto google admin and giving api access to the service account

Related

Why does my ConfidentialClientApplication AcquireTokenForClient return a null Account?

I have a WinForms app with no UI that runs as a daemon. This uses MailKit to read and send emails in an outlook.office365.com environment. This now requires OAuth2, which I have implemented as follows:
var options = new ConfidentialClientApplicationOptions() {
TenantId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
ClientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
ClientSecret = "xxxxx~xxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxx",
RedirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient"
};
var app = ConfidentialClientApplicationBuilder.CreateWithApplicationOptions(options).Build();
var scopes = new string[] { "https://outlook.office.com/.default" };
var authResult = await app.AcquireTokenForClient(scopes).ExecuteAsync();
var credentials = new SaslMechanismOAuth2(authResult.Account.Username, authResult.AccessToken);
The app is registered in Azure with the following permissions:
IMAP.AccessAsUser.All
POP.AccessAsUser.All
SMTP.Send
User.Read
The AcquireTokenForClient result has a likely looking AccessToken, but a null Account.
I am using the MailKit SaslMechanismOAuth2 method to get credentials, but this fails because there is no Account.Username.
I have tried calling SaslMechanismOAuth2 with a username (email address) that is registered in Azure, instead of Account.Username, but the result is
535: 5.7.3 Authentication unsuccessful
[SYCPR01CA0039.ausprd01.prod.outlook.com]
What do we need to do to get an AcquireTokenForClient result with Account defined?
Or is there another way for us to get credentials?

What permissions are needed to read the Azure AD users' calendar via Graph API

I need to access the calendars of Azure AD users. I do it with the code below:
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "MY_TENANT_ID";
var clientId = "APP_ID";
var clientSecret = "APP_SECRET";
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret, options);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var user = await graphClient.Users["USER_ID"]
.Calendar.Events
.Request()
.GetAsync();
As a result, I get the following error:
Status Code: Unauthorized
Microsoft.Graph.ServiceException: Code: NoPermissionsInAccessToken
Message: The token contains no permissions, or permissions can not be understood.
In Azure AD->App Registrations->API permissions I have got Calendars.Read permission, but don't have User.Read.All permission. So the question is can I get data from calendars without User.Read.All permission and if I can what am I doing wrong?
According to your code snippet, you used client credential flow to obtain the authentication and call graph api to list all calendar events for a specific user.
So you need to make your azure ad application to get Calendar.Read application permission. Without the permission then you can't call the api successfully
How to add application permission:

Access to User Outlook Calendar from REST API

I'm struggling a bit with Microsoft authentication architecture to access resources using the Graph API.
Let me explain my use-case: I have an Outlook account, which I need to insert events into the Calendar. I also have a REST API, in Node.js, that should read these events, using /me/events or /users/{id}/events Graph endpoint.
Since it is only one user, I don't need to implement login, but rather have the REST API be able to get an Authorization token to access these resources.
I tried to use the ConfidentialClientApplication class to login using the client_id and client_secret for my application (configured through Azure), but whenever I call the Microsoft Graph after login, I receive a 401.
Assuming that the problem is that the login I'm performing is with an admin account, I added the Application type Calendars.Read permission, to no help.
What am I doing wrong?
I just need to access this users' Calendar :(
Thanks for making it this far!
If you want to use ConfidentialClientApplication to get an access token and call Microsoft Graph API to read users' Calendar, try the code below :
const msal = require('#azure/msal-node');
const fetch = require('node-fetch');
const tenant= '<your tenant ID/name>'
const appID= '<azure ad app id>'
const appSec = '<azure ad app sec>'
const userID = '<user ID/UPN>'
const config = {
auth: {
clientId: appID,
authority: "https://login.microsoftonline.com/" + tenant,
clientSecret: appSec
}
}
function readUserCalendar(userID,accessToken){
const URL = 'https://graph.microsoft.com/v1.0/users/'+userID+'/events?$select=subject'
fetch(URL,{headers: { 'Authorization': 'Bearer ' + accessToken}})
.then(res => res.json())
.then(json => console.log(json));
}
const pca = new msal.ConfidentialClientApplication(config);
const acquireAccessToken = pca.acquireTokenByClientCredential({
scopes: ["https://graph.microsoft.com/.default"]
});
acquireAccessToken.then(result=>{readUserCalendar(userID,result.accessToken)})
Permissions grand to azure ad app in this case :
My test account calendar data:
Result :

Google Calendar API and Service Account permission error

I'm trying to integrate the Google Calendar API in my app.
So far i've managed to do this:
Created a new project on Cloud Platform
Enabled Calendar API
Added a new service account with role: Owner
Generated jwt.json
Granted domain-wide for that service account
Shared a calendar with that service account (modify rights)
Enabled in the GSuite the option for everyone out of the organisation to modify the events
Now, my code on node.js looks like this:
const { JWT } = require('google-auth-library');
const client = new JWT(
keys.client_email,
null,
keys.private_key,
['https://www.googleapis.com/auth/calendar']
);
const url = `https://dns.googleapis.com/dns/v1/projects/${keys.project_id}`;
const rest = await client.request({url});
console.log(rest);
The error I get is:
Sending 500 ("Server Error") response:
Error: Insufficient Permission
Anyone has any ideea? This gets frustrating.
How about this modification?
I think that in your script, the endpoint and/or scope might be not correct.
Pattern 1:
In this pattern, your endpoint of https://dns.googleapis.com/dns/v1/projects/${keys.project_id} is used.
Modified script:
const { JWT } = require("google-auth-library");
const keys = require("###"); // Please set the filename of credential file of the service account.
async function main() {
const calendarId = "ip15lduoirvpitbgc4ppm777ag#group.calendar.google.com";
const client = new JWT(keys.client_email, null, keys.private_key, [
'https://www.googleapis.com/auth/cloud-platform' // <--- Modified
]);
const url = `https://dns.googleapis.com/dns/v1/projects/${keys.project_id}`;
const res = await client.request({ url });
console.log(res.data);
}
main().catch(console.error);
In this case, it is required to enable Cloud DNS API at API console. And it is required to pay. Please be careful with this.
I thought that the reason of your error message of Insufficient Permission might be this.
Pattern 2:
In this pattern, as a sample situation, the event list is retrieved from the calendar shared with the service account. If the calendar can be used with the service account, the event list is returned. By this, I think that you can confirm whether the script works.
Modified script:
const { JWT } = require("google-auth-library");
const keys = require("###"); // Please set the filename of credential file of the service account.
async function main() {
const calendarId = "###"; // Please set the calendar ID.
const client = new JWT(keys.client_email, null, keys.private_key, [
"https://www.googleapis.com/auth/calendar"
]);
const url = `https://www.googleapis.com/calendar/v3/calendars/${calendarId}/events`; // <--- Modified
const res = await client.request({ url });
console.log(res.data);
}
main().catch(console.error);
Note:
This modified script supposes that you are using google-auth-library-nodejs of the latest version.
Reference:
JSON Web Tokens in google-auth-library-nodejs

Getting Error: unauthorized_client when trying to authorize script

I've created service account with domain wide delegation and its scopes (in Admin console and Developer console) as described in documentation. I've been trying this for a week now and I am stuck. This is my code:
const google = require('googleapis');
const gmail = google.gmail('v1');
const directory = google.admin('directory_v1');
const scopes = [
'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/admin.directory.user.readonly'
];
const key = require('./service_key.json');
var authClient = new google.auth.JWT(
key.client_email,
key,
key.private_key,
scopes,
"kruno#example.com"
);
authClient.authorize(function(err, tokens){
if (err) {
console.log(err);
return;
}
// API call methods here...
});
I get this error:
Error: unauthorized_client
I am unable to understand:
Is this proper technique for calling Google API methods from server-side scripts without any user interaction? (under domain only)
How do service account and actual user account communicate this way?
I heard about callback URI, am I missing it?
I think you are missing the final step which is giving access to your application in the control panel of your domain.
You can follow doc properly to activate it with your application
https://developers.google.com/+/domains/authentication/delegation
Also you can start with your first call step here
https://developers.google.com/adwords/api/docs/guides/first-api-call

Resources