Using OAuth2 in an HTML5 Web Application

I'm currently experimenting with OAuth2 to develop a fully JavaScript mobile application that speaks with the CakePHP API. Take a look at the following code to see how my application currently looks (note that this is an experiment, therefore, 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(); } }; 

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

1.) Apparently, storing access_token in localStorage is bad practice and I should use cookies instead. Can anyone explain why? Since this is not more secure or less secure, as far as I can tell, since the cookie data will not be encrypted.

UPDATE: In accordance with this question: Local storage and cookies storing data in localStorage is ONLY available on the client side and does not make any HTTP request, unlike cookies, it seems to me more secure, or at least for me that's no problem at all!

2.) Regarding question 1, using a cookie for the expiration will be equally meaningless to me, as if you looked at the code, the application will start asking for information about the user, which will return an error if it expired on the server and requires refresh_token . Therefore, Iโ€™m not sure about the benefits of expiration time on the BOTH client and server when the server really matters.

3.) How do I get the update token, without A, storing it with the original access_token for later use, and B) also storing the client_id? I was told that this is a security issue, but how can I use them later, but protect them in a JS application? Review the above code again to find out how I have done so far.

+44
javascript
Aug 16 '13 at 19:34
source share
3 answers

It looks like you are using the password credentials of the resource owner . OAuth 2.0 stream, for example. by sending username / password to return the access token and update the token.

  • the access token MAY be displayed in javascript, the risks associated with the access token being exposed in some way are mitigated by its short lifespan.
  • The update token MUST NOT be exposed on the JavaScript javascript side. It used to get more access tokens (as you do above), but if an attacker was able to get an update token, he will be able to get more access tokens at his discretion until the OAuth server invalidates the client for which the refresh token was issued .

Given this question, let me ask your questions:

  • Either the cookie or localstorage will give you local storage on the update page. Storing an access token in local storage gives you a little more protection against CSRF attacks, as it will not automatically be sent to the server as a cookie. Your client javascript will need to pull it from localstorage and pass it on every request. I am working on an OAuth 2 application, and because this is a one-page approach, I do not; instead, I just keep it in my memory.
  • I agree ... if you save in a cookie this is only for saving not for the expiration, the server will respond with an error when the token expires. The only reason I can think that you can create an expired cookie is that you can determine if it expired WITHOUT making a request first and waiting for an error response. Of course, you can do the same with local storage by preserving this known expiration time.
  • This is the essence of the whole question that I consider ... "How can I get the update token without A, storing it with the original access_token for later use, and B), also storing the client_id." Unfortunately, you really cannot ... As noted in this introductory comment, the presence of the client side refresh token denies the security provided by the access token , which has a limited lifetime. What I do in my application (where I do not use a constant session state on the server side) is as follows:
    • User sends username and password to server
    • the server then redirects the username and password to the OAuth endpoint in your example above http://domain.com/api/oauth/token and gets the access token and update token ... li>
    • The server encrypts the refresh token and sets it to a cookie (should only be HTTP)
    • The server responds only with a clear-text access token (in a JSON response) and an encrypted HTTP cookie
    • client-side javascript can now read and use an access token (stored in local storage or something else
    • When the access token expires, the client sends a request to the server (not the OAuth server, but the server on which the application is hosted) for the new token
    • The server receives the encrypted HTTP cookie that was created, decrypts it to get the refresh token, requests a new access token, and finally returns a new access token answer.

Admittedly, this violates the "JS-Only" restriction you were looking for. However, a) again you really should NOT have an update token in javascript, and b) it requires fairly minimal server-side logic when you log in / out and there is no persistent storage on the server side.

Note on CSRF . As noted in the comments, this solution does not address the cross-site request subroutine ; see the OWASP CSRF Cheat Sheet for further ideas on eliminating these forms of attacks.

Another alternative is simply not to request an update token (not sure if this is the OAuth 2 implementation you are dealing with, the update token is optional for the specification ) and constantly re-authenticated after expiration.

Hope this helps!

+78
Aug 23 '13 at 0:35
source share

The only way to be fully protected is not to store the client side of access tokens. Anyone who has (physical) access to your browser can get your token.

1) Your assessment that you are not a great solution is accurate.

2) Use of expiration dates will be your best if you are limited only by the development of the client side. This will not require your users to re-authenticate with Oauth so often, and ensure that the token will not live forever. Still not the safest.

3) Obtaining a new token will require the Oauth workflow to complete in order to receive a new token. Client_id is bound to a specific domain for Oauth to work.

The safest method for saving Oauth tokens is a server-side implementation.

+4
Aug 21 '13 at 13:45
source share

For the pure client side only, if you have a chance, try using an "Implicit stream" rather than a "resource owner stream". You are not getting an update token as part of the response.

  • When a JavaScript user access page checks for access_token in localStorage and checks its expires_in
  • If there is no or expiration, the application opens a new tab and redirects the user to the login page, after the user has successfully logged in, the user is redirected with an access token, which is processed only on the client side and stored in the local storage with the redirect page.
  • The main page may have a mechanism for polling an access token in the local storage, and as soon as the user logs in (the redirect page saves the token in the storage).

In the above approach, the access token should be long (e.g. 1 year). If there is a problem with a long live token, you can use the following trick.

  • When a JavaScript user access page checks for access_token in localStorage and checks its expires_in
  • If there is no or expiration, the application opens a hidden iframe and tries to log in. Usually on the auth website there is a user cookie and is stored on the client's website, so login is automatic and the script inside the iframe will fill the token in the store
  • On the clientโ€™s main page, a polling mechanism for access_token and timeout is set. If during this short period the access_token is not populated in the repository, this means that we need to open a new tab and set the normal Implicit flow in motion
+1
Jun 07 '15 at 18:00
source share



All Articles