-->

Google API Oauth2: Only one refresh token for all

2019-06-15 01:27发布

问题:

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.

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:

回答1:

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.



回答2:

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.



回答3:

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/



回答4:

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).