前提・実現したいこと
Ruby on RailsとSwiftUIで以下のようなログイン機構を実装しております。
Rails側でデバッグするとログイン処理中はcurrent_userが取得できますが
違う部分でAPIを叩くとcurrent_userがnilになってしまいます。
ログイン処理は正確に動きますが、ログアウト処理の部分が動きません。
ログアウトの場合も.deleteでAPIにリクエストを送りたいのですが、そのためには上記のログイン処理中にSwift側でもcurrent_userを保持しておく必要があると思いますが、どのような処理を実装してログアウトの.deleteでAPIではどのようなパラメータを送れば良いでしょうか?
ruby
1routes.rb 2post '/login', to: 'sessions#create' # ログイン 3delete '/logout', to: 'sessions#destroy' # ログアウト
Ruby
1helper/session_helper.rb 2module SessionsHelper 3 # 渡されたユーザーでログインする 4 def log_in(user) 5 session[:user_id] = user.id 6 end 7 8# 現在ログイン中のユーザーを返す 9 def current_user 10 if session[:user_id] 11 @current_user ||= User.find_by(id: session[:user_id]) 12 end 13 end 14 15#受け取ったユーザーがログイン中のユーザーと一致すればtrueを返す 16 def current_user?(user) 17 user == current_user 18 end 19 20# ユーザーがログインしていればtrue、その他ならfalseを返す 21 def logged_in? 22 !current_user.nil? 23 end 24 25# 現在のユーザーをログアウトする 26 def log_out 27 session.delete(:user_id) 28 @current_user = nil 29 end 30end
ruby
1controllers/sessions_controller.rb 2 3class SessionsController < ApplicationController 4 # ログイン処理 http://127.0.0.1:3000/login.json 5 def create 6 user = User.find_by(email: params[:session][:email].downcase) 7 if user && user.authenticate(params[:session][:password]) 8 log_in user 9 render json: { status: 'SUCCESS LOGIN', user: user } 10 else 11 render json: { status: 'SUCCESS FAILD', user: user } 12 end 13 end 14 15 # ログアウト処理 http://127.0.0.1:3000/logout.json 16 def destroy 17 log_out if logged_in? <- Swift側からAPIを叩くとここでcurrent_userがnilになる(swift側でcurrent_userの値を保持できていない) 18 redirect_to root_url 19 end 20end
Swift側は以下のようになっております。
Swift
1ContentView.swift 2 3import SwiftUI 4import Alamofire 5 6class AppState: ObservableObject { 7 @Published var isLogin = false 8} 9 10struct ContentView: View { 11 @State private var email: String = "" 12 @State private var password: String = "" 13 14 @EnvironmentObject var appState: AppState 15 16 var body: some View { 17 NavigationView { 18 VStack { 19 20 NavigationLink(destination: HomeView(), isActive: self.$appState.isLogin) { 21 EmptyView() 22 } 23 24 TextField("メールアドレス", text: $email) 25 TextField("パスワード", text: $password) 26 Button(action: { 27 28 let parameters = [ 29 "user": [ 30 "email": self.email, 31 "password": self.password 32 ] 33 ] 34 35 AF.request("http://127.0.0.1:3000/login.json", 36 method: .post, 37 parameters: parameters, 38 encoder: JSONParameterEncoder.default) 39 .validate(contentType: ["application/json"]) 40 .responseJSON { response in 41 if response.response?.statusCode == 200 { 42 if let json = response.value as? NSDictionary { 43 print(json) 44 if json["status"] as? String == "SUCCESS LOGIN" { 45 self.appState.isLogin = true 46 debugPrint(response) 47 print("SUCCESS LOGIN") 48 } else if json["status"] as? String == "INVALID" { 49 debugPrint(response) 50 print("IN ¥VALID") 51 } 52 } 53 } 54 } 55 }, label: { 56 Text("ログインする") 57 }) 58 }.padding() 59 } 60 } 61} 62 63 64struct HomeView: View { 65 @EnvironmentObject var appState: AppState 66 var body: some View { 67 if appState.isLogin == false { 68 ContentView().environmentObject(AppState()) 69 } 70 VStack { 71 Text("ホーム画面") 72 73 Button(action: { 74 75 let parameters = [ <- ここのパラメータにどのような値をセットすればよいか分からないため、.delete(ログアウト処理)は失敗する 76 "": "" 77 ] 78 79 AF.request("http://127.0.0.1:3000/logout.json", 80 method: .delete, 81 parameters: parameters, 82 encoder: JSONParameterEncoder.default) 83 .validate(contentType: ["application/json"]) 84 .responseJSON { response in 85 debugPrint(response) 86 } 87 self.appState.isLogin = false 88 }, label: { 89 Text("ログアウトする") 90 }) 91 } 92 .navigationBarBackButtonHidden(true) 93 .navigationBarHidden(true) 94 } 95}
コードの部分が長くなりましたが、是非アドバイスなどよろしくお願い致します。
Rails よく分かりませんが、そもそもセッション管理ができてない (logout 以外でもエラーになる) のでは…。
Rails側では、以下の部分でセッションを使っておりますが、swift側でも別にセッション管理が必要という事でしょうか?
def log_in(user)
session[:user_id] = user.id
end
それってクッキーを使うやつですよね。その場合、Alamofire 側でいろいろやる必要があるようです。
https://qiita.com/kixixixixi/items/0a9b6ffe7e4cd38090fc
ありがとうございます‼︎
かなり難しいですね…
あまりよく分からないのですが、Swift側ではAlamofireとNSUserDefaultsを使ってセッション管理をするという事でしょうか?
自分も Alamofire は使ってないので詳しくはないのですが、さっきの記事はちょっと古いかもですね…。
あとでもうちょっと調べてみます。
ありがとうございます!!
何卒よろしくお願い致します。
私も調べてみます!!
Rails で API サーバーを書いて実験してみましたが、Alamofire はデフォルトで Cookie を処理してくれますね。
なので、Rails 側の問題のような気がします。直接 API を叩いて結果 (レスポンスヘッダーに Set-Cookie があるか) を確認できますか?
もしかすると application.rb に config.middleware.use を 2 行追加すればいいのかも。
https://qiita.com/someone7140/items/b89863c1ef395e63ee2b#applicationrb
あと、SessionsController ですが、ログイン失敗した時に user 情報を返すのは情報漏洩でまずいですね。
また、API なのでログアウト時にリダイレクトする意味もないような…。
ありがとうございます!!
APIを叩くと以下のようにレスポンスヘッダーにSet-Cookieは表示されるのですが。
Set-Cookie: _session_id=ef7cfaf18c7f9f0060edc3e377c7af18; path=/; HttpOnly
この値をどのようにSwift側で保存してログアウトの処理のパラメータに設定するかは、まだ分かっていないです。Swift側でもRails側と同じようにcurrent_userみたいな形で保持するためには、どのようにすればよいのでしょうか?
例)user.name <- ログインしているuserのnameを表示する
レスポンスに Set-Cookie があれば、次回からのリクエストにはその値が自動的に送られてサーバー側ではセッションが認識されるはずですね。
アプリ側でユーザー名などを表示するには、login の際にユーザー情報が返ってきてるはずなので、それを保存して使えば良いでしょう。
なるほどですね!!
Swift側では、ユーザー情報を保存する場合は、UserDefaultsを使うと良いのでしょうか?
ちなみに、ユーザーのstructは以下のようになってます。
struct User: Decodable, Identifiable, Encodable {
var id: Int
var name: String
var email: String
var deleted_at: String?
var created_at: String
var updated_at: String
}
loginの際に返ってくるユーザーの情報をどのようにして上記の構造体として保存するのでしょうか?
すみません、あまりコード実装のイメージが分かりません。
User 構造体を JSONEncoder でエンコードして UserDefaults に保存すれば良いのでは。
ありがとうございます!!
やってみます。
すみません、以下のようにして色々と試してみたのですが、User構造体をJSONEncoderでエンコードして UserDefaults に保存する方法の書き方が分かりませんでした。
let user = User(
id: json["id"] as! Int,
name: json["name"] as! String
email: json["email"] as! String
deleted_at: json["deleted_at"] as! String
created_at: json["created_at"] as! String
updated_at: json["updated_at"] as! String
)
let jsonEncoder = JSONEncoder()
jsonEncoder.keyEncodingStrategy = .convertToSnakeCase
guard let data = try? jsonEncoder.encode(user) else {
return
}
UserDefaults.standard.set(data, forKey: "user")
このやり方ですと、アプリがクラッシュしてしまいます。
もう少しだけ、アドバイスして頂けますでしょうか?
ご迷惑をお掛けしますが、何卒よろしくお願い致します。
alamofireでのAPIの結果は以下のようになっております。
[Result]: success(Optional(6030 bytes))
{
status = "SUCCESS LOGIN";
user = {
id = 1;
name = test01;
email = "test01@example.com";
"created_at" = "2021-03-04T06:31:57.896+09:00";
"updated_at" = "2021-03-07T20:43:18.051+09:00";
"deleted_at" = "<null>";
};
}
あなたの回答
tips
プレビュー