Help us understand the problem. What is going on with this article?

iOSでPWAとして起動されたときにLocalStorageに保存したtokenを利用してオートログインさせる

More than 1 year has passed since last update.

背景

iOS(11.3時点)のPWAではCookieが起動ごとに初期化され、Session(Cookie)を利用したログイン状態の保持ができません。追加調査でLocalStorageについては初期化されないことがわかったため、LocalStorageを利用したオートログインの実現について実験しました

来週開催されるWWDCやiOS12で解消されるのを切に願ってます😭

過去の調査内容

サンプルアプリ

調査のために作成したサンプルアプリは今後もiOS, Androidバージョンアップ時に利用する可能性がありそうなためHerokuで公開しておきました。(無料プランのためしばらくアクセスがなかった場合はページが表示されるまで少し時間がかかります)

https://pwa-autologin-test-app.herokuapp.com/

pwa-autologin-test-app.png

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)のみ保存します。

  1. ログインページへ遷移する(↓)
    normal01.png

  2. ログインフォームでName/Passwordを入力してログインする(↓)
    normal02.png

  3. ログインに成功するとSessionとCookieにuser.idを保存する/tokenは保存しない(↓)
    normal03.png

iOSでPWAとして利用する場合

iOSでPWAとして利用する場合は専用のフォームが利用され、Session(Cookie)に加えてオートログインで利用するtokenも保存します。

  1. ホームスクリーンからアプリを起動する(↓)
    ios01.png

  2. 初回はiosPWA専用のログインフォームでName/Passwordを入力してログインする(↓)
    ios02.png

  3. ログインに成功するとSessionとCookieにuser.idを保存し、LocalStorageにtokenを保存する(↓)
    ios03.png

tokenが保存された状態でPWAとして起動すると、オートログインを実行します。

  1. ホームスクリーンからアプリを起動する(↓)
    ios04.png

  2. LocalStorageに保存されたtokenを利用してオートログインが実行される(↓)
    ios05.png

AndroidでPWAとして利用する場合

AndroidでPWAとして利用する場合もSession(Cookie)のみ保存します。

  1. ホームスクリーンからアプリを起動する(↓)
    android01.png

  2. ログインフォームでName/Passwordを入力してログインする(↓)
    android02.png

  3. ログインに成功するとSessionとCookieにuser.idを保存する/tokenは保存しない(↓)
    android03.png


ログイン方法、個体識別番号(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であると判断できる
app/controllers/sessions_controller.rb
    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を保存するのは共通の処理としてあります。

/views/sessions/new.html.erb
<%= form_for :session, url: sessions_path, remote: (@pwa == true ? true : false) do |f| %>
  <!-- 省略 -->
<% end%>
app/controllers/sessions_controller.rb
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
app/views/sessions/create.js.erb
// LocalStorageにtokenを保存する
localStorage.setItem("token", "<%= @token %>");
// root_pathへ移動
window.location.href = '<%= root_path %>';

ログアウト時の動作

ログアウト時の動作は共通で、sessionリセットCookieからuser_id削除LocalStorageからtoken削除を実行します。

app/controllers/sessions_controller.rb
def destroy
    # 省略
    reset_session
    cookies.delete :user_id
    # 省略
end
app/views/sessions/destroy.js.erb
// 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通信が実行され自動でログインを試みます。

app/views/sessions/new.html.erb
<!-- 省略 -->
<% 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認証の実装については以下の記事を参考にさせていただきました。

NaokiIshimura
Ruby on Rails Engineer.
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした