This is something I've tried to get some information on previously but I've never found the actual answer or solution to the problem. So hopefully someone can clarify and point me in the right direction.
I've split the problems into 3 questions at the bottom, so if they could be answered as 1,2,3 that'd make things much easier to digest and help me get my head around this.
So basically, I have an OAuth2 sever setup using CakePHP that the following JavaScript can municate with to allow a user to login and get an access token and then make various requests to different endpoints using this token to send and receive data.
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>Wele</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: '',
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: '',
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: '',
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();
}
};
Hopefully that code all makes sense... but in a nutshell, it sends a username and password to the API which then sends back both an access_token and refresh_token that I then store using the localStorage in HTML5. The refresh_token is used to get a new access_token once the access_token no longer works so the user gets a seamless experience without having to keep logging in (unless they actually log out!). This is handled by the checkTokenValid
function which I call to check it's still valid, and the either request a new token or make the user log in again if the refresh_token doesn't exist (or is also invalid).
The first problem is having to store the refresh_token. This would normally not be an issue as it'd be stored server-side, but because it's client side the client ID is exposed and therefore if someone was to access the users browser, they could request new tokens. So how would I keep a user logged in (i.e. request a new access_token automatically) without using refresh tokens? Is this even an issue as it's only on the users machine!
The second problem, is I've been told I shouldn't be using this type of grant type (Password/Resource Owner Password Credentials), because it's client-side and therefore things like the client id, and secret can't be protected. And I should instead, be using Implicit. However I've not been able to see how this will help me solve the first problem. Can anyone show an example of this? And how it would solve the refresh_token issue above. From what I have read regarding the implicit grant type, all it's doing is just simplifying the token process (by removing the need for the client ID) and doesn't actually do anything different.
Finally, because the application will ALWAYS be the only application using the API, there is no need for the user to ever go via the token grant type process, so the whole setting up of client IDs seems overkill for what is just some JavaScript talking to an API. What other options do I have? I've thought about sacking off the OAuth and just going with Basic Auth instead... But what about sessions? As I won't have a token then! Thoughts?
This is something I've tried to get some information on previously but I've never found the actual answer or solution to the problem. So hopefully someone can clarify and point me in the right direction.
I've split the problems into 3 questions at the bottom, so if they could be answered as 1,2,3 that'd make things much easier to digest and help me get my head around this.
So basically, I have an OAuth2 sever setup using CakePHP that the following JavaScript can municate with to allow a user to login and get an access token and then make various requests to different endpoints using this token to send and receive data.
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>Wele</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./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./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./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();
}
};
Hopefully that code all makes sense... but in a nutshell, it sends a username and password to the API which then sends back both an access_token and refresh_token that I then store using the localStorage in HTML5. The refresh_token is used to get a new access_token once the access_token no longer works so the user gets a seamless experience without having to keep logging in (unless they actually log out!). This is handled by the checkTokenValid
function which I call to check it's still valid, and the either request a new token or make the user log in again if the refresh_token doesn't exist (or is also invalid).
The first problem is having to store the refresh_token. This would normally not be an issue as it'd be stored server-side, but because it's client side the client ID is exposed and therefore if someone was to access the users browser, they could request new tokens. So how would I keep a user logged in (i.e. request a new access_token automatically) without using refresh tokens? Is this even an issue as it's only on the users machine!
The second problem, is I've been told I shouldn't be using this type of grant type (Password/Resource Owner Password Credentials), because it's client-side and therefore things like the client id, and secret can't be protected. And I should instead, be using Implicit. However I've not been able to see how this will help me solve the first problem. Can anyone show an example of this? And how it would solve the refresh_token issue above. From what I have read regarding the implicit grant type, all it's doing is just simplifying the token process (by removing the need for the client ID) and doesn't actually do anything different.
Finally, because the application will ALWAYS be the only application using the API, there is no need for the user to ever go via the token grant type process, so the whole setting up of client IDs seems overkill for what is just some JavaScript talking to an API. What other options do I have? I've thought about sacking off the OAuth and just going with Basic Auth instead... But what about sessions? As I won't have a token then! Thoughts?
- +1. I have been trying to get an answer to this (offered a bounty) as well, without much luck. Best answers are along the ones posted here that just confirm that once you're in a SPA with an implicit flow, there isn't a good option for persisting the token on the client. Of the two that I have seen implemented, there is one with a http only cookie for storing a refresh token and the one Taiseer suggests with CORS. It really is a shame there isn't more published about the way to handle this in SPAs. – tstojecki Commented Sep 19, 2014 at 8:52
3 Answers
Reset to default 41. How to keep your users from having to log in on every request
Nobody forces you to issue refresh tokens, or to expire access tokens on every request. If you issue an access token that is valid for an hour or two, that should be enough time for a user to use the website without allowing the sort of "endless" refresh chain refresh tokens offer (as typically using a refresh token will result in both a new access and refresh token being issued). Choose an expiration period that will allow for the typical user action period, allow some extra, and you can avoid issuing refresh tokens altogether.
Of course, there is some risk that a user will exceed that time span and be forced to log in again. I think usually if that happens, if it is explained to them that it happens for security reasons and what happened in easy, non-technical terms, they will be okay with that. In the end, it's always a trade-off between best security and best usability: better usability would be using refresh tokens, obviously - your users will never be forced to re-login while they're using the page - but if you do, you have to live with there being a risk of them being promised.
Local storage is protected by a same origin policy, so it's about as secure as anything the browser has to offer for storing things. Personally, I like the idea of using a session cookie for storing the tokens - it works across browser tabs, gets cleared when the browser is closed and when the user manually clears their cookies, they get the expected behavior of being "forgotten" by your page (which they won't with browser local/session storage). If you do this, make sure to use a secure only cookie. Setting the cookie path to a non-existent value will keep it from being transmitted on every request, although this is purely a message size consideration, as it will get transmitted on every request anyways (in the Authorization header). Whatever storage type you use, you are always susceptible to XSS attacks, so make very sure to guard well against it.
My point in a nutshell: You're not required to issue refresh tokens or expire access tokens on every request. If you want to avoid using refresh tokens, make your access tokens live longer - but you'll have to live with there being a chance that users will have to re-authenticate while using your website.
2. Use of the Implicit flow over the Resource Owner Password Credentials flow
The reason that you're discouraged from using the Resource Owner Password Credentials flow normally is because it forces the user to expose their credentials for another identity provider that you're using to authenticate them. I.e. if you're allowing your users to sign in using their Google/Facebook, you shouldn't use that flow because the user would have to give their username and password to your app/website, and you could do all kinds of shenanigans with them.
Since you on the other hand, from how I understand your question, have your own OAuth2 server up and running, you're your own identity provider - you already have access to the user's credentials (user name and hashed password), so avoiding the resource owner flow has no additional security benefits. RFC 6749 actually does not specify a client id and secret be used for this flow (and it would not make much sense, either), hence, no need to protect what you don't have to supply.
My point in a nutshell: Because you are your own identity provider using the Resource Owner Password Credentials flow is okay. You don't need a client id and/or secret for this. The Autorization Grant flow and Implicit flow are meant for when you authenticate against a different identity provider (such as Google or Facebook)
3. To keep or not to keep token based authentication
The probably most monly used authentication mechanism is still session based, i.e. the server, when you have logged in, issues you a session id by which it keeps track of the fact that you've successfully authenticated. Typically this session id gets stored as a cookie. The beauty is that you have pretty much no implementation concerns - set the cookie, and the browser will handle pretty much all the nitty gritty details for you. On the server, all you have to do is check that the session is valid. However, this mechanism is still susceptible to XSS attacks, and, worse, to XSRF (cross-site request forgery). While not impossible to guard against, it is a bit of a pain to detect and prevent this from happening. With a token based authentication system, you get XSRF protection built-in.
My point in a nutshell: Since you already have an OAuth2 server up and running, I would stick with it. If you go with the Resource Owner Password Credentials flow, there is no need for client id and secret to be used.
- Refresh tokens are optional for the flow type you are using. You can use the password flow when required to obtain another acesss token and dispense with refresh tokens.
- The resource owner grant type is the right flow type for your case since it is your client using your oauth server. The use of client ids is not required or meaningful since your client is a browser based client. To get a feel for it, Imagine you were instead using a third party oauth server, it would be their browser client code asking for the password or checking some cookie or localstorage state. The dirty work of asking the user to authorise the action and provide their username and password has to be done somewhere and since in your case its not a third party, its your oauth server and web client doing the asking its unavoidable and must be done.
- Stick with the resource owner grant type which yields an access token. Its up to you if you want to actually store this in localStorage, it won't be as user friendly and will require a relogin every time a hard page refresh is done but it is more secure if you are concerned about tokens being accessible on the client. Although an attacker with that level of access to the client can mount many other attacks so keep it in perspective.
In order to get a refresh token you need to use the code flow, not the implicit grant flow.
You cannot securely refresh a token in the implicit flow.
Obviously you can implement the authorization code flow in the client (i.e - as if it were a server), but that causes 2 problems:
1) If your IDP (Identity Provider) is in another domain than your client, your browser will prevent you from making HTTP calls to generate tokens from the user generated code (or using the refresh_token).
2) Your client secret will be available from inside the client - a security flaw. The refresh token will also be available - another security flaw.
In a nutshell - there cannot be a "seamless refresh experience" when using the implicit oAuth flow.