在HTML5 Web App中使用OAuth2 [英] Using OAuth2 in HTML5 Web App

查看:181
本文介绍了在HTML5 Web App中使用OAuth2的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在尝试使用OAuth2开发一个完全使用JavaScript构建的移动应用程序,该应用程序与CakePHP API进行通信。看看下面的代码,看看我的应用目前看起来如何(请注意,这是一个实验,因此代码混乱,缺乏结构等等。)

I am currently experimenting with OAuth2 to develop a mobile application built entirely in JavaScript that talks to a CakePHP API. Take a look at the following code to see how my app currently looks (please note that this is an experiment, hence the messy code, and lack of structure in areas, etc..)

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实施相关的问题:

I have a number of questions relating to my implementation of OAuth:

1。)显然在localStorage中存储access_token是不好的做法,我应该使用cookie。有谁能解释为什么?因为这不是更安全或更不安全,因为cookie数据不会被加密。

1.) Apparently storing the access_token in localStorage is bad practice and I should instead be using cookies. Can anyone explain why? As this isn't anymore secure or less secure as far as I can tell, as the cookie data wouldn't be encrypted.

更新:根据这个问题:本地存储与Cookie 在localStorage中存储数据仅在客户端可用并且不像cookie那样做任何HTTP请求,所以对我来说似乎更安全,或者至少我认为没有任何问题!

UPDATE: According to this question: Local Storage vs Cookies storing the data in localStorage is ONLY available on the client-side anyways and doesn't do any HTTP request unlike cookies, so seems more secure to me, or least doesn't seem to have any issues as far as I can tell!

2。)关于问题1,使用cookie到期时间对我来说同样毫无意义,就好像你看一下代码一样,在app上发出请求以获取用户信息,这将是如果它在服务器端已过期,则返回错误,并需要refresh_token。所以不确定在BOTH客户端和服务器上有到期时间的好处,当服务器真正重要时。

2.) Relating to question 1, use of a cookie for expiration time, would equally be pointless to me, as if you look at the code, a request is made on app start to get the user info, which would return an error if it had expired on the server end, and require a refresh_token. So not sure of benefits of having expiry times on BOTH client and server, when the server one is what really matters.

3.。如何获得刷新令牌,没有A,用原来的access_token存储它以便以后使用,B)还存储一个client_id?我被告知这是一个安全问题,但我怎样才能在以后使用它们,但是在仅限JS的应用程序中保护它们?再看上面的代码,看看到目前为止我是如何实现的。

3.) How do I get a refresh token, without A, storing it with the original access_token to use later, and B) also storing a client_id? I've been told this is a security issue, but how can I use these later, but protect them in a JS-only app? Again see the code above to see how I have implemented this so far.

推荐答案

看起来你正在使用资源所有者密码凭证 OAuth 2.0流程例如提交用户名/通行证以取回访问令牌和刷新令牌。

It looks like you're using the Resource Owner Password Credentials OAuth 2.0 flow e.g. submitting username/pass to get back both an access token and refresh token.


  • 可以公开访问令牌在javascript中,访问令牌以某种方式暴露的风险因其短暂的生命周期而得到缓解。

  • 刷新令牌不应暴露给客户端javascript。它用于获取更多访问令牌(正如您上面所做的那样)但是如果攻击者能够获得刷新令牌,他们将能够随意获得更多访问权限,直到OAuth服务器撤销授权为止。已发出刷新令牌的客户。

  • The access token CAN be exposed in javascript, the risks of the access token being exposed somehow are mitigated by its short lifetime.
  • The refresh token SHOULD NOT be exposed to client-side javascript. It's used to get more access tokens (as you're doing above) but if an attacker was able to get the refresh token they'd be able to get more access tokens at will until such time as the OAuth server revoked the authorization of the client for which the refresh token was issued.

考虑到这一背景,让我来解答您的问题:

With that background in mind, let me address your questions:


  1. cookie或localstorage都会在页面刷新时为您提供本地持久性。将访问令牌存储在本地存储中可以为CSRF攻击提供更多保护,因为它不会像cookie那样自动发送到服务器。您的客户端javascript需要将其从localstorage中拉出来并在每个请求中传输它。我正在开发一个OAuth 2应用程序,因为这是一个单页方法我不做;相反,我只是把它留在内存中。

  2. 我同意......如果你存储在一个cookie中,它只是为了持久性而不是到期,服务器会响应错误当令牌到期时我认为您可能创建一个过期的cookie的唯一原因是,您可以检测它是否已过期而不首先发出请求并等待错误响应。当然,通过保存已知的到期时间,您可以对本地存储执行相同的操作。

  3. 这是我相信的整个问题的关键......我如何获得刷新令牌,没有A,用原始access_token存储它以便以后使用,B)也存储client_id。不幸的是,你真的不能......正如那篇介绍性评论中所述,拥有刷新令牌的客户端否定了访问令牌的有限生命周期所提供的安全性。我在我的应用程序中所做的事情(我没有使用任何持久的服务器端会话状态)如下:


    • 用户提交用户名和密码到服务器

    • 服务器然后将用户名和密码转发到OAuth端点,在上面的示例中 http:// domain .com / api / oauth / token ,并接收访问令牌和刷新令牌

    • 服务器加密刷新令牌并将其设置为cookie(应该仅限HTTP)

    • 服务器以明文形式响应访问令牌(在一个JSON响应)和加密的HTTP唯一cookie

    • 客户端javascript现在可以读取并使用访问令牌(存储在本地存储或其他任何

    • 当访问令牌过期时,客户端向服务器(不是OAuth服务器,但托管应用程序的服务器)提交新令牌请求

    • 服务器,接收■它创建的加密的HTTP cookie,解密它以获得刷新令牌,请求新的访问令牌,最后在响应中返回新的访问令牌

  1. Either a cookie or localstorage will give you local persistence across page refreshes. Storing the access token in local storage gives you a little more protection against CSRF attacks as it will not be automatically sent to the server like a cookie will. Your client-side javascript will need to pull it out of localstorage and transmit it on each request. I'm working on an OAuth 2 app and because it's a single page approach I do neither; instead I just keep it in memory.
  2. I agree... if you're storing in a cookie it's just for the persistence not for expiration, the server is going to respond with an error when the token expires. The only reason I can think you might create a cookie with an expiration is so that you can detect whether it has expired WITHOUT first making a request and waiting for an error response. Of course you could do the same thing with local storage by saving that known expiration time.
  3. This is the crux of the whole question I believe... "How do I get a refresh token, without A, storing it with the original access_token to use later, and B) also storing a client_id". Unfortunately you really can't... As noted in that introductory comment, having the refresh token client side negates the security provided by the access token's limited lifespan. What I'm doing in my app (where I'm not using any persistent server-side session state) is the following:
    • The user submits username and password to the server
    • The server then forwards the username and password to the OAuth endpoint, in your example above http://domain.com/api/oauth/token, and receives both the access token and refresh token.
    • The server encrypts the refresh token and sets it in a cookie (should be HTTP Only)
    • The server responds with the access token ONLY in clear text (in a JSON response) AND the encrypted HTTP only cookie
    • client-side javascript can now read and use the access token (store in local storage or whatever
    • When the access token expires, the client submits a request to the server (not the OAuth server but the server hosting the app) for a new token
    • The server, receives the encrypted HTTP only cookie it created, decrypts it to get the refresh token, requests a new access token and finally returns the new access token in the response.

不可否认,这确实违反了您所寻求的仅限JS限制。但是,a)你真的不应该在javascript中有一个刷新令牌,b)它在登录/注销时需要非常小的服务器端逻辑,并且没有持久的服务器端存储。

Admittedly, this does violate the "JS-Only" constraint you were looking for. However, a) again you really should NOT have a refresh token in javascript and b) it requires pretty minimal server-side logic at login/logout and no persistent server-side storage.

关于CSRF的说明:如评论中所述,此解决方案未涉及跨站点请求伪造;有关解决这些问题的进一步想法,请参阅 OWASP CSRF预防备忘单攻击形式。

Note on CSRF: As noted in the comments, this solution doesn't address Cross-site Request Forgery; see the OWASP CSRF Prevention Cheat Sheet for further ideas on addressing these forms of attacks.

另一种选择是根本不要求刷新令牌(不确定这是否是您正在处理的OAuth 2实施的选项;刷新令牌是可选的根据规范)并在其到期时不断重新进行身份验证。

Another alternative is simply to not request the refresh token at all (not sure if that's an option with the OAuth 2 implementation you're dealing with; the refresh token is optional per the spec) and continually re-authenticate when it expires.

希望有所帮助!

这篇关于在HTML5 Web App中使用OAuth2的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆