OAuth для GAPI - избегайте аутентификации и авторизации после первоначального входа в Javascript - PullRequest
0 голосов
/ 23 октября 2018

Я создал расширение Chrome, которое читает электронную почту, что-то делает и создает задачи, используя клиентский API Google для JavaScript.Я использую идентификацию Chrome для аутентификации и авторизации.Расширение работает как положено.Тем не менее, он продолжает просить знак время от времени.Я хочу авторизовать пользователя в фоновом скрипте, чтобы ему не нужно было делать это снова и снова, после первоначальной аутентификации и авторизации.

Что я сделал до сих пор:

  • Я прочитал, что мне нужен токен обновления, чтобы избежать этого.Однако ожидается, что токены обновления будут обмениваться и храниться на стороне сервера, а не на стороне клиента (что не сработает, потому что здесь выполняется фоновый скрипт, который на стороне клиента)
  • Использование gapi.auth.авторизоваться с немедленной истиной.Это дает ошибку относительно внешней видимости.Когда я прочитал еще, они предложили использовать его на сервере.Я не уверен, как я могу сделать это в расширении Chrome.
  • Превратить интерактивный в false в getAuthToken, который начинает выдавать ошибку 401 из-за проблемы аутентификации после истечения срока действия токена доступа.

Ниже приведен код, который я использую для аутентификации и авторизации, при этом функция onGoogleLibraryLoaded вызывается после загрузки js-файла клиента API Google.

    var signin = function (callback) {
        chrome.identity.getAuthToken({interactive: true}, callback);
    };

    function onGoogleLibraryLoaded() {
        signin(authorizationCallback);
    }

    var authorizationCallback = function (data) {
        gapi.auth.setToken({access_token: data});
        gapi.client.load('tasks', 'v1')
        gapi.client.load('gmail', 'v1', function () {

            console.log("Doing my stuff after this ..")
        });
    };

ОБНОВЛЕНИЕ: Согласно предложению в ответе, я внес некоторые изменения в код.Тем не менее, я все еще сталкиваюсь с той же проблемой.Ниже приведен обновленный фрагмент кода

jQuery.loadScript = function (url, callback) {
    jQuery.ajax({
        url: url,
        dataType: 'script',
        success: callback,
        async: false

   });
}
//This is the first thing that happens. i.e. loading the gapi client 
if (typeof someObject == 'undefined') $.loadScript('https://apis.google.com/js/client.js', 
    function(){
    console.log("gapi script loaded...")
});

//Every 20 seconds this function runs with internally loads the tasks and gmail 
// Once the gmail module is loaded it calls the function getLatestHistoryId()
setInterval(function() {
    gapi.client.load('tasks', 'v1')
    gapi.client.load('gmail', 'v1', function(){
        getLatestHistoryId()
    })
    // your code goes here...
}, 20 * 1000); // 60 * 1000 milsec

// This is the function that will get user's profile and when the response is received 
// it'll check for the error i.e. error 401 through method checkForError
function getLatestHistoryId(){
  prevEmailData = []

  var request = gapi.client.gmail.users.getProfile({
        'userId': 'me'
    });
    request.execute(function(response){
      console.log("User profile response...")
      console.log(response)
      if(checkForError(response)){
        return
      }
    })
}

// Now here I check for the 401 error. If there's a 401 error 
// It will call the signin method to get the token again. 
// Before calling signin it'll remove the saved token from cache through removeCachedAuthToken
// I have also tried doing it without the removeCachedAuthToken part. However the results were the same.  
// I have left console statements which are self-explanatory
function checkForError(response){
  if("code" in response && (response["code"] == 401)){
    console.log(" 401 found will do the authentication again ...")
    oooAccessToken = localStorage.getItem("oooAccessTokenTG")
    console.log("access token ...")
    console.log(oooAccessToken)
    alert("401 Found Going to sign in ...")

    if(oooAccessToken){
        chrome.identity.removeCachedAuthToken({token: oooAccessToken}, function(){
        console.log("Removed access token")
        signin()
      })  
    }
    else{
      console.log("No access token found to be remove ...")
      signin()
    }
    return true
  }
  else{
    console.log("Returning false from check error")
    return false
  }
}

// So finally when there is 401 it returns back here and calls 
// getAuthToken with interactive true 
// What happens here is that everytime this function is called 
// there is a signin popup i.e. the one that asks you to select the account and allow permissions
// That's what is bothering me. 
// I have also created a test chrome extension and uploaded it to chrome web store. 
// I'll share the link for it separately. 

var signin = function (callback) {
    console.log(" In sign in ...")
    chrome.identity.getAuthToken({interactive: true}, function(data){
        console.log("getting access token without interactive ...")
        console.log(data)

        gapi.auth.setToken({access_token: data});
        localStorage.setItem("oooAccessTokenTG", data)

        getLatestHistoryId()
    })
};

Манифест выглядит следующим образом:

{
  "manifest_version": 2,

  "name": "Sign in Test Extension ",
  "description": "",
  "version": "0.0.0.8",
  "icons": {
      "16": "icon16.png", 
      "48": "icon48.png", 
      "128": "icon128.png" 
  },
  "content_security_policy": "script-src 'self' 'unsafe-eval' https://apis.google.com; object-src 'self'",
  "browser_action": {
   "default_icon": "icon.png",
   "default_popup": "popup.html"
  },
  "permissions": [   
    "identity",
    "storage"
   ],

  "oauth2": {
        "client_id": "1234.apps.googleusercontent.com",
        "scopes": [
            "https://www.googleapis.com/auth/gmail.readonly"
        ]
    },
    "background":{
      "scripts" : ["dependencies/jquery.min.js", "background.js"]
    }
}

Кто-нибудь еще сталкивался с такой же проблемой?

Ответы [ 2 ]

0 голосов
/ 29 октября 2018

Так что я верю, что это будет ответом на мой вопрос.

Немного важных вещей, которые нужно знать

  • Вход в Chrome отличается от входа в gmail. Пользователь A может войти в Chrome, хотя вы планируете использовать расширение Chrome с UserB. chrome.identity.getAuthToken не будет работать в этом случае , потому что он ищет пользователя, вошедшего в Chrome.
  • Для использования других учетных записей Google, например учетной записи, не вошедшей в Chrome, вам необходимо использовать chrome.identity.launchWebAuthFlow.Ниже приведены шаги, которые вы можете использовать.Я имею в виду приведенный здесь пример ( Можно ли получить Id-токен с Chrome App Indentity Api? )

  1. Перейти на консоль Google, создатьВаш собственный проект> Учетные данные> Создать учетные данные> OAuthClientID> Веб-приложение.На этой странице в поле URI авторизованного перенаправления введите URL-адрес перенаправления в формате https: //.chromiumapp.org.Если вы не знаете, что такое идентификатор расширения Chrome, обратитесь к этому ( Идентификатор расширения Chrome - как его найти )
  2. Это создаст идентификатор клиента, который будет добавлен в ваш файл манифеста.Забудьте о любом предыдущем идентификаторе клиента, который вы могли создать.Допустим, в нашем примере идентификатор клиента: 9999.apps.googleusercontent.com

Файл манифеста:

    {
      "manifest_version": 2,
      "name": "Test gmail extension 1",
      "description": "description",
      "version": "0.0.0.1",
      "content_security_policy": "script-src 'self' 'unsafe-eval' https://apis.google.com; object-src 'self'",
      "background": {
        "scripts": ["dependencies/jquery.min.js", "background.js"]
      },
      "browser_action": {
       "default_icon": "icon.png",
       "default_popup": "popup.html"
      },
      "permissions": [
        "identity",
        "storage"

      ],
      "oauth2": {
        "client_id": "9999.apps.googleusercontent.com",
        "scopes": [
          "https://www.googleapis.com/auth/gmail.readonly",
           "https://www.googleapis.com/auth/tasks"
        ]
      }
    }

Образецкод для получения информации о пользователе в background.js

    jQuery.loadScript = function (url, callback) {
        jQuery.ajax({
            url: url,
            dataType: 'script',
            success: callback,
            async: false
       });
    }
    // This is the first thing that happens. i.e. loading the gapi client 
    if (typeof someObject == 'undefined') $.loadScript('https://apis.google.com/js/client.js', 
        function(){
        console.log("gapi script loaded...")
    });


    // Every xx seconds this function runs with internally loads the tasks and gmail 
    // Once the gmail module is loaded it calls the function getLatestHistoryId()
    setInterval(function() {

        gapi.client.load('tasks', 'v1')
        gapi.client.load('gmail', 'v1', function(){
            getLatestHistoryId()
        })
        // your code goes here...
    }, 10 * 1000); // xx * 1000 milsec

    // This is the function that will get user's profile and when the response is received 
    // it'll check for the error i.e. error 401 through method checkForError
    // If there is no error i.e. the response is received successfully 
    // It'll save the user's email address in localstorage, which would later be used as a hint
    function getLatestHistoryId(){
      var request = gapi.client.gmail.users.getProfile({
            'userId': 'me'
        });
        request.execute(function(response){
          console.log("User profile response...")
          console.log(response)
          if(checkForError(response)){
            return
          }
            userEmail = response["emailAddress"]
            localStorage.setItem("oooEmailAddress", userEmail);
        })
    }

    // Now here check for the 401 error. If there's a 401 error 
    // It will call the signin method to get the token again. 
    // Before calling the signinWebFlow it will check if there is any email address 
    // stored in the localstorage. If yes, it would be used as a login hint.  
    // This would avoid creation of sign in popup in case if you use multiple gmail accounts i.e. login hint tells oauth which account's token are you exactly looking for
    // The interaction popup would only come the first time the user uses your chrome app/extension
    // I have left console statements which are self-explanatory
    // Refer the documentation on https://developers.google.com/identity/protocols/OAuth2UserAgent >
    // Obtaining OAuth 2.0 access tokens > OAUTH 2.0 ENDPOINTS for details regarding the param options
    function checkForError(response){
      if("code" in response && (response["code"] == 401)){
        console.log(" 401 found will do the authentication again ...")
        // Reading the data from the manifest file ...
        var manifest = chrome.runtime.getManifest();

        var clientId = encodeURIComponent(manifest.oauth2.client_id);
        var scopes = encodeURIComponent(manifest.oauth2.scopes.join(' '));
        var redirectUri = encodeURIComponent('https://' + chrome.runtime.id + '.chromiumapp.org');
        // response_type should be token for access token
        var url = 'https://accounts.google.com/o/oauth2/v2/auth' + 
                '?client_id=' + clientId + 
                '&response_type=token' + 
                '&redirect_uri=' + redirectUri + 
                '&scope=' + scopes

        userEmail = localStorage.getItem("oooEmailAddress")
        if(userEmail){
            url +=  '&login_hint=' + userEmail
        } 

        signinWebFlow(url)
        return true
      }
      else{
        console.log("Returning false from check error")
        return false
      }
    }


    // Once you get 401 this would be called
    // This would get the access token for user. 
    // and than call the method getLatestHistoryId again 
    async function signinWebFlow(url){
        console.log("THE URL ...")
        console.log(url)
        await chrome.identity.launchWebAuthFlow(
            {
                'url': url, 
                'interactive':true
            }, 
            function(redirectedTo) {
                if (chrome.runtime.lastError) {
                    // Example: Authorization page could not be loaded.
                    console.log(chrome.runtime.lastError.message);
                }
                else {
                    var response = redirectedTo.split('#', 2)[1];
                    console.log(response);

                    access_token = getJsonFromUrl(response)["access_token"]
                    console.log(access_token)
                    gapi.auth.setToken({access_token: access_token});
                    getLatestHistoryId()
                }
            }
        );
    }

    // This is to parse the get response 
    // referred from https://stackoverflow.com/questions/8486099/how-do-i-parse-a-url-query-parameters-in-javascript
    function getJsonFromUrl(query) {
      // var query = location.search.substr(1);
      var result = {};
      query.split("&").forEach(function(part) {
        var item = part.split("=");
        result[item[0]] = decodeURIComponent(item[1]);
      });
      return result;
    }

Не стесняйтесь связаться со мной, если у вас есть какие-либо вопросы.Я провел довольно много дней, соединяя эти точки.Я бы не хотел, чтобы кто-то еще делал то же самое.

0 голосов
/ 24 октября 2018

Я также использую API идентификации для авторизации Google в своем расширении Chrome.Я имел обыкновение получать статус 401, когда мой токен Google истек.Поэтому я добавил проверку, что если я получаю 401 статусный ответ на мой запрос, то я снова авторизуюсь и получу токен (это произойдет в фоновом режиме) и продолжу свою работу.

Вот пример из моегоbackground.js

var authorizeWithGoogle = function() {
    return new Promise(function(resolve, reject) {
        chrome.identity.getAuthToken({ 'interactive': true }, function(result) {
            if (chrome.runtime.lastError) {
                alert(chrome.runtime.lastError.message);
                return;
            }
            if (result) {
                chrome.storage.local.set({'token': result}, function() {
                    resolve("success");
                });
            } else {
                reject("error");
            }
        });
    });
}

function getEmail(emailId) {
    if (chrome.runtime.lastError) {
        alert(chrome.runtime.lastError.message);
        return;
    }
    chrome.storage.local.get(["token"], function(data){
        var url = 'https://www.googleapis.com/gmail/v1/users/me/messages/id?alt=json&access_token=' + data.token;
        url = url.replace("id", emailId);
        doGoogleRequest('GET', url, true).then(result => {
            if (200 === result.status) {
                //Do whatever from the result
            } else if (401 === result.status) {
                /*If the status is 401, this means that request is unauthorized (token expired in this case). Therefore refresh the token and get the email*/
                refreshTokenAndGetEmail(emailId);
            }
        });
    });
}

function refreshTokenAndGetEmail(emailId) {
    authorizeWithGoogle().then(getEmail(emailId));
}

Мне не нужно снова и снова входить в систему вручную.Токен Google автоматически обновляется в фоновом режиме.

...