Looks like late last year, Google changed behavior of calendar API in not allowing service accounts to create calendar events with attendees.
https://github.com/googleapis/google-api-php-client/issues/1725
https://support.google.com/calendar/thread/16769831?hl=en
So, to create event with attendees one has to ensure that domain-wide delegation is setup for the service account, and then specify the target/delegate account for the calendar the service account is attempting to write events to. I've done the former, and the latter I have the following code...
const jwt = jsonwebtoken.sign({
iss: GOOGLE_CALENDAR_SERVICE_ACCOUNT_EMAIL,
scope: SCOPES,
aud: 'https://oauth2.googleapis.com/token',
iat: currentUnixTimeInSeconds,
exp: currentUnixTimeInSeconds + 3600,
sub: GOOGLE_CALENDAR_TARGET_ACCOUNT_EMAIL,
},
privateKey,
{
algorithm: 'RS256',
header: {
alg: 'RS256',
typ: 'JWT',
},
}
);
...and then call https://oauth2.googleapis.com to get a bearer token, but it always errors out. ( with "Invalid JWT: Failed audience check. The right audience is..." following by what looks to be a long bearer token URL"
https://oauth2.googleapis.com/token?grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=...
)
What is the right way to do impersonation with service accounts against the Google calendar API?
What you are looking for is called Domain Wide Delegation and the whole procedure is very detailed in this documentation page. Is much more simpler and safer to use the correct library than actually making yourself all the requests.
From the own google documentation on how to achieve this through HTTP requests:
Recommendation: Although your application can complete these tasks by directly interacting with the OAuth 2.0 system using HTTP, the mechanics of server-to-server authentication interactions require applications to create and cryptographically sign JSON Web Tokens (JWTs), and it's easy to make serious errors that can have a severe impact on the security of your application.
For this reason, we strongly encourage you to use libraries, such as the Google APIs client libraries, that abstract the cryptography away from your application code.
Related
I currently have a web application that uses Azure AD to authenticate a user. This generates a JWT token which is then sent to my .NET Core Web API, which authenticates and authorizes successfully. The relevant parts of the JWT token that is sent across looks like this when put into jwt.ms (if you need more on this let me know and I can update this):
"aud": "api://<api-clientid>",
"appid": "<webapp-clientid>",
"hasgroups": "true",
"oid": "<userid">,
"tid": "<tenantid>"
I need to get to the groups that the user is a part of, and as the user is part of more than 6 groups, I need to make a call to the Graph API in order to return all groups for the user. This is documented in the description of the access tokens here, which states I should make a call to https://graph.microsoft.com/v1.0/users/{userID}/getMemberObjects.
I have tried using Postman to create a token to authenticate with the Graph API through the on-behalf-of flow, but I get the below error (removed irrelevant parts):
{
"error": "invalid_grant",
"error_description": "AADSTS65001: The user or administrator has not consented to use the application with ID '<api-clientId>' named '<api-displayName>'. Send an interactive authorization request for this user and resource...",
"error_codes": [
65001
],
"suberror": "consent_required",
...
}
I've tried following the web API that calls web APIs guide which seems to do exactly what I want to, using the Microsoft.Identity.Web. When I have tried this, I run into a similar issue with a MicrosoftIdentityWebChallengeUserException saying that I require more consent from the user. I tried changing the audience of the token to be for https://graph.microsoft.com/ but this then fails the authorisation to my web API, and I cannot have multiple endpoints in the audience of the JWT. I also tried using ITokenAcquisition as described here, but this did not work either, with a similar error to Postman.
Finally, I tried following this sample which also appears to do what I require, where I made sure my web API and my web client were set up in the same way that they do. The only change I had to make was a small change to the manifest of the API to include the client application Id in the "knownClientApplications", but this doesn't seem to have made any difference.
I feel like I'm missing something simple here, where either my web application needs to know that my web API might need permission to the Graph API, or I'm missing more configuration on my Azure AD. Any help would be greatly appreciated, and if more information is needed I'll provide as much as I can!
The error message says that admin has didn't consent the application.
Login to Azure AD as Global Administrator and Grant admin consent API permission via App registrations -> API Permissions -> Add a permission -> My APIs -> A -> Application permissions to fix this issue.To fix this issue.
There's an open feature request for Metabase to support IAP. I took a stab at it, and have a Clojure implementation of the steps detailed in Securing your app with signed headers (i.e. verify token header, verify token payload, retrieve user identity).
But this question isn't necessarily specific to Metabase. The general idea is to replace Google Sign-In and only use only IAP signed headers for authentication and user creation in an application on Google App Engine (specifically, GAE flex environment).
The "problem" is that the user identity information from the IAP token looks like: {"email":"alice#example.com","sub":"accounts.google.com:118133858486581853996"}. I also came across Using special URLs, but this returns something like: {"email":"accounts.google.com:USER_EMAIL","sub":"accounts.google.com:118133858486581853996"}.
With a Google Sign-In token, I can obtain values for given_name and family_name along with email, which means I can fetch-or-create a valid Metabase user. Is there a way to get the first and last name via the JWT sub, (i.e. accounts.google.com:118133858486581853996)?
Hm, if they have a public profile you can pass the number after "accounts.google.com:" to https://developers.google.com/+/web/api/rest/latest/people/get . Unfortunately, you won't be able to authenticate to that API as the user, since IAP doesn't currently provide a way to call let users delegate access to call Google APIs. (You'll have to use a service account to call that API.)
The other solution would be, if IAP provided a way to a) specify additional scopes in its OAuth request to Google, and if it then b) passed additional claims from the OIDC token into the IAP JWT, you'd be able to configure IAP to request the "profile" scope. However, IAP currently only requests the "email" and "openid" scopes, and doesn't have a mechanism for specifying additional scopes.
-- Matthew, Google Cloud IAP engineering
I want to implement frictionless sign in process for my web app.
After some searching, I have found that there are two solutions available :
Google Smart Lock.
Credential Managment API.
My question is, What is the difference between the two API's (if any) and what are the possible use cases for both of these.
From what I have understood, both allow us to save account related info. But the advantage with smart lock is, that saved credentials can be used in corresponding android apps as well.
Thanks !
Note:
I intend to support login from multiple sources (google, facebook, linkedin etc.) , not just google.
TL;DR the one-tap sign-up / auto sign-in library includes Credential Management. You should probably just use the library: https://developers.google.com/identity/one-tap/web/get-started
Details
The JavaScript library supports account creation with Google Accounts (via a streamlined inline UX that can be shown on content pages instead user having to navigate to a traditional button-based UX and figure out which which button/option to pick and interact with pop-up/redirect)
And for returning users, the library allows you to programmatically retrieve on page load both tokens for existing one-tap / traditional Google Sign-In users as well as passwords via the Credential Management API in browsers that support it. You can do this with code such as the following:
const retrievePromise = googleyolo.retrieve({
supportedAuthMethods: [
"https://accounts.google.com",
"googleyolo://id-and-password"
],
supportedIdTokenProviders: [
{
uri: "https://accounts.google.com",
clientId: "YOUR_GOOGLE_CLIENT_ID"
}
]
});
retrievePromise.then((credential) => {
if (credential.password) {
// An ID (usually email address) and password credential was retrieved.
// Sign in to your backend using the password.
signInWithEmailAndPassword(credential.id, credential.password);
} else {
// A Google Account is retrieved. Since Google supports ID token responses,
// you can use the token to sign in instead of initiating the Google sign-in
// flow.
useGoogleIdTokenForAuth(credential.idToken);
}
}
See the documentation for details. The library does not currently support non-Google/password forms of identity, you'd have to implement sign-in flow with other mentioned identity providers SDKs yourself at the moment.
Also note that any sign-ins associated with a Google Account (OAuth token-based or stored and sync'ed password) will be available across Android and Chrome (and the rest of the for token-based accounts).
Please leave comments for any follow up questions.
I'm aware of how to create a Google authenticated app via with google-signin-client_id 3089273xx-xxxxxxxxxxxx.apps.googleusercontent.com & <script src="https://apis.google.com/js/platform.js" async defer></script>, but the problem here is that, I have not been able to LIMIT the login to just my company's G Suite instance.
The app I have is a "serverless" JS bundle hosted on S3. The logged in Google token is tied to an AWS role that accesses sensitive resources.
So typical solutions to check the email of googleUser.getBasicProfile() or pass a hd parameter don't make any security sense since they can be manipulated with browser dev tools IIUC.
Is there some other Google API I could be using or strategy I could apply? I imagine the solution would come in the form of a special google-signin-client_id for my company's domain which is hosted by G Suite. This is how it's tied to the role at AWS:
I'm aware I could setup duplicate my users in AWS "user pools" and use Cognito, but I am trying to have a "single source of truth" for the company's employees & ease the administration burden.
UPDATE: This answer is insecure as if you simply remove hosted_domain, you can authenticate with any Google login.
After straying upon https://developers.google.com/identity/work/it-apps & using GAPI directly I found I could do a
GAPI.auth2.init({
client_id: CLIENT_ID,
hosted_domain: 'example.com'
})
And then as the documentation advises, you setup Manage API client access
So now only users of #example.com on Gsuite can access this JS app! This took weeks to figure out. So just to conclude, how to authenticate using Google on a AWS powered serverless app:
Setup a client ID via OAuth client ID with your whitelisted origin URLs from https://console.developers.google.com/apis/credentials
In AWS IAM setup a Role with Google as the (web) Identity provider with the client ID
Add your client ID https://admin.google.com/AdminHome?chromeless=1#OGX:ManageOauthClients as documented here https://developers.google.com/identity/work/it-apps to crucially limit your application to your company's domain.
So now we have a statically hosted App limited to only company employees to access sensitive paid AWS APIs.
I tried 3 different options, the first one worked for my scenario:
First Option - Validating Google Id Token on each call on lambda side
I always pass the id_token as a header on the client calls(web and mobile apps).
"acceptableHds" Is the list of allowed domains.
const oauth = new Auth.OAuth2(CLIENT_ID_WEB, CLIENT_SECRET);
oauth.verifyIdToken(token, null, (err, ticket) => {
if (err) {
return reject(err);
}
const payload = ticket.getPayload();
const tokenIsOK = payload &&
payload.aud === CLIENT_ID &&
new Date(payload.exp * 1000) > new Date() &&
acceptableISSs.has(payload.iss) &&
acceptableHds.has(payload.hd)
return tokenIsOK ? resolve(payload.hd) : reject();
});
Second Option - Validating Google Id Token once on lambda side
I started this alternative way but I didn't finished because the first solutions fitted to my needs and the milestones was close(it needs a indentity pool):
1)Send the id_token to the lambda function and validate it on Google API(here is where you can check the domain using the code above)
2)Call the cognitoidentity.getOpenIdTokenForDeveloperIdentity on the lambda side using the id_token coming from the browser
3) On the client, call any of the Cognito or STS functions like assumeWebIdentity, AssumeRole using the tokens returned from getOpenIdToken.
function getCognitoToken(id_token) {
var param = {
IdentityPoolId: 'us-east-1:f7b3d55f-6b63-4097-be8f-3dc22ddec1a4',
Logins: { 'accounts.google.com': id_token }
}
return check_company(id_token).then(function (valid) {
return cognitoidentity.getOpenIdTokenForDeveloperIdentity(param).promise()
})
I couldn't finish the third step. You need use the tokens received on the second step without revealing the 'identity pool id'. If you do that and assure that the role can't list identity pool ids, it will work as intended and It will be secure.
Third Option - SAML provider
You can create a SAML provider and use SAML assertions to validate the user domain.
http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_saml_assertions.html
I failed miserably trying to do it.
P.S.: Google Admin let you create private apps, limiting to you company domains, but It works only for mobile as far as I know
https://support.google.com/a/answer/2494992?hl=en
Hope it helps someone!
I'm using oauth to authenticate in google calendar, but i'm trying to send user and password to avoid open the authentication popup.
I'ts like automatic authentication.
Is it possible?
Thanks.
Since you want your application to always create events in a single calendar that is under your control, here is how I would do it:
Create a service account in Google Cloud Console - recent instructions on how to do so can be found here: google oauth2 how to get private key for service account
Share your calendar with that service account: https://support.google.com/calendar/answer/37082?hl=en
Write your code to use the service account credentials (private key downloaded in step 1).
I didn't post any sample code, as I'm not sure what language you are using. The following has some discussion of this use-case in PHP: Access Google calendar events from with service account: { "error" : "access_denied" }. No google apps