Overview
With granular permissions, consumers get more fine-grained control over what account data they choose to share with each app. They benefit both users and developers by providing greater control, transparency, and security. This guide will help you to understand the necessary changes and steps to successfully update your applications to handle granular permissions.
What is granular permission?
Imagine you develop a productivity app that requests both email and calendar scopes. Your users may want to use your application only for Google Calendar, but not Gmail. With granular OAuth permissions, users can choose to only grant Google Calendar permission but not Gmail. By letting users grant access to specific data, this minimizes data exposure, bolsters trust, and empowers users with privacy-first control over their digital life. It is important to design your application to handle such scenarios.
When more than one non-Sign-In scope is requested
Sign-In and non-Sign-In scopes
For applications that request both Sign-In and non-Sign-In scopes, users first see the consent
page for Sign-In scopes
(email
, profile
, and openid
). After users consent to
share their basic identity information (name, email address, and profile photo), users will see
a granular permission consent screen for the non-Sign-In scopes. In this case, the application
must check what scopes are granted by the users and can't assume users grant all requested
scopes. In the following example, the web application requests all three Sign-In scopes and a
Google drive non-Sign-In scope. After users consent to the Sign-In scopes, users will see the
granular permissions consent screen for the Google Drive permission:
More than one non-Sign-In scope
A granular permission consent screen would be displayed to users when applications request more than one non-Sign-In scope. Users can select which permissions they want to approve to share with the application. The following is an example granular permission consent screen requesting access to user's Gmail messages and Google Calendar data:
For applications that request only Sign-In
scopes (email
, profile
, and openid
), the granular
permissions consent screen is not applicable. Users either approve or deny the entire sign-in
request. In the other words, if applications only request Sign-In scopes (one, two, or all
three), the granular permission consent screen is not applicable.
For applications that request only one non-Sign-In scope, the granular permission consent screen is not applicable. In the other words, users either approve or deny the entire request, and there is no checkbox in the consent screen. The following table summarizes when granular permissions consent screen is displayed.
Number of Sign-In scopes | Number of Non-Sign-In scopes | Granular permissions consent screen |
---|---|---|
1-3 | 0 | Not applicable |
1-3 | 1+ | Applicable |
0 | 1 | Not applicable |
0 | 2+ | Applicable |
Determine if your applications are affected
Conduct a thorough review of all sections within your application where Google OAuth 2.0 authorization endpoints are utilized for permission requests. Pay attention to those that request multiple scopes as they activate granular permission consent screens presented to users. In such instances, make sure your code can handle the case where users only authorize some of the scopes.
How to determine if your application is using multiple scopes
Inspect your app code or the outgoing network call to determine if the Google OAuth 2.0 authorization requests your app makes will cause the granular permissions consent screen to be shown.
Inspect your application code
Review the sections of your application code where you are making calls to the Google OAuth authorization endpoints to request permission from users. If you use one of the Google API Client Libraries, you can often find what scopes your application requests in the client initialization steps. Some examples are shown in the following section. You should refer to the documentation of the SDKs your application uses to handle Google OAuth 2.0 to determine if your application is affected, using the guidance shown in the following examples as a reference.
Google Identity Services
The following Google Identity Services
JavaScript library code snippet initializes the TokenClient
with multiple
non-Sign-In scopes. The granular permission consent screen would be displayed when the web
app requests authorization from users.
const client = google.accounts.oauth2.initTokenClient({ client_id: 'YOUR_CLIENT_ID', scope: 'https://www.googleapis.com/auth/calendar.readonly \ https://www.googleapis.com/auth/contacts.readonly', callback: (response) => { ... }, });
Python
The following code snippet uses the google-auth-oauthlib.flow
module to
construct the authorization request; The scope
parameter includes two
non-Sign-In scopes. The granular permission consent screen would be displayed when the web
application requests authorization from users.
import google.oauth2.credentials import google_auth_oauthlib.flow # Use the client_secret.json file to identify the application requesting # authorization. The client ID (from that file) and access scopes are required. flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( 'client_secret.json', scopes=['https://www.googleapis.com/auth/calendar.readonly', 'https://www.googleapis.com/auth/contacts.readonly'])
Node.js
The code snippet following creates a google.auth.OAuth2
object, which defines the
parameters in the authorization request whose scope
parameter includes two
non-Sign-In scopes. The granular permission consent screen would display when the web app
requests authorization from users.
const {google} = require('googleapis'); /** * To use OAuth2 authentication, we need access to a CLIENT_ID, CLIENT_SECRET, AND REDIRECT_URI * from the client_secret.json file. To get these credentials for your application, visit * https://console.cloud.google.com/apis/credentials. */ const oauth2Client = new google.auth.OAuth2( YOUR_CLIENT_ID, YOUR_CLIENT_SECRET, YOUR_REDIRECT_URL ); // Access scopes for read-only Calendar and Contacts. const scopes = [ 'https://www.googleapis.com/auth/calendar.readonly', 'https://www.googleapis.com/auth/contacts.readonly'] ]; // Generate a url that asks permissions const authorizationUrl = oauth2Client.generateAuthUrl({ // 'online' (default) or 'offline' (gets refresh_token) access_type: 'offline', /** Pass in the scopes array defined above. * Alternatively, if only one scope is needed, you can pass a scope URL as a string */ scope: scopes, // Enable incremental authorization. Recommended as best practices. include_granted_scopes: true });
Inspect outgoing network call
- Web application - inspect network activity on Chrome
- Android - inspect network traffic with the Network Inspector
-
Chrome apps
- Navigate to the Chrome extensions page
- Check the Developer mode checkbox at the top right corner of the extension page
- Select the extension you want to monitor
- Click on the background page link in the Inspect views section of the extension page
- A Developer Tools popup will open up where you can monitor the network traffic in the Network tab
- iOS - Analyzing HTTP traffic with Instruments
- Universal Windows Platform (UWP) - Inspect network traffic in Visual Studio
- Desktop apps - use a network capture tool available for the operating system the app was developed for
While inspecting network calls, look for requests sent to the Google OAuth
authorization endpoints and examine the scope
parameter.
These values cause the granular permissions consent screen to be shown.
The
scope
parameter contains Sign-In scopes and non-Sign-In scopes.A following sample request contains all three Sign-In scopes and one non-Sign-In scope to view the metadata of user's Google Drive files:
https://accounts.google.com/o/oauth2/v2/auth? access_type=offline& scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile%20openid%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.metadata.readonly& include_granted_scopes=true& response_type=code& redirect_uri=YOUR_REDIRECT_URL& client_id=YOUR_CLIENT_ID
The
scope
parameter contains more than one non-Sign-In scope.A following sample request contains two non-Sign-In scopes to view the user's Google Drive metadata and manage specific Google Drive files:
https://accounts.google.com/o/oauth2/v2/auth? access_type=offline& scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.metadata.readonly%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.file& include_granted_scopes=true& response_type=code& redirect_uri=YOUR_REDIRECT_URL& client_id=YOUR_CLIENT_ID
Best practices to handle granular permissions
If you determine that your application needs to be updated to handle granular permissions, you should make necessary updates to your code to properly handle consent for multiple scopes. All applications should comply with the following best practices:
- Review the Google API Services: User Data Policy and make sure you comply with them.
- Request specific scopes that are needed for a task. You must comply with the Google OAuth 2.0 policy that you only request scopes that you need. You should avoid asking for multiple scopes at sign-in, unless it is essential for the core functionality of your app. Bundling several scopes together, especially for the first-time users unfamiliar with your application's features, can make it challenging for them to comprehend the need for these permissions. This may raise alarms and deter users from further engaging with your application.
- Provide justification to users before asking the authorization request. Clearly explain why your application needs the requested permission, what you'll do with the user's data, and how the user will benefit from approving the request. Our research indicates that these explanations increase user trust and engagement.
- Use incremental authorization whenever your application requests scopes to avoid having to manage multiple access tokens.
- Check which scopes users granted. When requesting multiple scopes at once, users may not grant all scopes your app requests. Your app should always check which scopes were granted by the user and handle any denial of scopes by disabling relevant features. Follow the Google OAuth 2.0 policies on handling consent for multiple scopes and only prompt the user for consent again once they have clearly indicated an intent to use the specific feature that requires the scope.
Update your application to handle granular permissions
Android applications
You should consult the documentation of SDKs you use to interact with Google OAuth 2.0 and update your application to handle granular permissions based on best practices.
If you use
auth.api.signin
SDK from Play Services to interact with Google OAuth 2.0, you can use
requestPermissions
function to request the smallest set of scopes needed,
and the
hasPermissions
function to check which scopes the user granted when
requesting granular permissions.
Chrome extension applications
You should use Chrome Identity API to work with Google OAuth 2.0 based on best practices.
The following example shows how to properly handle granular permissions.
manifest.json
The example manifest file declares two non-Sign-In scopes for the Chrome extension application.
{ "name": "Example Chrome extension application", ... "permissions": [ "identity" ], "oauth2" : { "client_id": "YOUR_CLIENT_ID", "scopes":["https://www.googleapis.com/auth/calendar.readonly", "https://www.googleapis.com/auth/contacts.readonly"] } }
Incorrect Approach
All or nothing
Users click the button to initiate the authorization process. The code snippet assumes
users are presented with an "all-or-nothing" consent screen for the two scopes specified
in manifest.json
file. It neglects to check which scopes users granted.
oauth.js
... document.querySelector('button').addEventListener('click', function () { chrome.identity.getAuthToken({ interactive: true }, function (token) { if (token === undefined) { // User didn't authorize both scopes. // Updating the UX and application accordingly ... } else { // User authorized both or one of the scopes. // It neglects to check which scopes users granted and assumes users granted all scopes. // Calling the APIs, etc. ... } }); });
Correct Approach
Smallest scopes
Select the smallest set of scopes needed
Application should only request the smallest set of scopes needed. It is recommended that your application requests one scope at a time when it is needed to complete a task.
In this example, it is assumed that both scopes declared in the manifest.json
file are the smallest set of scopes needed. The oauth.js
file uses Chrome
Identity API to initiate the authorization process with Google. You should opt in to
enable granular permissions, so users have greater control granting permissions to your
application. Your application should properly handle the response from users by checking
which scopes users authorize.
oauth.js
... document.querySelector('button').addEventListener('click', function () { chrome.identity.getAuthToken({ interactive: true, enableGranularPermissions: true }, function (token, grantedScopes) { if (token === undefined) { // User didn't authorize any scope. // Updating the UX and application accordingly ... } else { // User authorized the request. Now, check which scopes were granted. if (grantedScopes.includes('https://www.googleapis.com/auth/calendar.readonly')) { // User authorized Calendar read permission. // Calling the APIs, etc. ... } else { // User didn't authorize Calendar read permission. // Update UX and application accordingly ... } if (grantedScopes.includes('https://www.googleapis.com/auth/contacts.readonly')) { // User authorized Contacts read permission. // Calling the APIs, etc. ... } else { // User didn't authorize Contacts read permission. // Update UX and application accordingly ... } } }); });
iOS, iPadOS, and macOS applications
You should consult the documentation of SDKs you use to interact with Google OAuth 2.0 and update your application to handle granular permissions based on best practices.
If you use the Google Sign-In for iOS and macOS library to interact with Google OAuth 2.0, you should review the documentation on handling granular permissions.
Web applications
You should consult the documentation of SDKs you use to interact with Google OAuth 2.0 and update your application to handle granular permissions based on best practices.
Server-side (offline) access
- Stand up a server and define a publicly accessible endpoint to receive the authorization code.
- Configure the redirect URI of your public endpoint in the of the Google Cloud console.
The following code snippet shows a NodeJS example requests two non-Sign-In scopes. Users will see the granular permission consent screen.
Incorrect Approach
All or nothing
Users are redirected to authorization URL. The code snippet assumes users are presented
with an "all-or-nothing" consent screen for the two scopes specified in
the scopes
arrary. It neglects to check which scopes users granted.
main.js
... const oauth2Client = new google.auth.OAuth2( YOUR_CLIENT_ID, YOUR_CLIENT_SECRET, YOUR_REDIRECT_URL ); // Access scopes for two non-Sign-In scopes - Google Calendar and Contacts const scopes = [ 'https://www.googleapis.com/auth/contacts.readonly', 'https://www.googleapis.com/auth/calendar.readonly' ]; // Generate a url that asks permissions for the Google Calendar and Contacts scopes const authorizationUrl = oauth2Client.generateAuthUrl({ // 'online' (default) or 'offline' (gets refresh_token) access_type: 'offline', // Pass in the scopes array defined above scope: scopes, // Enable incremental authorization. Recommended as best practices. include_granted_scopes: true }); async function main() { const server = http.createServer(async function (req, res) { // Example on redirecting user to Google OAuth 2.0 server. if (req.url == '/') { res.writeHead(301, { "Location": authorizationUrl }); } // Receive the callback from Google OAuth 2.0 server. if (req.url.startsWith('/oauth2callback')) { // Handle the Google OAuth 2.0 server response let q = url.parse(req.url, true).query; if (q.error) { // User didn't authorize both scopes. // Updating the UX and application accordingly ... } else { // User authorized both or one of the scopes. // It neglects to check which scopes users granted and assumes users granted all scopes. // Get access and refresh tokens (if access_type is offline) let { tokens } = await oauth2Client.getToken(q.code); // Calling the APIs, etc. ... } } res.end(); }).listen(80); }
Correct Approach
Smallest scope
Select the smallest set of scopes needed
Application should only request the smallest set of scopes needed. It is recommended that your application requests one scope at a time when it is needed to complete a task. Whenever your application requests scopes, it should use incremental authorization to avoid having to manage multiple access tokens.
If your application must request multiple non-Sign-In scopes, you should always use incremental authorization when requesting and check which scopes users granted.
In this example, it is assumed that both scopes stated are required for the app to properly function. You should opt in to enable granular permissions, so users have greater control granting permissions to your application. Your application should properly handle the response from users by checking which scopes they have authorized.
main.js
... const oauth2Client = new google.auth.OAuth2( YOUR_CLIENT_ID, YOUR_CLIENT_SECRET, YOUR_REDIRECT_URL ); // Access scopes for two non-Sign-In scopes - Google Calendar and Contacts const scopes = [ 'https://www.googleapis.com/auth/contacts.readonly', 'https://www.googleapis.com/auth/calendar.readonly' ]; // Generate a url that asks permissions for the Google Calendar and Contacts scopes const authorizationUrl = oauth2Client.generateAuthUrl({ // 'online' (default) or 'offline' (gets refresh_token) access_type: 'offline', // Pass in the scopes array defined above scope: scopes, // Enable incremental authorization. Recommended as best practices. include_granted_scopes: true, // Set to true to enable more granular permissions for Google OAuth 2.0 client IDs created before 2019. // No effect for newer Google OAuth 2.0 client IDs, since more granular permissions is always enabled for them. enable_granular_consent: true }); async function main() { const server = http.createServer(async function (req, res) { // Redirect users to Google OAuth 2.0 server. if (req.url == '/') { res.writeHead(301, { "Location": authorizationUrl }); } // Receive the callback from Google OAuth 2.0 server. if (req.url.startsWith('/oauth2callback')) { // Handle the Google OAuth 2.0 server response let q = url.parse(req.url, true).query; if (q.error) { // User didn't authorize both scopes. // Updating the UX and application accordingly ... } else { // Get access and refresh tokens (if access_type is offline) let { tokens } = await oauth2Client.getToken(q.code); oauth2Client.setCredentials(tokens); // User authorized the request. Now, check which scopes were granted. if (tokens.scope.includes('https://www.googleapis.com/auth/calendar.readonly')) { // User authorized Calendar read permission. // Calling the APIs, etc. ... } else { // User didn't authorize Calendar read permission. // Calling the APIs, etc. ... } // Check which scopes user granted the permission to application if (tokens.scope.includes('https://www.googleapis.com/auth/contacts.readonly')) { // User authorized Contacts read permission. // Calling the APIs, etc. ... } else { // User didn't authorize Contacts read permission. // Update UX and application accordingly ... } } } res.end(); }).listen(80); }
Review the server-side web app guide on how to access Google APIs from server-based applications.
Client-side only access
- For applications that use Google Identity Services JavaScript library to interact with Google OAuth 2.0, you should review this documentation on handling granular permissions.
- For applications that directly make calls using JavaScript to Google OAuth 2.0 authorization endpoints, you should review this documentation on handling granular permissions.
Test your updated application on handling granular permissions
- Outline all the cases that users can respond to permission requests and the expected behavior from your application. For example, if the user only authorizes two out of three requested scopes, your application should behave accordingly.
-
Test your application with granular permission enabled. There are two ways to enable
granular permissions:
- Check the OAuth 2.0 consent screens of your application to see if granular permissions are already enabled for your application. You can also create a new Web, Android, or iOS Google OAuth 2.0 client ID through the Google Cloud console for testing purposes as granular permission is always enabled for them.
-
Set the parameter
enable_granular_consent
totrue
when calling the Google OAuth authorization endpoints. Some SDKs have explicit support for this parameter. For others, check the documentation to see how you can add this parameter and its value manually. If your implementation doesn't support adding the parameter, you can create a new Web, Android, or iOS Google OAuth 2.0 client ID through the Google Cloud console for testing purposes only as stated in preceding point.
- When testing your updated application, use a personal Google Account (@gmail.com) instead of a Workspace account. This is because Workspace Enterprise apps with domain-wide delegation of authority or marked as Trusted are not affected by the changes to granular permissions at this time. Therefore, testing with a Workspace account from your organization might not show the new granular consent screen as intended.