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 #inspect-your-application-codegranular
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
The method for inspecting network calls will vary depending on your
application client type.
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
The server-side (offline) access mode requires you to do the following:
-
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
Credentials page
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
to
true
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.