背景
iOS(11.3時点)のPWAではCookieが起動ごとに初期化され、Session(Cookie)を利用したログイン状態の保持ができません。追加調査でLocalStorageについては初期化されないことがわかったため、LocalStorageを利用したオートログインの実現について実験しました
来週開催されるWWDCやiOS12で解消されるのを切に願ってます😭
過去の調査内容
- 【調査】WEBアプリをPWAとして起動した場合にブラウザのCookieが引き継がれるか確認する - Qiita
- 【調査】WEBアプリをPWAとして起動した場合にブラウザのlocalStorageが引き継がれるか確認する - Qiita
サンプルアプリ
調査のために作成したサンプルアプリは今後もiOS, Androidバージョンアップ時に利用する可能性がありそうなためHerokuで公開しておきました。(無料プランのためしばらくアクセスがなかった場合はページが表示されるまで少し時間がかかります)
Loginに利用するユーザ名/パスワードは以下をご利用ください。
- name : test
- password: hoge
ソースコード
NaokiIshimura/Qiita-PWA-AutoLogin
アプリの説明
- ブラウザやAndroidでログインするとSession/Cookieにuser.id保存する。
- iOSでPWAとして起動した後にログインするとSession/Cookieにuser.id、LocalStorageにtokenを保存する。
- iOSでPWAとして起動した際にLocalStorageにtokenが保存されてるとオートログインを実行する
ブラウザで利用する場合
PC/iOS/Androidブラウザからアクセスしてログインした場合は、tokenを利用したオートログインは不要なためSession(Cookie)のみ保存します。
iOSでPWAとして利用する場合
iOSでPWAとして利用する場合は専用のフォームが利用され、Session(Cookie)に加えてオートログインで利用するtokenも保存します。
tokenが保存された状態でPWAとして起動すると、オートログインを実行します。
AndroidでPWAとして利用する場合
AndroidでPWAとして利用する場合もSession(Cookie)のみ保存します。
ログイン方法、個体識別番号(uuid)について
海外のフォーラムにおいてもiOSのPWAにおいてSession(Cookie)が保持されないことに対する解決策については議論されており、自分が調べた感じでは以下の解決案が提示されていました。
解決案1
LocalStorageに個体識別情報(uuid)やトークンなどを保存させて、PWA起動時のオートログインで利用する
解決案2
start_urlに個体識別番号(uuid)を埋め込んで、PWA起動時のオートログインで利用する
例:https://xxx.xxx.xxx.xxx/login?uuid=xxxxxxxxxx
解決案3
オフラインキャッシュさせるファイル(html, jsなど)に個体識別番号(uuid)を埋め込んで、、PWA起動時のオートログインで利用する
補足:Credential Management API
ログインをサポートするCredential Management APIについてはiOSではまだサポートされていないため、利用することができません。
Can I use... Support tables for HTML5, CSS3, etc
所感
- 解決案1:一番オーソドックスで実装も簡単そう🧐
- 解決案2:簡単に書き換え可能なURL部分にuuidを埋め込むのは少し怖い😇
- 解決案3:一部のPWAアプリはこの方法をとってみたいだけど、キャッシュの挙動を理解してないと採用するのは怖い😇
「LocalStorageに個体識別情報(uuid)やトークンなどを保存させて、PWA起動時のオートログインで利用する」が一番カンタンそうなので試してみることにしました😊
実装
ログイン部分のみ抜粋して解説させていただきます。
サーバサイドはRailsを利用してます。
不明点がありましたらコメントしていただけると助かります。
OS/起動契機の判定
以下の2つの条件がtrueの場合にログイン画面を「iOS PWA」用に切り替えてます。
- URLに「?launcher=true」が含まれている
- UserAgentからiOSであると判断できる
require 'browser'
@pwa = true if (params['launcher'] == 'true' && browser.platform.ios? )
UserAgentの判定にはbrowserというgemを利用してます。
ログイン時の動作
ログインフォームの挙動は「iOS PWA」と「それ以外」で分けてあり、「iOS PWA」の場合はremote: true
となり、Ajax通信でログインを実行します。ログインに成功するとcreate.js.erb
でLocalStorageにtokenを保存します。
「それ以外」の場合はremote: false
となり、通常のHTTP通信でログインが実行され、LocalStorageにtokenを保存する処理が行われないようにしてあります。
ログイン成功後はSessionやCookieを利用してユーザ情報を管理したいので、SessionとCookieにuser.idを保存するのは共通の処理としてあります。
<%= form_for :session, url: sessions_path, remote: (@pwa == true ? true : false) do |f| %>
<!-- 省略 -->
<% end%>
def create
# 省略
session[:user_id] = user.id
cookies[:user_id] = user.id
# 省略
respond_to do |format|
format.html { redirect_to root_path }
format.js { @token = user.token }
end
# 省略
end
// LocalStorageにtokenを保存する
localStorage.setItem("token", "<%= @token %>");
// root_pathへ移動
window.location.href = '<%= root_path %>';
ログアウト時の動作
ログアウト時の動作は共通で、sessionリセット、Cookieからuser_id削除、LocalStorageからtoken削除を実行します。
def destroy
# 省略
reset_session
cookies.delete :user_id
# 省略
end
// LocalStorageからtokenを削除する
localStorage.removeItem("token");
// root_pathへ移動
window.location.href = '<%= root_path %>';
オートログイン
start_urlにはログイン画面へのパスとPWA起動を示すパラメータが指定されているので、iOS端末がPWAとして起動させると必ずログイン画面が表示されます。
- PWAとして起動された(URLに?launcher=trueが付与されている)
- UserAgentからiOSであると判断できる
- 未ログイン状態である
- LocalStorageにtokenが保存されている
という条件が揃うと以下のJavaScript内でAjax通信が実行され自動でログインを試みます。
<!-- 省略 -->
<% if @pwa %>
<script>
// ログインしてない場合、
<% if logged_in? == false %>
$(function () {
// autoLoginを実行させる
autoLogin();
});
<% end %>
function autoLogin() {
console.log("[debug] auto login");
// LocalStorageからtokenを取得
var token = localStorage.getItem("token");
// tokenが空じゃない場合、
if (token != null) {
console.log("[debug] Token exists");
// POSTするjsonを作成
var data = {
"authentication":
{
token: token
}
};
// Ajaxでtoken認証を実施
$.ajax({
type: "post",
url: "<%= authentication_path %>",
data: JSON.stringify(data),
contentType: 'application/json',
dataType: "json",
// token認証を成功した場合、
success: function (data) {
console.log("[debug] auto login success");
// root_pathへページ遷移
window.location.href = '<%= root_path %>';
},
// token認証に失敗した場合、
error: function (data) {
console.log("[debug] auto login fail");
}
});
} else {
console.log("[debug] No token exists");
}
}
</script>
<% end %>
実装後に思ったこと
個人的にはオートログインに利用する情報がtoken 1つだけだと心もとないので、AWSのCLIなどのように2種類のtoken(key)を利用したり、tokenに利用期限を設けたり、定期的にリフレッシュさせたりする必要があると感じた。
参考
ユーザ認証
ユーザ認証の実装については以下の記事を参考にさせていただきました。
token認証
token認証の実装については以下の記事を参考にさせていただきました。