post Image
railsとjwtでswiftアプリケーションのログイン機能を実装する

今更感がありますがswift案件に今年かかわり認証周りにもっといい方法は無いかと探していたときにjwtが良さそうだったのでrailsとswiftで実装しました。

当初はknockも使う予定でしたがsorceryとメソッド名が重複していたかだったかで上手く同居させることができなかったためあきらめました

動作環境

macOS
docker for mac
swift4
Xcode9.2

サンプルコード

アプリからログイン後にユーザのプロフィールを表示するサンプル
githubにコードおいてあります。

起動

rails

docker-compose up

$ cd auth-sample/auth-sample-server
$ docker-compose up

migration

$ docker exec authsampleserver_web_1 ./bin/rake db:migrate

swift

carthage

carthage updateしてプロジェクトをビルドしてrealmなどのライブラリが見つからないと言われたらcarthageでビルドされた各種フレームワークを追加してください。

$ cd auth-sample/auth-sample-client
$ carthage update --platform iOS

コード

railsのAPIモードは使ってません。
アプリのサービスだったとしてもほぼ管理画面が必要になったりするので、プロジェクトを分けたりするとデプロイに気を使ったり、モデル周りのコードが分散してしまったりで走り初めのサービスは同じリポジトリにコードがまとまっていたほうがメリットのほうが大きいためです。
実際にプロジェクトに導入するときはgrapeなどのgemを使って実装し直してもいいかもしれないです。

loginボタンを押下するとIBActionで設定されたloginメソッドが実行されます

LoginViewController.swift
class LoginViewController: UIViewController {

    @IBOutlet weak var emailField: UITextField!
    @IBOutlet weak var passwordField: UITextField!

    let loginURL: String = "http://localhost:3000/api/user_sessions"
    lazy var indicator: UIActivityIndicatorView = {
        let indicator = UIActivityIndicatorView()
        indicator.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
        indicator.center = self.view.center
        indicator.hidesWhenStopped = true
        indicator.activityIndicatorViewStyle = .gray
        return indicator
    }()
    lazy var alertViewController: UIAlertController = {
        let alert: UIAlertController = UIAlertController(title: "エラー", message: "ログインに失敗しました", preferredStyle: .alert)
        let cancelAction: UIAlertAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
        alert.addAction(cancelAction)
        return alert
    }()

    @IBAction func login(_ sender: Any) {
        let param = ["email": self.emailField.text ?? "", "password": self.passwordField.text ?? ""]
        self.indicator.startAnimating()
        Alamofire.request(self.loginURL, method: .post, parameters: param, encoding: URLEncoding.default).responseJSON { [weak self] response in
            self?.indicator.stopAnimating()
            guard response.response?.statusCode == 200 else {
                if let alertViewController = self?.alertViewController {
                    self?.present(alertViewController, animated: true, completion: nil)
                }
                return
            }

            if let data = response.data {
                let decoder: JSONDecoder = JSONDecoder()
                if let user = try? decoder.decode(UserCodable.self, from: data) {
                    User.create(codable: user)
                    let sb = UIStoryboard(name: "Profile", bundle: nil)
                    if let vc = sb.instantiateInitialViewController(){
                        vc.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
                        self?.present(vc, animated: true, completion: nil)
                    }
                }
            }
        }
    }
}

アプリからリクエストを受けたrailsのコードです。
user_idからjwtトークンを生成して返却します。
アプリはこのトークンを他のリクエスト時にHTTP headerのAuthorizationに追加してリクエストを行います。
jsonにuserオブジェクトを雑にぶん投げてますが本番ではやってはいけません、passwordなどのフィールドが含まれてるので絶対にフィルタリングしてください。

api/user_sessions_controller
class Api::UserSessionsController < Api::ApplicationController
  def create
    user = User.authenticate(params[:email], params[:password])

    if user
      token = Jwt::TokenProvider.(user_id: user.id)
      render json: {user: user, token: token}
    else
      render json: {error: 'Error description'}, status: 422
    end
  end
end

ユーザのプロフィールを取得するAPIです。

user_controller.rb
class Api::UsersController < Api::ApplicationController
  before_action :authenticate

  def show
    render json: current_user
  end
end
api/application_controller.rb
class Api::ApplicationController < ActionController::Base
  def authenticate
    render json: {errors: 'Unauthorized'}, status: 401 unless current_user
  end

  def current_user
    @current_user ||= Jwt::UserAuthenticator.(request.headers)
  end
end
service/jwt/user_authenticator.rb
module Jwt::UserAuthenticator
  extend self

  def call(request_headers)
    @request_headers = request_headers

    begin
      payload, header = Jwt::TokenDecryptor.(token)
      return User.find(payload['user_id'])
    rescue => e
      # log error here
      return nil
    end
  end

  def token
    @request_headers['Authorization'].split(' ').last
  end
end
service/jwt/token_decryptor.rb
module Jwt::TokenDecryptor
  extend self

  def call(token)
    decrypt(token)
  end

  private
  def decrypt(token)
    begin
      JWT.decode(token, Rails.application.secrets.secret_key_base)
    rescue 
       raise InvalidTokenError
    end
  end
end

class InvalidTokenError < StandardError; end;

注意点

JWTトークンが第三者に漏れてしまった時にそのユーザのトークンを無効にしないと不正アクセスし放題になってしまいますが、現在の実装方法だとトークンの無効化ができません。
理由はJWTトークンの発行にuser_idを使用しているためです。user_idは作成されたら変更しないものなのでこれをユーザモデルに紐付いたキーに変更しかつ有効期限をもたせるようなモデル設計に変える必要がありそうです。

この辺はユーザ作成のサンプルも作るのでその時に一緒に変更します。


『 Swift 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。