2014-05-31 4 views
4

Это то, о чем я пытался получить информацию ранее, но я никогда не нашел ответ или решение проблемы. Поэтому, надеюсь, кто-то может прояснить ситуацию и указать мне в правильном направлении.Использование токенов доступа с JavaScript в OAuth2

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

В основном, у меня есть установка OAuth2 sever с использованием CakePHP, с которой можно связаться с JavaScript, чтобы позволить пользователю войти в систему и получить токен доступа, а затем сделать различные запросы к разным конечным точкам, используя этот токен для отправки и получения данных.

var access_token, 
    refresh_token; 

var App = { 
    init: function() { 
     $(document).ready(function(){ 
      Users.checkAuthenticated(); 
     }); 
    }(), 
    splash: function() { 
     var contentLogin = '<input id="Username" type="text"> <input id="Password" type="password"> <button id="login">Log in</button>'; 
     $('#app').html(contentLogin); 
    }, 
    home: function() { 
     var contentHome = '<h1>Welcome</h1> <a id="logout">Log out</a>'; 
     $('#app').html(contentHome); 
    } 
}; 

var Users = { 
    init: function(){ 
     $(document).ready(function() { 
      $('#login').live('click', function(e){ 
       e.preventDefault(); 
       Users.login(); 
      }); 
      $('#logout').live('click', function(e){ 
       e.preventDefault(); 
       Users.logout(); 
      }); 
     }); 
    }(), 

    // Check that if user is logged in (has an access token) 

    checkAuthenticated: function() { 
     access_token = window.localStorage.getItem('access_token'); 
     if(access_token == null) { 
      Users.logout(); 
     } 
     else { 
      Users.checkTokenValid(access_token); 
     } 
    }, 

    // Check the token is still valid on the server for access (also get User info) 

    checkTokenValid: function(access_token){ 

     $.ajax({ 
      type: 'GET', 
      url: 'http://domain.com/api/oauth/userinfo', 
      data: { 
       access_token: access_token 
      }, 
      dataType: 'json', 
      success: function(data) { 

       console.log('success'); 

       console.log(data); 

       if(data.error) { 
        refresh_token = window.localStorage.getItem('refresh_token'); 
        if(refresh_token == null) { 
         Users.logout(); 
        } else { 
         Users.refreshToken(refresh_token); 
        } 
       } else { 
        App.home(); 
       } 
      }, 
      error: function(a,b,c) { 

       console.log('error'); 

       console.log(a); 

       refresh_token = window.localStorage.getItem('refresh_token'); 
       if(refresh_token == null) { 
        Users.logout(); 
       } else { 
        Users.refreshToken(refresh_token); 
       } 
      } 
     }); 

    }, 

    // Request a new access token using the refresh token 

    refreshToken: function(refresh_token){ 

     $.ajax({ 
      type: 'GET', 
      url: 'http://domain.com/api/oauth/token', 
      data: { 
       grant_type: 'refresh_token', 
       refresh_token: refresh_token, 
       client_id: 'NTEzN2FjNzZlYzU4ZGM2' 
      }, 
      dataType: 'json', 
      success: function(data) { 
       if(data.error) { 
        alert(data.error); 
       } else { 
        window.localStorage.setItem('access_token', data.access_token); 
        window.localStorage.setItem('refresh_token', data.refresh_token); 
        access_token = window.localStorage.getItem('access_token'); 
        refresh_token = window.localStorage.getItem('refresh_token'); 
        App.home(); 
       } 
      }, 
      error: function(a,b,c) { 
       console.log(a,b,c); 
       Users.logout(); 
      } 
     }); 

    }, 

    // send login credentials and store tokens in localStorage and in variables 

    login: function() { 
     $.ajax({ 
      type: 'GET', 
      url: 'http://domain.com/api/oauth/token', 
      data: { 
       grant_type: 'password', 
       username: $('#Username').val(), 
       password: $('#Password').val(), 
       client_id: 'NTEzN2FjNzZlYzU4ZGM2' 
      }, 
      dataType: 'json', 
      success: function(data) { 
       if(data.error) { 
        alert(data.error); 
       } else { 
        window.localStorage.setItem('access_token', data.access_token); 
        window.localStorage.setItem('refresh_token', data.refresh_token); 
        access_token = window.localStorage.getItem('access_token'); 
        refresh_token = window.localStorage.getItem('refresh_token'); 
        App.home(); 
       } 
      }, 
      error: function(a,b,c) { 
       console.log(a,b,c); 
      } 
     }); 
    }, 

    // Clear the localStorage and token variables and load the login (splash page) 

    logout: function() { 
     localStorage.removeItem('access_token'); 
     localStorage.removeItem('refresh_token'); 
     access_token = window.localStorage.getItem('access_token'); 
     refresh_token = window.localStorage.getItem('refresh_token'); 
     App.splash(); 
    } 
}; 

Надеется, что код все это имеет смысл ... но в двух словах, он посылает имя пользователя и пароль к API, который затем посылает обратно как в access_token и refresh_token, что я тогда магазин используя LocalStorage в HTML5. Refresh_token используется для получения нового access_token после того, как access_token больше не работает, поэтому пользователь получает беспрепятственный опыт без необходимости вести вход в систему (если только они не выходят из системы!). Это обрабатывается функцией checkTokenValid, которую я вызываю, чтобы проверить, что она по-прежнему действительна, и либо запросить новый токен, либо сделать запрос пользователя повторно, если refresh_token не существует (или также недействителен).

  1. Первой проблемой является сохранение refresh_token. Это, как правило, не будет проблемой, поскольку оно будет храниться на стороне сервера, но поскольку на клиентской стороне отображается идентификатор клиента, и поэтому, если кто-то должен был получить доступ к браузеру пользователей, они могут запрашивать новые токены. Итак, как я могу заставить пользователя войти в систему (т. Е. Запросить новый access_token автоматически) без использования токенов обновления? Это даже проблема, поскольку это только на машине пользователя!

  2. Вторая проблема: мне сказали, что я не должен использовать этот тип типа гранта (пароль/пароль владельца ресурса), потому что это клиентская сторона и, следовательно, такие вещи, как идентификатор клиента, и секрет не может быть защищен. И вместо этого я должен использовать Implicit. Однако я не смог понять, как это поможет мне решить первую проблему. Может ли кто-нибудь показать пример этого? И как это решит проблему refresh_token выше. Из того, что я прочитал относительно неявного типа гранта, все, что он делает, просто упрощает процесс маркера (устраняя необходимость в идентификаторе клиента) и фактически не делает ничего другого.

  3. Наконец, поскольку приложение ВСЕГДА будет единственным приложением, использующим API, пользователю не нужно будет когда-либо проходить через процесс предоставления типа токена, поэтому вся настройка идентификаторов клиентов кажется излишним для того, что просто некоторые JavaScript говорят с API. Какие у меня есть другие варианты? Я думал о том, чтобы уволить OAuth и просто пойти с Basic Auth вместо этого ... Но как насчет сеансов? Так как у меня не будет токена! Мысли?

+0

+1. Я пытался получить ответ на этот вопрос (предлагал щедрость), но без большой удачи. Наилучшие ответы можно найти на страницах, опубликованных здесь, которые просто подтверждают, что, когда вы находитесь в SPA с неявным потоком, нет хорошего варианта для сохранения токена на клиенте. Из двух, которые я видел, реализовано, есть один, содержащий только cookie http для хранения токена обновления, и тот, который предлагает Taiseer, использует CORS. Очень жаль, что в SPA не публикуется больше о том, как справиться с этим. – Tom

ответ

0

Для получения токена обновления вам необходимо использовать поток кода, а не неявный поток грантов.

Вы не можете безопасно обновить токен в неявном потоке.

Очевидно, что вы можете реализовать поток кода авторизации в клиенте (то есть - как будто это сервер), но это вызывает 2 проблемы:

1) Если IDP (Идентичность Provider) находится в другом домене, ваш клиент, ваш браузер не позволит вам делать HTTP-вызовы для генерации токенов из созданного пользователем кода (или с помощью refresh_token).

2) Ваш секрет клиента будет доступен изнутри клиента - недостаток безопасности. Также будет доступен токен обновления - еще один недостаток безопасности.

Вкратце - при использовании неявного потока oAuth не может быть «бесшовного обновления».

+0

Таким образом, невозможно создать приложение в чисто JavaScript, где пользователю не нужно вести регистрацию после каждого запроса в API без хранения данных, которые создавали бы недостаток безопасности? В противном случае, какие у меня варианты? – Cameron

+0

Если вы хотите использовать приложение на основе браузера, то нет, вы не можете обновлять токены из него, поскольку неявный поток не предоставляет токены обновления, и это единственный поток «чистого браузера» oAuth.Причина, по которой этот поток был создан, заключается в том, что браузеры блокируют HTTP-запросы кросс-домена, и в большинстве сценариев IDP находится в другом домене, чем веб-сайт, который его использует. Таким образом, браузер может перенаправлять, но не делать HTTP-запросы к IDP. Если предоставление опыта обновления важно, вам просто нужно выполнить поток кода авторизации через сервлет на вашем сервере. –

+0

Итак, пользователь должен будет снова войти в систему после каждого запроса? – Cameron

3

1. Как сохранить пользователей от необходимости войти на каждый запрос

Никто не заставляет вас выдавать токенов обновления, или истечь токенов на каждом запросе. Если вы выдаете токен доступа, который действителен в течение часа или двух, для пользователя должно быть достаточно времени, чтобы пользователь мог использовать веб-сайт, не позволяя предлагать «бесконечные» обновления токенов обновления обновления (как обычно с использованием токена обновления, как новый токен доступа, так и обновляемый токен). Выберите период истечения срока действия, который позволит использовать типичный период действия пользователя, разрешить некоторые дополнительные действия, и вы можете вообще не выпускать токены обновления.

Конечно, существует определенный риск того, что пользователь превысит этот временной интервал и будет вынужден снова войти в систему. Я думаю, что обычно, если это происходит, если им объясняется, что это происходит по соображениям безопасности и что происходит в простых, нетехнических терминах, с ними все будет в порядке. В конце концов, это всегда компромисс между лучшей безопасностью и лучшим юзабилити: лучшее удобство использования будет использовать токены обновления, очевидно - ваши пользователи никогда не будут вынуждены повторно войти в систему, пока они используют страницу, - но если вы это сделаете, вы должны жить с тем, чтобы быть подвергнутым риску.

Локальное хранилище защищено одной политикой происхождения, поэтому оно примерно так же безопасно, как и все, что браузер может предложить для хранения вещей. Лично мне нравится идея использования cookie сеанса для хранения токенов - она ​​работает через вкладки браузера, очищается, когда браузер закрыт, и когда пользователь вручную очищает свои файлы cookie, они получают ожидаемое поведение «забытого» вашим страницы (которые они не будут использовать с локальным/сеансовым хранилищем браузера). Если вы это сделаете, обязательно используйте безопасный только cookie. Установка пути к файлу cookie к несуществующему значению будет препятствовать его передаче по каждому запросу, хотя это чисто соображение размера сообщения, так как оно будет передаваться по каждому запросу в любом случае (в заголовке авторизации). Какой бы тип памяти вы ни использовали, вы всегда подвержены атакам XSS, поэтому убедитесь, что вы хорошо защищаете его.

Мое мнение вкратце: вы не обязаны выпускать токены обновления или пропускать токены доступа по каждому запросу. Если вы хотите избежать использования токенов обновления, увеличьте токены доступа дольше - но вам придется жить с тем, что пользователи будут вынуждены повторно аутентифицироваться при использовании вашего веб-сайта.

2. Использование неявного потока над владелец ресурса Пароль мандатного поток

Причина, по которой вы обескуражены от использования ресурсов Владелец паролей учетных данных поток обычно происходит потому, что вынуждает пользователь выставить свои полномочия для другого поставщика удостоверений, который вы используете для их аутентификации. То есть если вы разрешаете своим пользователям входить в систему с помощью своего Google/Facebook, вы не должны использовать этот поток, потому что пользователь должен будет указать свое имя пользователя и пароль для вашего приложения/веб-сайта, и вы можете делать с ними всевозможные махинации ,

Поскольку вы с другой стороны, от того, как я понимаю ваш вопрос, есть своего собственного сервер OAuth2 и работают, вы собственный поставщик идентичности - у вас уже есть доступ к учетным данным пользователя (имя пользователя и хэшируются пароль), поэтому избегать потока владельца ресурса не имеет дополнительных преимуществ безопасности.RFC 6749 на самом деле не указывает идентификатор клиента и секрет для этого потока (и это тоже не имеет смысла), следовательно, нет необходимости защищать то, что вам не нужно предоставлять.

Мое мнение в двух словах: поскольку вы являетесь вашим собственным поставщиком удостоверений, используя поток учетных данных пароля владельца ресурса, все в порядке. Для этого вам не нужен идентификатор клиента и/или секрет. Поток Autorization Гранта и неявный поток предназначены для проверки подлинности, когда вы против другого провайдера идентификации (например, Google или Facebook)

3. Для того, чтобы сохранить или не сохранить маркер аутентификации на основе

, вероятно, наиболее часто используемыми механизм аутентификации по-прежнему зависит от сеанса, то есть сервер, когда вы вошли в систему, выдает идентификатор сеанса, с помощью которого он отслеживает тот факт, что вы успешно прошли аутентификацию. Обычно этот идентификатор сеанса хранится как файл cookie. Красота заключается в том, что у вас практически нет проблем с реализацией - установите cookie, и браузер будет обрабатывать практически все подробные подробные сведения для вас. На сервере все, что вам нужно сделать, это проверить, что сеанс действителен. Тем не менее, этот механизм по-прежнему подвержен атакам XSS и, что еще хуже, к XSRF (подделка запроса на межсайтовый запрос). Хотя это не невозможно предотвратить, это немного больно обнаружить и предотвратить это. С помощью системы аутентификации на основе токенов вы получаете встроенную защиту XSRF.

Моя точка зрения: поскольку у вас уже есть сервер OAuth2 и я буду придерживаться его. Если вы переходите с потоком учетных данных пароля владельца ресурса, нет необходимости использовать идентификатор клиента и секретный код.

+0

Для пункта 2 вы говорите, что мне не нужен идентификатор клиента, но я на самом деле делаю это! Я использую этот плагин CakePHP: https://github.com/thomseddon/cakephp-oauth-server, который использует: https://github.com/quizlet/oauth2-php, и для всех запросов требуется идентификатор клиента ... Неправильно ли этот плагин? – Cameron

+0

На самом деле плагин не реализует неявное, поэтому я не могу его использовать (так что вам может не понадобиться ID для этого типа) ... **, но это сказано ... ** какая фактическая выгода этот тип гранта в любом случае? как если бы идентификатор был укоренился по соображениям безопасности ... как вообще не было такого безопасного? И как я могу остановить других людей, использующих тип ресурса? т. е. ограничивать его только моим приложением? – Cameron

+0

@Cameron Я бы предложил прочитать https://developers.google.com/accounts/docs/OAuth2 и RFC OAUTH 2, чтобы получить представление о разных потоках. После того, как у вас есть тип поддержки пароля владельца ресурса, который поддерживается на вашем сервере, все может использовать его для аутентификации с правильным именем пользователя и паролем, чтобы получить токен доступа для использования в запросах API. Идентификаторы клиентов и секреты клиента обычно используются для взаимодействия между бэкэнд-услугами, то есть пользователь предоставляет доступ к службе A для использования ресурсов, управляемых службой B, через поток OAUTH2 и таргетинг на перенаправление браузера. –

2
  1. Обновить токены необязательны для типа потока, который вы используете. Вы можете использовать поток паролей, когда требуется, чтобы получить еще один токен доступа и отказаться от токенов обновления.
  2. Тип гранта владельца ресурса - это правильный тип потока для вашего случая, так как он является вашим клиентом, использующим ваш сервер oauth. Использование идентификаторов клиентов не требуется или не имеет смысла, поскольку ваш клиент является клиентом на основе браузера. Чтобы убедиться в этом, представьте, что вы использовали сторонний сервер oauth, это будет их код клиентского клиента, запрашивающий пароль или проверяющий состояние cookie или localstorage. Грязная работа просить пользователя разрешить действие и предоставить имя пользователя и пароль должна быть выполнена где-то, а поскольку в вашем случае это не третья сторона, ваш сервер oauth и веб-клиент делают запрос о неизбежности и должны быть выполнены.
  3. Придерживайтесь типа ресурса владельца ресурса, который дает токен доступа. Это зависит от вас, если вы хотите сохранить это в localStorage, это будет не так удобно и потребует переустройства каждый раз, когда выполняется обновление жесткой страницы, но оно более безопасно, если вы обеспокоены тем, что токены доступны на клиент. Хотя злоумышленник с таким уровнем доступа к клиенту может монтировать многие другие атаки, держите его в перспективе.
+0

Я задал еще несколько вопросов (размещенных под вопросом BadIdeaException, поскольку они имели аналогичный ответ). Не стесняйтесь чип в своих мыслях: http://stackoverflow.com/questions/23974783/using-access-tokens-with-javascript-in-oauth2#comment38238151_24086545 – Cameron

Смежные вопросы