15

I am using OAuth2 Authentication, and I have a CMS with multiple users, each with their own profiles. It happens that our company has a Google account with access to multiple Analytics accounts. For each user who uses the CMS, I connect to the Google Analytics API using a different username, and each user’s token is saved in a database data store. The problem is, if one user disconnects and revokes his token, none of the other users who use the same Google account will be able to access the Analytics API either, which doesn’t make sense.

enter image description here

EDIT: After further investigation, I found that when the first user authenticates, the token saved in the data store contains a 'refresh_roken' as well as an 'access_token'. However, when other users authenticate (they use the same google account, but different Analytics accounts), their tokens will only contain the 'access_token'. If one of them revokes his token, all will lose their connections.

How can I stop this from happening and have each user receive his own refresh_token?


EDIT 2:

I do store separate rows in the data store, one for each user. Let me clarify - if you look at this diagram, imagine a CMS where one CMS user needs to see statistics from "Liz's Personal Account", and another CMS user needs to see stats from "Liz's Team Account".

Both CMS users are from the same company, and they both use the same google account - "liz@gmail.com". CMS user A connects to the Analytics API using "liz@gmail.com", receives a refresh_token and wants to view statistics for "Liz's Website". Then CMS user B connects to the Analytics API also using "liz@gmail.com", but now he doesn't receive a refresh_token anymore, only an access_token - this is a problem because the user will be asked again to connect after the access_token expires.

What I was normally doing when one user was disconnecting was to delete the token from the data store AND revoke the token, but maybe I shouldn't revoke it, should I? In any case, if in my scenario user A disconnects, this deletes his data store token, which means we won't have the refresh_token stored anymore, as user B didn't have it in the first place.

User accounts diagram: User accounts diagram

DaImTo
  • 72,534
  • 21
  • 122
  • 346
SsjCosty
  • 756
  • 1
  • 7
  • 24

4 Answers4

4

I think you need to check databaseDatastore. If done correctly your database datastore should be storing refreshTokens for each of your users identified by userName in your code.

When a new user authenticates you should be inserting a new row in the database for them.

 // New User we insert it into the database
 string insertString = "INSERT INTO [dbo].[GoogleUser]  ([username],[RefreshToken],[Userid]) " +
                                              " VALUES (@key,@value,'1' )";

Then when you want to access it again you should be selecting it out by again by username

using (SqlCommand command = new SqlCommand("select RefreshToken from GoogleUser where UserName = @username;", myConnection))

If for some reason it doesn't work then you should delete only this one user. it almost sounds like if one authentication fails you are deleting them all.

 // Deletes the users data.                        
 string deleteString = "delete [dbo].[GoogleUser] from " +                                 
                                  " where username = @key";

Without seeing how you have implemented databasedatastore I cant help more, but this is my version of databasedatastore.cs

Update:

If you are only accessing a single Google Analytics account you should be using a Service account.

Update in response to your update

You need to have a look at my database datastore. But seriously you should not have more then one user using the same gmail account.

Step one:

User liz@gmail.com logs into your app authenticates the app, a row should be inserted into your database username liz@gmail.com with a refreshtoken. your databasedatastore should handle saveing it.

when the access token expires assuming that you have created databasedatastore correctly it will use the refreshtoken to automaticly get a new access token for user name liz@gmail.com.

Step 2:

Some other person logs in with liz@gmail.com, database data store checks the database for user name liz@gmail.com and gets the refresh token associated with liz@gmail.com and requests a new access token.

You should not be deleting refresh tokens or users from the database unless for some reason it doesn't work. It may not work if a user went to the Google account and revoked your access. There should be only one username liz@gmail.com and refresh token associated for it.

Service account:

A service account is a type of authentication that doesn't require a user to authenticate it. It works by creating a service account authentication in the developer console then taking the email address and adding it to the Google analytics admin like you would any other user. Now the service account has access to the google analytics account directly just like a user.

Then your application request data from the API without prompting a user for access. You can show Liz , Jim and sue all the data with out asking them to authenticate.

DaImTo
  • 72,534
  • 21
  • 122
  • 346
  • Check my EDIT 2 for clarifications. – SsjCosty Jun 10 '15 at 08:20
  • I think your problem is in databasedatastore, The client library should be able to handle all of this for you. – DaImTo Jun 10 '15 at 08:29
  • a service account still may be an option for you. – DaImTo Jun 10 '15 at 08:42
  • 1
    "But seriously you should not have more then one user using the same gmail account." - I cannot decide this. It is a large company and people have been using the same corporate account for years. It's only now that I upgraded to the v3 API that we're having this issue. Besides this, we have customers using our CMS and they each log in with their own accounts for this feature, which means I cannot lock it in to a service account. – SsjCosty Jun 11 '15 at 11:27
  • Regarding steps 1 & 2: The CMS user will use the liz@gmail.com to log in, but the username we use for saving the tokens is in the form "CMS user ". Is there a way to identify that the user has connected with the liz@gmail.com account? If it were, maybe I could save that. – SsjCosty Jun 11 '15 at 11:28
  • you could add one of the google+ scopes https://developers.google.com/+/web/api/rest/oauth then you can get a login id back or the email address. Point to remember if you have more then one user authenticating with the same email. then after 26 its going to stop working you can only have 26 refreshtokesn outstanding per user. – DaImTo Jun 11 '15 at 11:54
  • Interesting idea. Is it safe to mix together 2 different APIs into one token? I'll try to play with it for a bit, maybe I can get something working. Thanks for the tip. – SsjCosty Jun 11 '15 at 11:56
  • you can mix as many as you like. but you will have to create a different service. plusservice access Google plus api Analyticsservice access analytics api. they are different. – DaImTo Jun 11 '15 at 11:57
  • The drawback with this is that I would have to disconnect all the people who are currently authenticated to the Analytics API (a few hundreds). But if that's the only solution, then I guess I have to. – SsjCosty Jun 11 '15 at 12:24
  • which scope did you authenticate with now? – DaImTo Jun 11 '15 at 12:26
  • https://developers.google.com/analytics/devguides/config/mgmt/v3/mgmtReference/management/accounts/list returns username. this looks like the google login email. – DaImTo Jun 11 '15 at 12:29
  • Weird, I don't get that structure when calling service.Management.Accounts.List(); This is what I get: http://screencast.com/t/Tmyrj6M9jMeG and http://screencast.com/t/vhZPlta3jEZc – SsjCosty Jun 11 '15 at 13:11
  • that's weird Try me should be the same. I must now test this. – DaImTo Jun 11 '15 at 13:13
  • When I try it directly by clicking "Try it now" in your link, I do get the username. The request was https://www.googleapis.com/analytics/v3/management/accounts?max-results=10 However, when calling this same exact request in the Google OAuth 2.0 Playground, I don't get the username field. Nor in my C# code. – SsjCosty Jun 11 '15 at 14:18
  • Ok, if I add the "fields=username" query string, I do get it. I'll try to integrate this in my C# code. – SsjCosty Jun 11 '15 at 14:20
  • This feels a lot like a hack. I also have a less used YouTube feature that uses the same user connection principle as the Analytics API, so I'm not sure how I should handle that. It's unfortunate, because you'd expect stuff like this to be handled by the library, but instead I have to resort to all sorts of API-specific hacks to make it work. Oh well... I'll update you when I manage to get a result. – SsjCosty Jun 11 '15 at 14:25
  • 1
    I wonder why Google doesn't send back the refresh_token all the time. Or at least they could send an ID that would be the same between authentications of the same Google account. – SsjCosty Jun 11 '15 at 14:30
0

You can ask Google to add additional information about the account to the scope to fix this issue. I recommend adding profile since it doesn't require any additional prompts for the user and it provides a unique ID for the Google Account (not the Analytics Account). Using the OAuth2 Ruby gem, your final request might look something like this:

client = OAuth2::Client.new(
  ENV["GOOGLE_CLIENT_ID"],
  ENV["GOOGLE_CLIENT_SECRET"],
  authorize_url: "https://accounts.google.com/o/oauth2/auth",
  token_url: "https://accounts.google.com/o/oauth2/token"
)

# Configure authorization url
client.authorize_url(
  scope: "https://www.googleapis.com/auth/analytics.readonly profile",
  redirect_uri: callback_url,
  access_type: "offline",
  prompt: "select_account"
)

Note the scope has two space-delimited entries, one for read-only access to Google Analytics, and the other is just profile, which is an OpenID Connect standard.

This will result in Google providing an additional attribute called id_token in the get_token response. Worth noting, if you change the scope, you'll get back a refresh token again for users that have already authenticated with the original scope. This is useful if, say, you have a bunch of users already and don't want to make them all un-auth the app in Google. ;)

To get information out of the id_token, check out this page in the Google docs. Ultimately you'll want to use the sub parameter provided since that's unique per-account.

Oh, and one final note: you don't need prompt=select_account, but it's useful for these types of situations where your users might want to authenticate with more than one Google account (i.e., you're not using this for sign-in / authentication).

coreyward
  • 68,091
  • 16
  • 122
  • 142
-1

You describe expected behaviour. If you just mean the user disconnects (doesn't revoke app permissions), try setting access_type to "offline".

There is no distinction between "Analytics account" and "Google account". You authenticate to the analytics api as a Google account.

Additional notes:

they use the same google account, but different Analytics accounts

Analytics uses Google accounts to login. Perhaps you mean different profiles?

if one user disconnects and revokes his token, none of the other users who use the same Google account will be able to access the Analytics API either, which doesn’t make sense

If permissions are revoked from your app for a Google account, your app won't have access anymore. There isn't such thing as an "Analytics account" - you sign into Google Analytics with a Google Account. Once authenticated, your app will have access to all Analytics profiles / properties that the Google Account does.

The first time you pass oauth authentication, you receive both an access and refresh token (as you noticed) for that Google Account. You won't receive another for that same Google Account unless they remove your app in the security center app permissions and then complete oauth again later.

mz3
  • 1,292
  • 11
  • 25
  • The google .net client library is auto set to access_Type offline a refresh token is always returned. Actually your app get an unlimited number of refreshtokesn. But only 26 of them will work so your statement (You won't receive another ) is totally incorrect. – DaImTo Jun 10 '15 at 07:04
  • Actually there is a distinction between Google accounts and Google Analytics accounts. Check out the diagram in this article: https://developers.google.com/analytics/resources/concepts/gaConceptsAccounts – SsjCosty Jun 10 '15 at 07:44
  • You authenticate to a Google Account. Which gives you access to all of the Google Analytics accounts -> web proprites -> profiles (Views) that a user has access to. I can authorize your application as many times as you ask me to and have up to 26 different refresh tokens to your application that will all work. The .net client library will return all 26 refreshtokens it is up to you the user to store them in this case the database. Associated by the by the username in his code – DaImTo Jun 10 '15 at 08:03
  • If the author of the question has chosen to store all of the CMS users Google analytics data in one Google analytics account and is then trying to show different profiles to different users. He should be using a service account and associating the profiles directly to the different users. But we don't have enough information to be sure that is what he has done. – DaImTo Jun 10 '15 at 08:06
  • @DaImTo - Yes, we have a google account which manages several (694) Analytics accounts. Usually, our CMS customers connect themselves with their own Google Account which has access to their own Analytics account, and that is fine, no problems there. In other cases, we have customers who want us to connect them to Analytics, and we do this by using our own Google account which has access to all these multiple Analytics accounts. This is where the problem is. – SsjCosty Jun 10 '15 at 08:26
  • If you have one account and want to grant other users access to it. YOu use a service account. Then you can use the analytics api to access the Analytics account directly without requesting authentication from a user who doesn't infact have access to it. http://www.daimto.com/googleanalytics-authentication-csharp/#Google_Analytics_API_Service_Account_Authentication – DaImTo Jun 10 '15 at 08:35
  • Yes, but not ALL users use this account. – SsjCosty Jun 11 '15 at 11:52
-1

I think you need to get refreshToken again store in different table not user dependent table. You can get refresh token again using :

$client->setAccessType('offline');
$client->setApprovalPrompt('force');

Now use refresh token to authenticate user, and get new access type each time to have user dependent token and store it in session or db. And each time crosscheck before using it whether it is expired or not and if it is expired then get access token using refresh token again.

  $client->refreshToken('stored refresh token');

Hope it will be helpful. Note that this is PHP script syntax, so to have your programming language use https://developers.google.com/api-client-library/

  • you know that's php and he's using C#? Do you really think that it answers the question? the .net client library gets the refresh token automatically unlike the php client library where you have to request it. – DaImTo Jun 10 '15 at 07:00