J'expérimente actuellement avec OAuth2 pour développer une application mobile entièrement construite en JavaScript qui parle à une API CakePHP. Jetez un oeil au code suivant pour voir à quoi ressemble actuellement mon application (veuillez noter qu'il s'agit d'une expérience, d'où le code désordonné et le manque de structure dans les zones, 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();
}
};
J'ai un certain nombre de questions concernant ma mise en œuvre d'OAuth:
1.) Stocker apparemment le access_token dans localStorage est une mauvaise pratique et je devrais plutôt utiliser des cookies. Quelqu'un peut-il expliquer pourquoi? Comme cela n'est plus sécurisé ou moins sécurisé pour autant que je sache, car les données des cookies ne seraient pas cryptées.
MISE À JOUR: Selon cette question: Local Storage vs Cookies le stockage des données dans localStorage est de toute façon UNIQUEMENT disponible côté client et ne fait aucune requête HTTP contrairement à les cookies, me semble donc plus sécurisé, ou du moins ne semble pas avoir de problème pour autant que je sache!
2.) En ce qui concerne la question 1, l'utilisation d'un cookie pour le temps d'expiration, serait également inutile pour moi, comme si vous regardez le code, une demande est faite au démarrage de l'application pour obtenir les informations utilisateur, ce qui retournerait une erreur si il avait expiré du côté serveur et nécessitait un refresh_token. Donc, je ne suis pas sûr des avantages d'avoir des délais d'expiration sur le client et le serveur, lorsque celui du serveur est ce qui compte vraiment.
3.) Comment puis-je obtenir un jeton d'actualisation, sans A, en le stockant avec le access_token d'origine pour l'utiliser plus tard, et B) en stockant également un client_id? On m'a dit qu'il s'agissait d'un problème de sécurité, mais comment puis-je les utiliser plus tard, mais les protéger dans une application JS uniquement? Voir à nouveau le code ci-dessus pour voir comment j'ai implémenté cela jusqu'à présent.
Il semble que vous utilisiez le flux Informations d'identification du mot de passe du propriétaire de la ressource OAuth 2.0, par exemple soumettre un nom d'utilisateur/pass pour récupérer à la fois un jeton d'accès et un jeton d'actualisation.
Dans ce contexte, permettez-moi de répondre à vos questions:
http://domain.com/api/oauth/token
, Et reçoit à la fois le jeton d'accès et le jeton d'actualisation =.Certes, cela viole la contrainte "JS-Only" que vous recherchiez. Cependant, a) encore une fois, vous ne devriez PAS avoir de jeton d'actualisation en javascript et b) cela nécessite une logique côté serveur assez faible à la connexion/déconnexion et aucun stockage persistant côté serveur.
Note sur CSRF: Comme indiqué dans les commentaires, cette solution ne traite pas Cross-site Request Forgery ; voir le OWASP CSRF Prevention Cheat Sheet pour plus d'idées sur la lutte contre ces formes d'attaques.
Une autre alternative consiste simplement à ne pas demander du tout le jeton d'actualisation (je ne sais pas si c'est une option avec l'implémentation OAuth 2 dont vous avez besoin; le jeton d'actualisation est facultatif selon les spécifications ) et se ré-authentifier continuellement à son expiration.
J'espère que cela pourra aider!
La seule façon d'être totalement sécurisé est de ne pas stocker les jetons d'accès côté client. Toute personne ayant un accès (physique) à votre navigateur peut obtenir votre jeton.
1) Votre évaluation selon laquelle ni l'une ni l'autre n'est une excellente solution est exacte.
2) L'utilisation des délais d'expiration serait la meilleure solution si vous êtes limité au développement côté client uniquement. Cela n'obligerait pas vos utilisateurs à se ré-authentifier avec Oauth aussi souvent, et garantirait que le jeton ne vivrait pas éternellement. Toujours pas le plus sûr.
3) L'obtention d'un nouveau jeton nécessiterait d'effectuer le flux de travail Oauth pour obtenir un nouveau jeton. L'identifiant client_id est lié à un domaine spécifique pour que Oauth fonctionne).
La méthode la plus sûre pour conserver les jetons Oauth serait une implémentation côté serveur.
Pour une approche purement côté client uniquement, si vous en avez l'occasion, essayez d'utiliser "Implicit Flow" plutôt que "Resource owner flow". Vous ne recevez pas de jeton d'actualisation dans le cadre de la réponse.
Dans l'approche ci-dessus, le jeton d'accès devrait durer longtemps (par exemple 1 an). S'il y a un problème avec un jeton à longue durée de vie, vous pouvez utiliser l'astuce suivante.