I am writing an App and am trying to leverage Google for A&A. The app itself relies on access to the users Google Calendar, and so initially I leveraged their updated OAUTH2 library for A&A.
Here is my flow:
User goes to the index.html which has "https://accounts.google.com/gsi/client" script and google.accounts.oauth2.initCodeClient is called with my client_id, scopes, redirect url
<script src="https://accounts.google.com/gsi/client"></script>
<script>
let client;
function initClient() {
client = google.accounts.oauth2.initCodeClient({
client_id: 'xxxxx-xxxx.apps.googleusercontent.com',
scope:
'https://www.googleapis.com/auth/userinfo.profile \
https://www.googleapis.com/auth/userinfo.email \
https://www.googleapis.com/auth/calendar.readonly \
https://www.googleapis.com/auth/calendar.events',
ux_mode: 'redirect',
redirect_uri: 'http://localhost:5000/oauth2callback',
});
}
// Request an access token
function getAuthCode() {
client.requestCode();
}
The user clicks the login button, which kicks off requestCode() and they begin the login flow. They login or select their google account, then besides the unapproved app screen, they get to the consent screen with my requested scopes.
After, they are redirected to my expressjs endpoint and using the "googleapis" library I exchange with id_token for the access and refresh tokens.
...
const { tokens } = await oauth2Client.getToken(req.query.code); //exchange code for tokens
const userInfo = (
await oauth2Client.verifyIdToken({
idToken: tokens.id_token,
audience: config.google.clientID,
})
).payload;
if (!indexBy.email[userInfo.email]) { // check if user exists
const newUser = {
name: userInfo.name,
email: userInfo.email,
o_id: userInfo.sub,
picture: userInfo.picture,
r_token: tokens.refresh_token,
};
...
Ok, all good.... but not quite. The problem is, that next time the user wants to login to the app, they go through the entire flow again, including the consent screen (again).
So, after going through more docs, even looking at examples from google. I was surprised and I noticed that many of those apps used the passport oauth2 plugin :( Something i've done in the past, but was hoping to avoid that with the recently updated Google client and nodejs libraries.
Ok, how to not prompt for consent screen on subsequent logins?
Maybe separate A&A, so first I use "Sign In With Google" for Authentication, then when I get the user info, check if the user is already registered (hence I have already saved the refresh token) and they start the app.
On the other hand, if they are new (not in existing app user collection), after authenticating, I will then call the OAUTH2 authorization redirect, so again they on Googles site, this time to do the scopes api confirmation.
So, first question, is that the best practice with most apps with leverage a Google API via OAuth? To first Authenticate, then possibility Authorize (as needed). Hopefully this will still work ok when things come up with expired/invalid refresh token (fingers crossed the default google library handles that).
When doing the Authorize for consent, can I pass something from the previous Authenticate flow so they don't need to do that again.
Or maybe when doing the Authenticate process (Google Identity Service), there is some flag or param so that if they have already consented, they don't have to do that again on subsequent logins.
Incase I wasn't clear, in a nutshell the question is: should I be doing Authenticate for login, separately from Authorization (oauth2 token). Or should I go right into the Authorization flow, which first Authenticates the user, and can I skip the Authorization consent screens if they've already done that. Or maybe there's another way which is the best practice.
Thanks for your attention.
Background info
Authentication is the act where by a user logs in into a system using their login and password. With authentication we know that the user is behind the machine. For this we use Open id connect, which was built on top of Oauth2. Open id connect returns and id_token which can be used to identify the user, it is often a jwt containing some claims to identify the subject or the user behind the Authentication.
The scope used for open id connect is profile and email. open id connect grants you consent to access a users profile information.
This is an example of the decrypted id token returned by google from a simple call using profile scope only. All this id token is telling you is who the user behind the machine is.
{
"iss": "https://accounts.google.com",
"azp": "4074087181.apps.googleusercontent.com",
"aud": "4074087181.apps.googleusercontent.com",
"sub": "1172004755672775346",
"at_hash": "pYlH4icaIx8PssR32_4qWQ",
"name": "Linda Lawton",
"picture": "https://lh3.googleusercontent.com/a-/AOh14GhroCYJp2P9xeYeYk1npchBPK-zbtTxzNQo0WAHI20=s96-c",
"given_name": "Linda",
"family_name": "Lawton",
"locale": "en",
"iat": 1655219027,
"exp": 1655222627
}
In the same call google also returned an access token. Now my call contained only the scope for profile, due to the fact that its open id connect. This means that I will only have access to the data that the profile scope would grant access to. In this case most of what is behind the Google people api.
Note: The user does not see a consent screen with open id connect, even though they are consenting to profile scope. It is assumed by signing into your account that the system you are logging into would have access to your profile info.
Authorization
Authorization is the process by which a user grants your application authorization to access their private user data. The user is shown a consent screen where they consent to your application accessing data defined by some scopes.
In the case of google calendar api there are serval
https://www.googleapis.com/auth/calendar See, edit, share, and permanently delete all the calendars you can access using Google Calendar
https://www.googleapis.com/auth/calendar.events View and edit events on all your calendars
https://www.googleapis.com/auth/calendar.events.readonly View events on all your calendars
https://www.googleapis.com/auth/calendar.readonly See and download any calendar you can access using your Google Calendar
https://www.googleapis.com/auth/calendar.settings.readonly View your Calendar settings
In this case you are only given an access token this is again Oauth2 it is authorization to access the users calendar data it is not authentication this is not related to login.
Your question
So, first question, is that the best practice with most apps with leverage a Google API via OAuth? To first Authenticate, then possibility Authorize (as needed).
You would do both at the same time.
When you authencation your user make sure to include your google calendar scope then the access token and refresh token returned will grant you access to google calendar.
I am going to assume that you have some kind of user system. When you store the user be sure to store the refresh token that is returned.
As far as Authentication goes i will assume you either have a remember me system which will set a cookie on their machine and remember the user so that you can then get the refresh token from their system the next time they come back.
If they did not chose to select a remember me option then will then have to login every time they visit your site but part of the login will return the "sub": "1172004755672775346", this is the users id on google system so you can use that in your database to match the user when they come back.
Your question is quite complex and will depend upon the type of system you have what it is designed to do as well as what programming language you are using. That being said I hope this very long answer clears things up a bit.
Related
Account Linking provides several ways of linking users to their own accounts such as their Google account or Twitter account.
I chose OAuth in Actions on Google website to do OAuth 2.0 Authorization Code Grant for obtaining access token in exchange for data resources. When I linked a user to a Google account Google Sign-In enabled, the fetching of user information was easy because the data is stored under payload, but OAuth implementation does not seem like it produces user data under payload inside User object.
So should I make an API call to the third party to fetch the user data and store that personal data to a database in this case? I wondered if there is a way that Google prepares on behalf of developers. If not, then greeting a user who visits my app again by saying 'Hello, {person name}' gets tedious...
You have two options with Account Linking.
In the case you describe, you're providing an OAuth endpoint and getting an auth token from the Assistant, and you are responsible for taking this token and using it to determine who the user is so you can get whatever you know about him. The token is one that you issue and control, so presumably you have that info in your database already. If you are reusing a token from another service, that service should be able to tell you who they are.
It sounds like you're using using a Google Sign In, however, in which case it is easier to use Google Sign In for Assistant. Once the user has signed into your service (either through an app or webapp) and granted permission to your service, then they will also be able to gain access through the Assistant. You will get an id token which can be decoded to get profile information about the user including their Google ID and name.
I'm having a problem with the OAuth Implicit flow for the Google Assistant.
I managed to set up a OAuth server and got it to work. Here's the flow:
The user Is redirected to my endpoint, authenticates with a Google account, and gets send back to the Assistant with an Acces Token and result code=SUCCES.
In my fullfilment I get the users email address by doing a https request to: https://www.googleapis.com/plus/v1/people/me?access_token=access_token.
I then find the matching user in my database and add the acces token to the database for this user.
The next time the user logs in I check the acces token and greet the user by their name.
Now the problem is that this is the Implict flow which according to the documentation should have an access token that never expires:
Note: Google requires that access tokens issued using the implicit
flow never expire, so you don't need to record the grant time of an
access token, as you would with other OAuth 2.0 flows.
But the Assistant forces me to re-authenticate every hour, meaning the access token did expire.
My question is: Is this flow correct or am I missing something? Is there something I've done wrong in my OAuth endpoint?
I based my endpoint on https://developers.google.com/identity/protocols/OAuth2UserAgent.
<html>
<head>
<script src="https://apis.google.com/js/platform.js" async defer></script>
<meta name="google-signin-client_id" content="CLIENT_ID">
</head>
<body>
<script>
var YOUR_CLIENT_ID = 'CLIENT_ID';
function oauth2SignIn() {
// Google's OAuth 2.0 endpoint for requesting an access token
var oauth2Endpoint = 'https://accounts.google.com/o/oauth2/v2/auth';
// Create element to open OAuth 2.0 endpoint in new window.
var form = document.createElement('form');
form.setAttribute('method', 'GET'); // Send as a GET request.
form.setAttribute('action', oauth2Endpoint);
//Get the state and redirect_uri parameters from the request
var searchParams = new URLSearchParams(window.location.search);
var state = searchParams.get("state");
var redirect_uri = searchParams.get("redirect_uri");
//var client_id = searchParams.get("client_id");
// Parameters to pass to OAuth 2.0 endpoint.
var params = {
'client_id': YOUR_CLIENT_ID,
'redirect_uri': redirect_uri,
'scope': 'email',
'state': state,
'response_type': 'token',
'include_granted_scopes': 'true'
};
// Add form parameters as hidden input values.
for (var p in params) {
var input = document.createElement('input');
input.setAttribute('type', 'hidden');
input.setAttribute('name', p);
input.setAttribute('value', params[p]);
form.appendChild(input);
}
// Add form to page and submit it to open the OAuth 2.0 endpoint.
document.body.appendChild(form);
form.submit();
}
oauth2SignIn();
</script>
</body>
</html>
It sounds like what you are doing is having the user log into your page, and using this to get an auth token from a Google service. You're then turning this around and passing this back to the Assistant and calling this the Identity Flow.
While clever - this isn't the Identity Flow.
This is you using the Auth Code Flow to authenticate the user with Google, and then returning this token to Google and pretending this is an Identity Flow token. However, since you're using the Auth Code Flow, the auth tokens that you get back expire after an hour. (You can check out the lifetime in the information you get back from Google.)
If you are trying to do Account Linking and not manage anything yourself, you need to actually implement an OAuth server that proxies the Auth Code Flow requests from the Assistant to Google and the replies from Google back to the Assistant. While doable, this may be in violation of their policy, and isn't generally advised anyway.
Update to address some questions/issues in your comment.
using the Google Auth endpoints doesn't store the session either, so you'd still have to re-authenticate every hour
Since the Google Auth endpoints use the Auth Code Flow, you can use the offline mode to request a refresh token. Then, when an auth token expires, you can use the refresh token to get a new auth token. So you still have a long-term authorization for access and can get the short-term token to do the work you need.
Trying to shoehorn this into the Identity Flow, however, doesn't work. (And would be a really bad idea, even if it did.)
Can you provide some clarification on how to create an endpoint for the implicit flow?
Beyond the step-by-step description of what your OAuth server code can do in the Assistant documentation, I'm not sure what clarification you need. Your OAuth server fundamentally just needs to:
Be able to have a user:
Connect to an HTTPS URL
Authenticate themselves
Authorize the Assistant to contact your service on their behalf
Return a code by redirecting the user to Google's URL with a code in the parameter
And the Action webhook needs to be able to:
Accept this code as part of the request from the Assistant and
Figure out who the user is from this code. (ie - map the code to a user account in your system.)
There are a variety of ways you can do all of that. The OAuth server and Action could be on the same server or separate, but they at least need to have some agreement about what that code is and how that maps to your user accounts.
If your primary need is to access Google APIs on behalf of your user - then the user account that you have will likely store the OAuth tokens that you use to access Google's server. But you should logically think of that as separate from the code that the Assistant uses to access your server.
(As an aside - those steps are for the Identity Flow. The Auth Code Flow has a few more steps, but the fundamentals are similar. Especially on the Action side.)
I'm making a website for a football club. they have one google calender (and just one google account). on the website I'd like to have a list of upcoming events. So I've to access to Google Calendar via Google API. For Authorisation I've to use OAuth2. Is it possible to login with OAuth2 automatically so that the website visitors don't have login always via redirect (to google login site)? For example I make a the login via Java, instead of a user login? Because it's no so comfortable if every user of the website have to login, for just viewing the club calendar.
Not sure it is possible,
Important note concerning your design: if you login automatically to the club's account, it means that everyone that uses this website is logged in to Google Calendar on behalf of the club's user name. hence, everyone can CHANGE the calendar, delete events, etc.
Are you sure you want this to happen?
(you can set the login params to "read-only", but even then, it means that the club shows ALL his calendar to everyone. there is no privacy...)
I suggest that every user logins with his own creds, and the club's calendar can invite all registered users to his events....
Of course, you can do it, and even without giving the access to the visitor if you're doing this on the server side.
You need to do a initial step by hand.
After this step you get a refresh token, with this token you can regenerate the access token which you need to access to the calendar API, for instance to get the upcoming events.
You need to regenerate the access token if the previous access token is expired. In this case you also get an new refresh token. You need to save it, into a database or JSON file.
To get a refresh token you need to pass this options on authorization:
access_type: "offline"
approval_prompt: "force"
Without these options you only get an access token, no refresh token.
Here is the documentation: https://developers.google.com/accounts/docs/OAuth2WebServer#formingtheurl
And here a kind of tutorial: http://www.tqis.com/eloquency/googlecalendar.htm
I am attempting to create a login system for my website that permits both authentication via Google's API and access to any of the OAuth-supported Google Data APIs while ideally only showing the user one prompt ever, no matter if he's creating an account or logging into his existing one. I want to minimize the number of times he's asked for approval.
I am aware that Google provides Hybrid OpenID/OAuth for this purpose, but the issue is that every time I add OAuth extensions to my OpenID request, it never remembers the user's approval for that request. Is there any way for the approval to be remembered when I am doing Hybrid OpenID/OAuth? If I just do OpenID without OAuth extensions, everything is remembered just fine and it doesn't keep bugging the user with the prompt.
Here are the pertinent extensions I'm sending in addition to my OpenID request, which result in me getting an OAuth request token (good) but cause the approval to never get remembered (bad).
PHP syntax:
$args["openid.ns.ext2"] = 'http://specs.openid.net/extensions/oauth/1.0';
$args["openid.ext2.consumer"] = 'www.MYSITE.com';
$args["openid.ext2.scope"] = 'http://www-opensocial.googleusercontent.com/api/people/';
$args["openid.mode"] = 'checkid_setup';
$args["openid.realm"] = 'http://www.MYSITE.com/';
Is it normal for Hybrid OpenID/OAuth to act this way (not remembering the last OAuth authorization)? What is the best way to get around this? I have thought of storing cookies on the user's computer to link to somewhere in my database so I could use the last access token again, etc... (the issue here being I don't know whose token to look up unless I know who the user is...a circular problem). And doing an OpenID-only request to get his user ID to see if he has an account in order to look up his access token, followed by an OpenID+OAuth request (if an access token for him isn't stored) would result in two prompts, which really wouldn't help.
It also seems like Hybrid only supports OAuth 1.0, which I think is fine until 2015, so it's not an issue right now for me. I am assuming they will support OAuth 2.0 in the future.
Is checkid_immediate relevant to this in any way? I'm just not sure how to use this to accomplish what I want.
I would suggest using OAuth 2.0. This supports getting both identity and access to APIs -- so accomplishes the same end goal, but is much easier than OAuth 1 Hybrid.
Take a look here:
https://developers.google.com/accounts/docs/OAuth2Login
The scopes you're trying to access are included in the URL (see "Forming the URL"). The referenced doc lists the scopes required for getting identity/profile information. You can simply add additional scopes to the string, comma-delimited in order to request access to other APIs. The resulting access token will access both the APIs and identity information (via the UserInfo API endpoint mentioned).
That said, what you're trying to do with OpenID 2.0/OAuth 1 hybrid should work-- and the user should see a checkbox for "remembering" the authorization. If you really want to debug this further, it'd be helpful to have a webpage you can point to which kicks off this authentication+authorization flow so we can see what's happening.
I figured out that checkid_immediate (and x-has-session, not sure if that's needed or even working) is allowing me to determine whether or not a user is logged in without prompting him, and if he is, it is giving me a claimed_id by which I can identify the user. That's exactly what I needed. The original question is solved, but I do want to figure out how to use identify with OAuth 2.0 because I have already implemented that.
Furthermore, I've noticed that when using OpenID/OAuth that the user still gets asked to authorize OAuth even after he's authorized OpenID. I can't see the advantage to the hybrid approach from the user's perspective.
If the user is logged out of Google, that's a total of three prompts just to sign up for my website and grab his name and profile image.
If anyone wondered, here are the steps necessary to get Hybrid OpenID/OAuth completely working (an overview). I was confused thoroughly throughout this process, so I hope this helps someone.
Do normal OpenID handshake and add on AX extensions for OAuth 1.0.
Use 'checkid_immediate' to permit probing for an active Google session without prompting the user. Use *claimed_id* as a unique identifier to link the user to your database.
If 'setup_needed' is returned, use 'checkid_setup' so the user is prompted and verified before continuing.
This leaves you with two possibilities. *checkid_immediate* returning immediately giving you a claimed_id, or a claimed_id coming through after *checkid_setup* (basically sign-up) succeeds.
Hybrid OpenID/OAuth 1.0 will give you an authorized request token.
Use the authorized request token to get an access token (you only need to call OAuthGetAccessToken)
Use that OAuth 1.0 access token to do whatever you want.
I was successful in using OAuthGetAccessToken to get an access token from the authorized request token my Hybrid OAuth dance, omitting the 'oauth_verifier' parameter (irrelevant to Hybrid).
I was successful in using OAuthGetAccessToken to get an access token after my Hybrid OAuth dance, omitting the 'oauth_verifier' parameter (irrelevant to Hybrid).
In a PHP/Zend environment:
$config = array(
'accessTokenUrl' => 'https://www.google.com/accounts/OAuthGetAccessToken',
'consumerKey' => $consumer_key,
'consumerSecret' => $consumer_secret
);
$consumer = new Zend_Oauth_Consumer($config);
$zendRToken = new Zend_Oauth_Token_Request(); // create class from request token we already have
$zendRToken->setToken($requestToken);
try{
$accessToken = $consumer->getAccessToken(array(
'oauth_token' => $requestToken,
// 'oauth_verifier' => '', // unneeded for Hybrid
'oauth_timestamp' => time(),
'oauth_nonce' => md5(microtime() . mt_rand()),
'oauth_version' => '1.0'
), $zendRToken);
} catch (Zend_Oauth_Exception $e){
echo $e->getMessage() . PHP_EOL;
exit;
}
echo "OAuth Token: {$accessToken->getToken()}" . PHP_EOL;
echo "OAuth Secret: {$accessToken->getTokenSecret()}" . PHP_EOL;
The long QUEST to get an access token
app gets a request token from twitter
user clicks a button on the ap
app opens a twitter page, user types username/password
twitter gives user a PIN
user use this PIN in the application
GREAT, finally app exchange the request token for an access token
Questions:
This token belongs to who? I mean, i don't even know his username!
The user will have to repeat the steps above every time?
I can't figure out how to fit this into a real application.
Thanks in advance.
Once you have the token, you can call the account/verify_credentials endpoint, which will return you the User profile that the token belongs to (also confirming everything works as intended).
You can then associate the twitter account and the tokens and store the token locally. (They are good forever unless the user later revokes access for your app). How you manage stored tokens depends on your app platform, your needs, etc.