I have a web app that needs to allow for users to book an appointment. The app loads my calander and shows a visual representation of what timeslots are available from my calander. Each timeslot available was made into a button. When the user clicks the button, it needs to set the event for that time slot. The event details are pre-made, the client doesn't do anything but click the time they want.
Ill be adding a form to accept their email and add it to the "attendees" object so that it can update their calendar.
Using the javascript quickstart, you use
gapi.load('client:auth2', () => {
gapi.client.init({
apiKey: API_KEY,
clientId: CLIENT_ID,
discoveryDocs: DISCOVERY_DOCS,
scope: SCOPES,
});
To get access to my calendar. But the rest of the quickstart
.then(() => {
gapi.auth2.getAuthInstance().isSignedIn.listen(updateSigninStatus);
});
function updateSigninStatus() {
gapi.auth2.getAuthInstance().isSignedIn.get();
}
Asks the user to sign into their own account using Oauth to add an event...
How do I just allow for them to add an event by clicking the timeslot button, without needing them to go through Oauth?
For visual representation of what it looks like:
The reason why it's asking the user to sign in is because your code is trying to make their account add a new event. What you probably want is to use a service account to be able to create the events without need to log in. This needs to be done in the backend, as it will contain the private key (which should never be public). This is called server to server application.
References & further reading
Using OAuth 2.0 for Server to Server Applications (Google's OAuth 2.0 Guide)
Google APIs Node.js Client (GitHub project)
Related
I've written a simple Calendar API call using the official NodeJS client: https://github.com/googleapis/google-api-nodejs-client#service-account-credentials
It works fine on my local machine, using a Service Account set up with Domain-Wide Delegation to create the event and invite a list of attendees on my behalf.
I set the credentials location using GOOGLE_APPLICATION_CREDENTIALS env var on my local machine, but do not set this on the Google Cloud Run service because it's supposedly automatic since I've associated the Service Account. This assumption seems true because I can call GoogleAuth functions and get back the expected service account name.
However, once I try to run it in Cloud Run, it throws the error:
Error: Service accounts cannot invite attendees without Domain-Wide Delegation of Authority.
At first I thought this was an issue with default credentials somehow loading the wrong service account.
I added logging directly before the event is called to see what account it is using:
const auth = new googleClient.auth.GoogleAuth({
clientOptions: {
subject: eventOwner
},
scopes: calendarScopes,
})
const serviceAcctName = (await auth.getCredentials())?.client_email
googleClient.options({
auth: auth
})
logger.info(`${serviceAcctName} acting as ${eventOwner}, using calendar ${calendarId}`)
const calendar = googleClient.calendar('v3')
const response = await calendar.events.insert(event)
The log output is exactly as expected, with the correct service account acting as the correct user on the correct calendar id.
I've double-checked that the account has domain-wide delegation of authority and the proper scopes, and it works fine on my local machine, so the only thing I can think of is something about the library's feature of grabbing default credentials in a Google environment is overwriting my googleClient.options() call. But I'm still confused because GoogleAuth functions still give the expected service account info when it grabs the 'default'.
Our supplier maintains a Googlesheet and we are trying to add a form on our website that accesses that sheet and pulls data from it. Very simple task but we are having trouble with OAuth. The sheet is not public and only shared with certain people (including my Google ID).
Now, I used Google Developer Console to setup the Googlesheets API library. I added a simple form which is here: https://alcocovers.com/knowledge-base/track-your-order/. When I first used the form, I clicked on the "sign in" button (currently hidden), it showed me the consent form in a popup, I signed in using my Google ID and it asked "alcocovers.com wants access to my profile info" and I gave access. The form started to work and I could pull information from the sheet and show on our web page.
But the problem is, the authentication I did to link the website to access my Google account (and the google sheet) doesn't work for everyone. If I use the form in incognito mode, it fails to access the sheet that means everyone who uses the form has to give consent. That doesn't make sense because I thought when I signed in first time and gave the website access to my account and sheet, it will work for everyone. It's the website making the access not the individual user. We want the users of our site to be able to use the form and pull data from the sheet. How can I achieve this?
Below is the code I am using to initialize and authenticate the client. This code is copy pasted from the Google Sheet API documentation. I am assuming this needs to change so the OAuth only happens once which is already done linking the site to the sheet and on future form use, no consent should be needed.
function initClient() {
var API_KEY = ''; // I added api key here
var CLIENT_ID = ''; // I added client id here
var SCOPE = 'https://www.googleapis.com/auth/spreadsheets.readonly';
gapi.client.init({
'apiKey': API_KEY,
'clientId': CLIENT_ID,
'scope': SCOPE,
'discoveryDocs': ['https://sheets.googleapis.com/$discovery/rest?version=v4'],
}).then(function() {
gapi.auth2.getAuthInstance().isSignedIn.listen(updateSignInStatus);
updateSignInStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
});
}
function handleClientLoad() {
gapi.load('client:auth2', initClient);
}
function updateSignInStatus(isSignedIn) {
if (isSignedIn) {
makeApiCall();
}
}
function handleSignInClick(event) {
gapi.auth2.getAuthInstance().signIn();
}
function handleSignOutClick(event) {
gapi.auth2.getAuthInstance().signOut();
}
Update #1:
After trying the solution suggested by #Jescanellas, I am getting following error. Note, I kept the same API key, only changed the CLIENT ID.
The other users cannot use your credentials to access the Sheet. In order to do that you need to use a Service Account. Once you create it you can use your Google Account to make the authorized calls to the API with Domain-Wide Delegation, by using the Service Account credentials instead of yours. Follow the steps from the documentation:
1 - Create the service account and credentials
Open the Service accounts page. If prompted, select a project.
Click add Create Service Account, enter a name and description for
the service account. You can use the default service account ID, or
choose a different, unique one. When done click Create.
The Service account permissions (optional) section that follows is
not required. Click Continue.
On the Grant users access to this service account screen, scroll
down to the Create key section. Click add Create key.
In the side panel that appears, select the format for your key: JSON
is recommended.
Click Create. Your new public/private key pair is generated and
downloaded to your machine; it serves as the only copy of this key.
For information on how to store it securely, see Managing service
account keys.
Click Close on the Private key saved to your computer dialog, then
click Done to return to the table of your service accounts.
2 - To enable G Suite domain-wide delegation:
Locate the newly-created service account in the table. Under
Actions, click show more, then Edit.
In the service account details, click Show domain-wide
delegation, then ensure the Enable G Suite Domain-wide Delegation
checkbox is checked.
If you haven't yet configured your app's OAuth consent screen, you
must do so before you can enable domain-wide delegation. Follow the
on-screen instructions to configure the OAuth consent screen, then
repeat the above steps and re-check the checkbox.
Click Save to update the service account, and return to the table of
service accounts. A new column, Domain-wide delegation, can be seen.
Click View Client ID, to obtain and make a note of the client ID.
As said before, use the Service Account credentials to access the Sheet.
I want to allow users to sign in/up via GitHub using firebase by clicking on the same button.
I create a new authentication for every user in the server side.
With the little piece of code, I'm able to detect if either the user is new or not:
const provider = new firebase.auth.GithubAuthProvider();
firebase.auth().signInWithPopup(provider).then((result) => {
if (result.additionalUserInfo.isNewUser) {
// The user is new
} else {
// The user is old
}
But, when the function signInWithPopup is called, if the user is a new user, a new authentication is automatically created for him. How can I avoid this?
And if the user is already authenticate, how can the user sign in from the client side? Where is the link between the authentication done from the back end with the user that wants to sign in the front end?
This is not how OAuth works. If you use an authentication provider like GitHub, they handle auth flow for you. The only thing that you are left with on the frontend side is an idToken with your identity, basic profile info, and a signature so you can as a user using this token. There's no distinction between sign up/sign in actions.
As you have noticed, Firebase is an extra layer in this flow, it creates an account for a user who signs in for the first time. But there's no user limit or extra payment so I wouldn't bother too much about these extra accounts. You might consider periodical cleanups if you care about the security here.
If you want to actually check if the user exists you have to use firebase-admin e.g. in a Firebase Function before the signInWithPopup is called. But still, unless you want to prevent users from signing up, you can hook your server logic into functions.auth.user().onCreate trigger.
To answer your last question, when the user is already signed in, you'll get the user object in firebase.auth().onAuthStateChanged when a page is loaded. Login state is stored by Firebase.js so once you have called signInWithPopup, you don't need extra steps.
I have a custom Google Sign In button in React (follows branding guidelines) that I need to implement into a web app. The android app already has Google Sign In implemented with the BE and requires the authorization code to be sent to the BE. I seem to have trouble accessing the authorization code (I can get the access token, id, and id_token fairly easily though).
I initialize as such:
static googleInit (googleSignInID) {
gapi.load('auth2', () => {
ThirdPartyLogInApi.auth2 = gapi.auth2.init({
// Retrieve the singleton for the GoogleAuth library and set up the client.
client_id: `${googleSignInID}.apps.googleusercontent.com`,
})
})
}
In my React component, I attach a click handler to a React ref (as I've seen in other stackoverflow problems).
componentDidMount () {
// element, options, success, failure
ThirdPartyLogInApi.auth2.attachClickHandler(
this.button,
{},
this.onGoogleSuccess,
this.setProcessingToFalse
);
}
In the success callback (this.onGoogleSuccess), I am able to call googleUser.getBasicProfile() to get user information and googleUser.getAuthResponse(true) to get id_token and access_token, but I need the authorization code too.
I'm at my wits end with Google's documentation on this - can someone point me in the right direction? I don't want to switch the entire auth flow to use gapi.auth2.authorize, as I do need the user information.
Thanks
EDIT: Is it even possible to access the authorization code? It seems like Google abstracts away that step and just returns the id_token right away. I just need something that looks like:
4/RQFWJLGd3sJQrgxuRaguzJy2yiUV4fAqVGftRSUYuqc
I'm working on an application where only the admin should be able to create users for the system; meaning the user is restricted from creating an account but can login if login credentials were made for him/her.
I'm thinking about using houston:admin to manually create users, but how can I restrict users from creating an account using accounts-ui?
Should I use different packages to achieve this altogether?
You have several ways to prevent users from creating accounts:
throwing an error in the Accounts.onCreateUser() callback (server only):
Accounts.onCreateUser(function(options, user) {
if (/* some logic to figure out if current user is an admin */) {
return user;
}
throw new Meteor.Error("user creation disabled.");
});
This will prevent the account from being created if the current user is not an admin.
configuring Accounts to forbid account creation (both client and server):
Accounts.config({
forbidClientAccountCreation: true
});
which rejects calls to createUser() from the client (but will not prevent user creation using oAuth services).
A combination of both is a likely course of action.
Take a look at the linked documentation for more details.