Подтвердить что ты не робот

Использование OAuth2 в веб-приложении HTML5

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

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();
            });
        });
    }(),
    checkAuthenticated: function() {
        access_token = window.localStorage.getItem('access_token');
        if( access_token == null ) {
            App.splash();
        }
        else {
            Users.checkTokenValid(access_token);
        }
    },
    checkTokenValid: function(access_token){

        $.ajax({
            type: 'GET',
            url: 'http://domain.com/api/oauth/userinfo',
            data: {
                access_token: access_token
            },
            dataType: 'jsonp',
            success: function(data) {
                console.log('success');
                if( data.error ) {
                    refresh_token = window.localStorage.getItem('refresh_token');
                     if( refresh_token == null ) {
                         App.splash();
                     } else {
                         Users.refreshToken(refresh_token);
                    }
                } else {
                    App.home();
                }
            },
            error: function(a,b,c) {
                console.log('error');
                console.log(a,b,c);
                refresh_token = window.localStorage.getItem('refresh_token');
                 if( refresh_token == null ) {
                     App.splash();
                 } else {
                     Users.refreshToken(refresh_token);
                }
            }
        });

    },
    refreshToken: function(refreshToken){

        $.ajax({
            type: 'GET',
            url: 'http://domain.com/api/oauth/token',
            data: {
                grant_type: 'refresh_token',
                refresh_token: refreshToken,
                client_id: 'NTEzN2FjNzZlYzU4ZGM2'
            },
            dataType: 'jsonp',
            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);
            }
        });

    },
    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: 'jsonp',
            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);
            }
        });
    },
    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();
    }
};

У меня есть ряд вопросов, связанных с моей реализацией OAuth:

1.) По-видимому, сохранение access_token в localStorage - это плохая практика, и вместо этого я должен использовать файлы cookie. Может ли кто-нибудь объяснить, почему? Поскольку это не является более безопасным или менее безопасным, насколько я могу судить, поскольку данные cookie не будут зашифрованы.

ОБНОВЛЕНИЕ: В соответствии с этим вопросом: Локальное хранилище и файлы cookie хранение данных в localStorage ТОЛЬКО доступно на клиентской стороне и не делает любой HTTP-запрос, в отличие от файлов cookie, так кажется мне более безопасным, или, по крайней мере, у меня нет никаких проблем!

2.) Относительно вопроса 1, использование файла cookie для истечения срока будет одинаково для меня бессмысленным, как если бы вы посмотрели на код, в приложении начнется запрос на получение информации о пользователе, которая вернется ошибка, если она истекла на сервере, и требует refresh_token. Поэтому не уверен в преимуществах времени истечения срока действия на BOTH-клиенте и сервере, когда сервер действительно имеет значение.

3.) Как мне получить токен обновления, без A, сохраняя его с помощью исходного access_token для использования позже, и B) также сохраняя client_id? Мне сказали, что это проблема с безопасностью, но как я могу использовать их позже, но защищать их в приложении JS? Снова просмотрите приведенный выше код, чтобы узнать, как я это сделал до сих пор.

4b9b3361

Ответ 1

Похоже, вы используете учетные данные пароля владельца ресурса. поток OAuth 2.0, например. отправив имя пользователя/пароль, чтобы вернуть токен доступа и обновить токен.

  • токен доступа МОЖЕТ отображаться в javascript, риски, связанные с тем, что токен доступа подвергается каким-то образом, смягчаются его короткой продолжительностью жизни.
  • токен обновления НЕ ДОЛЖЕН быть выставлен на стороне JavaScript javascript. Он использовал, чтобы получить больше токенов доступа (как вы делаете выше), но если злоумышленник смог получить токен обновления, он сможет получить больше токенов доступа по своему усмотрению до тех пор, пока сервер OAuth не аннулирует авторизацию клиент, для которого был выпущен токен refresh.

С учетом этого вопроса позвольте мне задать ваши вопросы:

  • Либо файл cookie, либо localstorage даст вам локальное сохранение на странице обновления. Хранение токена доступа в локальном хранилище дает вам немного больше защиты от атак CSRF, поскольку он не будет автоматически отправляться на сервер, как cookie. Ваш клиентский javascript должен будет вытащить его из localstorage и передать его по каждому запросу. Я работаю над приложением OAuth 2, и потому, что это одностраничный подход, я не делаю этого; вместо этого я просто храню его в памяти.
  • Я согласен... если вы сохраняете в cookie это только для сохранения не для истечения срока, сервер будет реагировать с ошибкой, когда токен истекает. Единственная причина, по которой я могу думать, что вы можете создать куки файл с истечением срока, - это то, что вы можете определить, истек ли он БЕЗ, сначала сделав запрос и ожидая ответа об ошибке. Конечно, вы можете сделать то же самое с локальным хранилищем, сохранив это известное время истечения срока действия.
  • Это суть всего вопроса, который я считаю... "Как я могу получить токен обновления без A, сохраняя его с помощью исходного access_token для использования позже, и B), также сохраняя client_id". К сожалению, вы действительно не можете... Как отмечается в этом вступительном комментарии, наличие клиентской стороны refresh token отрицает безопасность, предоставляемую токеном доступа, ограниченным сроком службы. То, что я делаю в своем приложении (где я не использую постоянное состояние сеанса на стороне сервера), выглядит следующим образом:
    • Пользователь отправляет имя пользователя и пароль на сервер
    • сервер затем перенаправляет имя пользователя и пароль в конечную точку OAuth в вашем примере выше http://domain.com/api/oauth/token и получает токен доступа и токен обновления... li >
    • Сервер шифрует токен refresh и устанавливает его в файл cookie (должен быть только HTTP)
    • Сервер отвечает только токеном доступа в открытом тексте (в ответе JSON) И зашифрованный HTTP файл cookie
    • javascript на стороне клиента теперь может читать и использовать токен доступа (хранить в локальном хранилище или что-то еще
    • Когда токен доступа истекает, клиент отправляет запрос на сервер (не сервер OAuth, а сервер, на котором размещено приложение) для нового токена
    • Сервер, получает зашифрованный HTTP файл cookie, который был создан, расшифровывает его, чтобы получить токен refresh, запрашивает новый токен доступа и, наконец, возвращает новый токен доступа ответ.

По общему признанию, это нарушает ограничение "JS-Only", которое вы искали. Тем не менее, а) снова вы действительно НЕ должны иметь токен обновления в javascript и b) он требует довольно минимальной логики на стороне сервера при входе в систему/выходе из системы и отсутствии постоянного хранилища на стороне сервера.

Примечание по CSRF. Как отмечено в комментариях, это решение не адресует Подпрограмма запроса на межсайтовый сайт; см. OWASP CSRF Cheat Sheet для дальнейших идей по устранению этих форм атак.

Другой альтернативой является просто не запрашивать токен обновления (не уверен, что это вариант с реализацией OAuth 2, с которой вы имеете дело, токен обновления необязателен за спецификацию) и постоянно повторно аутентифицироваться по истечении срока действия.

Надеюсь, что это поможет!

Ответ 2

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

1) Ваша оценка того, что вы не являетесь отличным решением, является точной.

2) Использование сроков истечения срока действия будет вашим лучшим, если вы ограничены только развитием клиентской стороны. Это не потребует от ваших пользователей повторной аутентификации с Oauth так часто, и гарантировать, что токен не будет жить вечно. Все еще не самый безопасный.

3) Получение нового токена потребует выполнения рабочего процесса Oauth для получения нового токена. Client_id привязан к определенному домену для работы Oauth.

Самый безопасный метод для сохранения токенов Oauth - это реализация на стороне сервера.

Ответ 3

Только для чистой клиентской стороны, если у вас есть шанс, попробуйте использовать "Неявный поток" , а не "поток владельца ресурсов". Вы не получаете токен обновления в качестве части ответа.

  • Когда страница доступа пользователя JavaScript проверяет для access_token в localStorage и проверяет ее expires_in
  • При отсутствии или истечении срока действия приложение открывает новую вкладку и перенаправляет пользователя на страницу входа в систему, после успешного входа пользователя пользователя перенаправляется обратно с токеном доступа, который обрабатывается только на стороне клиента и сохраняется в локальном хранилище с страницей перенаправления.
  • На главной странице может быть механизм опроса маркера доступа в локальном хранилище, и как только пользователь выполнит вход в систему (страница переадресации сохраняет токен в хранилище).

В приведенном выше подходе токен доступа должен быть длинным (например, 1 год). Если есть проблема с длинным живым токеном, вы можете использовать следующий трюк.

  • Когда страница доступа пользователя JavaScript проверяет для access_token в localStorage и проверяет ее expires_in
  • При отсутствии или истечении срока действия приложение открывает скрытый iframe и пытается войти в систему. Обычно на веб-сайте auth есть файл cookie пользователя и хранится на веб-сайте клиента, поэтому вход автоматически происходит, и script внутри iframe будет заполнять токен в хранилище
  • На главной странице клиента устанавливается механизм опроса на access_token и тайм-аут. Если за этот короткий период access_token не заселен в хранилище, это означает, что нам нужно открыть новую вкладку и установить нормальный Неявный поток в движении