错误 403:“请求的身份验证范围不足”,即使相关范围已激活
Posted
技术标签:
【中文标题】错误 403:“请求的身份验证范围不足”,即使相关范围已激活【英文标题】:Error 403: 'Request had insufficient authentication scopes' even though the relevant scopes are activated 【发布时间】:2021-10-03 07:45:31 【问题描述】:我正在尝试构建一个可以从 Gmail 帐户发送电子邮件的基本脚本,我的代码主要来自:https://ui.ads.microsoft.com/campaign/vnext/accounts/overview/expert?cid=19013943&uid=93024518
我已经尝试了我发现的常见修复,例如启用GMAIL API,授权相应的调用,目前授权的是:
.../auth/gmail.send .../auth/docs .../auth/驱动器 .../auth/drive.scripts .../auth/电子表格 .../auth/gmail.modify .../auth/gmail.compose https://mail.google.com/function main()
var MailApp = new Bing();
const notificationEmail = '**my-email**';
var email = [
`To: $notificationEmail`,
'Subject: Google Services script',
'',
`You script ran successfully ✓`
].join('\n');
MailApp.sendEmail(email);
class Bing
constructor()
this.credentials =
accessToken: '',
clientId: '**my-client-id**',
clientSecret: '**my-client-secret**',
refreshToken: '**my-refresh-token**'
;
this.gmailApi = GoogleApis.createGmailService(this.credentials);
sendEmail(email)
// ^ Basic Copy+Paste from tutorial mentioned above ^
var GoogleApis;
(function (GoogleApis)
function createGmailService(credentials)
return createService("https://www.googleapis.com/discovery/v1/apis/gmail/v1/rest", credentials);
GoogleApis.createGmailService = createGmailService;
// Creation logic based on https://developers.google.com/discovery/v1/using#usage-simple
function createService(url, credentials)
var content = UrlFetchApp.fetch(url).getContentText();
var discovery = JSON.parse(content);
var baseUrl = discovery['rootUrl'] + discovery['servicePath'];
var accessToken = getAccessToken(credentials);
var service = build(discovery, , baseUrl, accessToken);
return service;
function createNewMethod(method, baseUrl, accessToken)
return (urlParams, body) =>
var urlPath = method.path;
var queryArguments = [];
for (var name in urlParams)
var paramConfg = method.parameters[name];
if (!paramConfg)
throw `Unexpected url parameter $name`;
switch (paramConfg.location)
case 'path':
urlPath = urlPath.replace('' + name + '', urlParams[name]);
break;
case 'query':
queryArguments.push(`$name=$urlParams[name]`);
break;
default:
throw `Unknown location $paramConfg.location for url parameter $name`;
var url = baseUrl + urlPath;
if (queryArguments.length > 0)
url += '?' + queryArguments.join('&');
var httpResponse = UrlFetchApp.fetch(url, contentType: 'application/json', method: method.httpMethod, payload: JSON.stringify(body), headers: Authorization: `Bearer $accessToken` , muteHttpExceptions: true );
var responseContent = httpResponse.getContentText();
var responseCode = httpResponse.getResponseCode();
var parsedResult;
try
parsedResult = JSON.parse(responseContent);
catch (e)
parsedResult = false;
var response = new Response(parsedResult, responseContent, responseCode);
if (responseCode >= 200 && responseCode <= 299)
return response;
throw response;
function Response(result, body, status)
this.result = result;
this.body = body;
this.status = status;
Response.prototype.toString = function ()
return this.body;
function build(discovery, collection, baseUrl, accessToken)
for (var name in discovery.resources)
var resource = discovery.resources[name];
collection[name] = build(resource, , baseUrl, accessToken);
for (var name in discovery.methods)
var method = discovery.methods[name];
collection[name] = createNewMethod(method, baseUrl, accessToken);
return collection;
function getAccessToken(credentials)
if (credentials.accessToken)
return credentials.accessToken;
var tokenResponse = UrlFetchApp.fetch('https://www.googleapis.com/oauth2/v4/token', method: 'post', contentType: 'application/x-www-form-urlencoded', muteHttpExceptions: true, payload: client_id: credentials.clientId, client_secret: credentials.clientSecret, refresh_token: credentials.refreshToken, grant_type: 'refresh_token' );
var responseCode = tokenResponse.getResponseCode();
var responseText = tokenResponse.getContentText();
if (responseCode >= 200 && responseCode <= 299)
var accessToken = JSON.parse(responseText)['access_token'];
return accessToken;
throw responseText;
)(GoogleApis || (GoogleApis = ));
class Base64
static encode(input)
const keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
let output = "";
let chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
input = this.utf8Encode(input);
while (i < input.length)
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2))
enc3 = enc4 = 64;
else if (isNaN(chr3))
enc4 = 64;
output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4);
return output.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
static utf8Encode(input)
input = input.replace(/\r\n/g, "\n");
var utftext = "";
for (var n = 0; n < input.length; n++)
var c = input.charCodeAt(n);
if (c < 128)
utftext += String.fromCharCode(c);
else if ((c > 127) && (c < 2048))
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
else
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
return utftext;
我使用本教程中的选项 2 从谷歌获得了 OAuth2:https://docs.microsoft.com/en-us/advertising/scripts/examples/authenticating-with-google-services
范围已正确启用,即 .../auth/gmail.send
我仍然收到以下错误:
"error":
"code": 403,
"message": "Request had insufficient authentication scopes.",
"errors": [
"message": "Insufficient Permission",
"domain": "global",
"reason": "insufficientPermissions"
],
"status": "PERMISSION_DENIED"
【问题讨论】:
你在哪里添加你的代码中的范围我看不到它。 【参考方案1】:我在您的代码中看不到您提供范围的任何地方,您应该尝试关注JS quickstart
<!DOCTYPE html>
<html>
<head>
<title>Gmail API Quickstart</title>
<meta charset="utf-8" />
</head>
<body>
<p>Gmail API Quickstart</p>
<!--Add buttons to initiate auth sequence and sign out-->
<button id="authorize_button" style="display: none;">Authorize</button>
<button id="signout_button" style="display: none;">Sign Out</button>
<pre id="content" style="white-space: pre-wrap;"></pre>
<script type="text/javascript">
// Client ID and API key from the Developer Console
var CLIENT_ID = '<YOUR_CLIENT_ID>';
var API_KEY = '<YOUR_API_KEY>';
// Array of API discovery doc URLs for APIs used by the quickstart
var DISCOVERY_DOCS = ["https://www.googleapis.com/discovery/v1/apis/gmail/v1/rest"];
// Authorization scopes required by the API; multiple scopes can be
// included, separated by spaces.
var SCOPES = 'https://www.googleapis.com/auth/gmail.readonly';
var authorizeButton = document.getElementById('authorize_button');
var signoutButton = document.getElementById('signout_button');
/**
* On load, called to load the auth2 library and API client library.
*/
function handleClientLoad()
gapi.load('client:auth2', initClient);
/**
* Initializes the API client library and sets up sign-in state
* listeners.
*/
function initClient()
gapi.client.init(
apiKey: API_KEY,
clientId: CLIENT_ID,
discoveryDocs: DISCOVERY_DOCS,
scope: SCOPES
).then(function ()
// Listen for sign-in state changes.
gapi.auth2.getAuthInstance().isSignedIn.listen(updateSigninStatus);
// Handle the initial sign-in state.
updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
authorizeButton.onclick = handleAuthClick;
signoutButton.onclick = handleSignoutClick;
, function(error)
appendPre(JSON.stringify(error, null, 2));
);
/**
* Called when the signed in status changes, to update the UI
* appropriately. After a sign-in, the API is called.
*/
function updateSigninStatus(isSignedIn)
if (isSignedIn)
authorizeButton.style.display = 'none';
signoutButton.style.display = 'block';
listLabels();
else
authorizeButton.style.display = 'block';
signoutButton.style.display = 'none';
/**
* Sign in the user upon button click.
*/
function handleAuthClick(event)
gapi.auth2.getAuthInstance().signIn();
/**
* Sign out the user upon button click.
*/
function handleSignoutClick(event)
gapi.auth2.getAuthInstance().signOut();
/**
* Append a pre element to the body containing the given message
* as its text node. Used to display the results of the API call.
*
* @param string message Text to be placed in pre element.
*/
function appendPre(message)
var pre = document.getElementById('content');
var textContent = document.createTextNode(message + '\n');
pre.appendChild(textContent);
/**
* Print all Labels in the authorized user's inbox. If no labels
* are found an appropriate message is printed.
*/
function listLabels()
gapi.client.gmail.users.labels.list(
'userId': 'me'
).then(function(response)
var labels = response.result.labels;
appendPre('Labels:');
if (labels && labels.length > 0)
for (i = 0; i < labels.length; i++)
var label = labels[i];
appendPre(label.name)
else
appendPre('No Labels found.');
);
</script>
<script async defer src="https://apis.google.com/js/api.js"
onload="this.onload=function();handleClientLoad()"
onreadystatechange="if (this.readyState === 'complete') this.onload()">
</script>
</body>
</html>
【讨论】:
以上是关于错误 403:“请求的身份验证范围不足”,即使相关范围已激活的主要内容,如果未能解决你的问题,请参考以下文章
Google Calendar API - 请求的身份验证范围不足
Google Clas-s-room Pub/Sub 注册返回 403 身份验证错误
Youtube 评论 API 抛出“权限不足:请求的身份验证范围不足”错误