現在、CakePHP APIと通信するJavaScriptのみで構築されたモバイルアプリケーションを開発するために、OAuth2を実験しています。次のコードを見て、私のアプリの現在の状態を確認してください(これは実験であり、コードが乱雑で、エリアの構造が欠けているなどのことに注意してください)。
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.)localStorageにaccess_tokenを保存するのは悪い習慣であり、代わりにCookieを使用する必要があります。誰でもその理由を説明できますか?これは、Cookieデータが暗号化されないため、私が知る限り、これ以上安全ではないか、安全性が低いためです。
更新:この質問によると、 Local Storage vs Cookies localStorageにデータを保存することは、クライアント側でのみ利用可能であり、Cookieとは異なり、HTTP要求を行わないため、私にとってより安全であるか、少なくとも私が知る限り問題はないようです!
2.)質問1に関連して、有効期限にCookieを使用することは、コードを見ると、アプリの起動時にユーザー情報を取得する要求が行われるため、同様に無意味です。サーバー側で有効期限が切れており、refresh_tokenが必要です。そのため、クライアントとサーバーの両方で有効期限を設定することの利点は、サーバーが本当に重要な場合にわかりません。
3.)更新トークンを取得するには、Aなしで、後で使用するために元のaccess_tokenで保存し、B)client_idも保存しますか?これはセキュリティの問題であると言われましたが、これらを後で使用する方法はありますが、JS専用アプリで保護できますか?もう一度上記のコードを参照して、これまでどのようにこれを実装したかを確認してください。
Resource Owner Password Credentials OAuth 2.0フローを使用しているようです。たとえば、ユーザー名/パスを送信してアクセストークンと更新トークンの両方を取得します。
その背景を念頭に置いて、あなたの質問に答えさせてください:
http://domain.com/api/oauth/token
、およびアクセストークンとリフレッシュトークンの両方を受け取ります。確かに、これは探していた「JSのみ」の制約に違反します。ただし、a)JavaScriptでリフレッシュトークンを実際に使用しないでください。b)ログイン/ログアウト時にサーバー側のロジックが最小限で済み、サーバー側の永続的なストレージは必要ありません。
CSRFに関する注意:コメントで述べたように、この解決策は対処しません クロスサイトリクエストフォージェリ ;これらの形式の攻撃に対処するためのさらなるアイデアについては、 OWASP CSRF防止チートシート を参照してください。
別の代替方法は、単にリフレッシュトークンをまったく要求しないことです(それがOAuth 2実装のオプションであるかどうか不明です。リフレッシュトークンはオプションです spec )有効期限が切れたときに継続的に再認証します。
お役に立てば幸いです!
完全に安全にする唯一の方法は、クライアント側のアクセストークンを保存しないことです。ブラウザに(物理的に)アクセスできる人は誰でもトークンを取得できます。
1)どちらも優れたソリューションではないというあなたの評価は正確です。
2)有効期限の使用は、クライアント側の開発のみに制限されている場合に最適です。ユーザーがOauthのように頻繁に再認証する必要はなく、トークンが永久に存続しないことを保証します。それでも、最も安全ではありません。
3)新しいトークンを取得するには、Oauthワークフローを実行して新しいトークンを取得する必要があります。client_idは、特定のドメインに関連付けられてOauthが機能します。
Oauthトークンを保持するための最も安全な方法は、サーバー側の実装です。
純粋なクライアント側のみのアプローチの場合、機会があれば、「リソース所有者のフロー」ではなく "Implicit Flow" を使用してみてください。応答の一部として更新トークンを受け取りません。
上記のアプローチでは、アクセストークンは長生きする必要があります(1年など)。長寿命トークンに懸念がある場合は、次のトリックを使用できます。